summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Dodd <mdodd@google.com>2015-08-11 11:16:59 -0700
committerMike Dodd <mdodd@google.com>2015-08-12 08:58:28 -0700
commit461a34b466cb4b13dbbc2ec6330b31e217b2ac4e (patch)
treebc4b489af52d0e2521e21167d2ad76a47256f348
parent8b3e2b9c1b0a09423a7ba5d1091b9192106502f8 (diff)
downloadandroid_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.tar.gz
android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.tar.bz2
android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.zip
Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
-rw-r--r--Android.mk88
-rw-r--r--AndroidManifest.xml524
-rwxr-xr-xForceProguard.mk40
-rw-r--r--assets/licenses.html666
-rw-r--r--build/README8
-rw-r--r--build/android_lint.mk59
-rw-r--r--build/android_lint.xslt40
-rwxr-xr-xbuild/colorize_errors.py42
-rw-r--r--build/gcheckstyle.mk45
-rw-r--r--build/gcheckstyle/google-style-checker_deploy.jarbin0 -> 3621944 bytes
-rw-r--r--build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml411
-rwxr-xr-xbuild/process_style_output.py38
-rw-r--r--jni/Android.mk35
-rw-r--r--jni/GifTranscoder.cpp573
-rw-r--r--jni/GifTranscoder.h122
-rw-r--r--proguard-release.flags24
-rwxr-xr-xproguard-test.flags41
-rw-r--r--proguard.flags58
-rw-r--r--res/animator/fab_anim.xml37
-rw-r--r--res/color/tab_text_color.xml23
-rw-r--r--res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.pngbin0 -> 450 bytes
-rw-r--r--res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.pngbin0 -> 538 bytes
-rw-r--r--res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.pngbin0 -> 471 bytes
-rw-r--r--res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.pngbin0 -> 536 bytes
-rw-r--r--res/drawable-hdpi/cab_bg.9.pngbin0 -> 144 bytes
-rw-r--r--res/drawable-hdpi/contact_popup_background.9.pngbin0 -> 149 bytes
-rw-r--r--res/drawable-hdpi/fab_new_message_static_shadow.pngbin0 -> 2172 bytes
-rw-r--r--res/drawable-hdpi/ic_add_gray.pngbin0 -> 608 bytes
-rw-r--r--res/drawable-hdpi/ic_add_white.pngbin0 -> 370 bytes
-rw-r--r--res/drawable-hdpi/ic_archive_small_dark.pngbin0 -> 983 bytes
-rw-r--r--res/drawable-hdpi/ic_archive_small_light.pngbin0 -> 968 bytes
-rw-r--r--res/drawable-hdpi/ic_archive_undo_small_dark.pngbin0 -> 983 bytes
-rw-r--r--res/drawable-hdpi/ic_archive_undo_small_light.pngbin0 -> 967 bytes
-rw-r--r--res/drawable-hdpi/ic_arrow_back_dark.pngbin0 -> 789 bytes
-rw-r--r--res/drawable-hdpi/ic_arrow_back_light.pngbin0 -> 644 bytes
-rw-r--r--res/drawable-hdpi/ic_attachment_dark.pngbin0 -> 975 bytes
-rw-r--r--res/drawable-hdpi/ic_audio_light.pngbin0 -> 965 bytes
-rw-r--r--res/drawable-hdpi/ic_audio_pause.pngbin0 -> 1242 bytes
-rw-r--r--res/drawable-hdpi/ic_audio_play.pngbin0 -> 1387 bytes
-rw-r--r--res/drawable-hdpi/ic_camera_front_light.pngbin0 -> 1126 bytes
-rw-r--r--res/drawable-hdpi/ic_camera_light.pngbin0 -> 1116 bytes
-rw-r--r--res/drawable-hdpi/ic_camera_rear_light.pngbin0 -> 935 bytes
-rw-r--r--res/drawable-hdpi/ic_cancel_small_dark.pngbin0 -> 655 bytes
-rw-r--r--res/drawable-hdpi/ic_cancel_small_light.pngbin0 -> 1388 bytes
-rw-r--r--res/drawable-hdpi/ic_checkbox_blank_light.pngbin0 -> 423 bytes
-rw-r--r--res/drawable-hdpi/ic_checkbox_light.pngbin0 -> 895 bytes
-rw-r--r--res/drawable-hdpi/ic_checkbox_outline_light.pngbin0 -> 1030 bytes
-rw-r--r--res/drawable-hdpi/ic_checkmark_circle_blue.pngbin0 -> 2168 bytes
-rw-r--r--res/drawable-hdpi/ic_checkmark_large.pngbin0 -> 1256 bytes
-rw-r--r--res/drawable-hdpi/ic_checkmark_large_light.pngbin0 -> 2906 bytes
-rw-r--r--res/drawable-hdpi/ic_checkmark_small_blue.pngbin0 -> 760 bytes
-rw-r--r--res/drawable-hdpi/ic_checkmark_small_light.pngbin0 -> 626 bytes
-rw-r--r--res/drawable-hdpi/ic_color_lens.pngbin0 -> 1142 bytes
-rw-r--r--res/drawable-hdpi/ic_content_copy_dark.pngbin0 -> 817 bytes
-rw-r--r--res/drawable-hdpi/ic_crying_hero.pngbin0 -> 13612 bytes
-rw-r--r--res/drawable-hdpi/ic_delete_small_dark.pngbin0 -> 763 bytes
-rw-r--r--res/drawable-hdpi/ic_delete_small_light.pngbin0 -> 751 bytes
-rw-r--r--res/drawable-hdpi/ic_dnd_on_dark.pngbin0 -> 1535 bytes
-rw-r--r--res/drawable-hdpi/ic_dnd_on_light.pngbin0 -> 1251 bytes
-rw-r--r--res/drawable-hdpi/ic_failed_light.pngbin0 -> 501 bytes
-rw-r--r--res/drawable-hdpi/ic_failed_status_red.pngbin0 -> 1814 bytes
-rw-r--r--res/drawable-hdpi/ic_file_download_dark.pngbin0 -> 799 bytes
-rw-r--r--res/drawable-hdpi/ic_file_download_light.pngbin0 -> 755 bytes
-rw-r--r--res/drawable-hdpi/ic_forward_dark.pngbin0 -> 714 bytes
-rw-r--r--res/drawable-hdpi/ic_history_light.pngbin0 -> 1344 bytes
-rw-r--r--res/drawable-hdpi/ic_image_light.pngbin0 -> 863 bytes
-rw-r--r--res/drawable-hdpi/ic_ime_dark.pngbin0 -> 1000 bytes
-rw-r--r--res/drawable-hdpi/ic_ime_light.pngbin0 -> 893 bytes
-rw-r--r--res/drawable-hdpi/ic_info_dark.pngbin0 -> 1212 bytes
-rw-r--r--res/drawable-hdpi/ic_keyboard_arrow_left_light.pngbin0 -> 637 bytes
-rw-r--r--res/drawable-hdpi/ic_keyboard_arrow_right_light.pngbin0 -> 628 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher.pngbin0 -> 6408 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_audio_mic.pngbin0 -> 1505 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_camera_large_light.pngbin0 -> 3892 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_camera_small_light.pngbin0 -> 2066 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_capture_stop_large_light.pngbin0 -> 8312 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_full_screen_light.pngbin0 -> 2031 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_video_large_light.pngbin0 -> 2087 bytes
-rw-r--r--res/drawable-hdpi/ic_mp_video_small_light.pngbin0 -> 1286 bytes
-rw-r--r--res/drawable-hdpi/ic_notifications_off_dark.pngbin0 -> 1264 bytes
-rw-r--r--res/drawable-hdpi/ic_notifications_off_light.pngbin0 -> 1092 bytes
-rw-r--r--res/drawable-hdpi/ic_notifications_off_small_light.pngbin0 -> 839 bytes
-rw-r--r--res/drawable-hdpi/ic_notifications_on_dark.pngbin0 -> 1291 bytes
-rw-r--r--res/drawable-hdpi/ic_notifications_on_light.pngbin0 -> 1192 bytes
-rw-r--r--res/drawable-hdpi/ic_numeric_dialpad.pngbin0 -> 626 bytes
-rw-r--r--res/drawable-hdpi/ic_oobe_conv_list.pngbin0 -> 4940 bytes
-rw-r--r--res/drawable-hdpi/ic_oobe_freq_list.pngbin0 -> 4275 bytes
-rw-r--r--res/drawable-hdpi/ic_open_in_new.pngbin0 -> 995 bytes
-rw-r--r--res/drawable-hdpi/ic_people_add_light.pngbin0 -> 1117 bytes
-rw-r--r--res/drawable-hdpi/ic_person_add_blue.pngbin0 -> 1149 bytes
-rw-r--r--res/drawable-hdpi/ic_person_add_dark.pngbin0 -> 1153 bytes
-rw-r--r--res/drawable-hdpi/ic_person_add_light.pngbin0 -> 1084 bytes
-rw-r--r--res/drawable-hdpi/ic_person_light.pngbin0 -> 906 bytes
-rw-r--r--res/drawable-hdpi/ic_person_light_large.pngbin0 -> 1148 bytes
-rw-r--r--res/drawable-hdpi/ic_phone_small_light.pngbin0 -> 1005 bytes
-rw-r--r--res/drawable-hdpi/ic_photo_library_light.pngbin0 -> 994 bytes
-rw-r--r--res/drawable-hdpi/ic_preview_pause.pngbin0 -> 1631 bytes
-rw-r--r--res/drawable-hdpi/ic_preview_play.pngbin0 -> 3494 bytes
-rw-r--r--res/drawable-hdpi/ic_remove_light.pngbin0 -> 2778 bytes
-rw-r--r--res/drawable-hdpi/ic_remove_small_light.pngbin0 -> 712 bytes
-rw-r--r--res/drawable-hdpi/ic_save_dark.pngbin0 -> 947 bytes
-rw-r--r--res/drawable-hdpi/ic_save_light.pngbin0 -> 913 bytes
-rw-r--r--res/drawable-hdpi/ic_search_light.pngbin0 -> 1092 bytes
-rw-r--r--res/drawable-hdpi/ic_send_dark.pngbin0 -> 1061 bytes
-rw-r--r--res/drawable-hdpi/ic_send_light.pngbin0 -> 1054 bytes
-rw-r--r--res/drawable-hdpi/ic_share_dark.pngbin0 -> 1274 bytes
-rw-r--r--res/drawable-hdpi/ic_share_light.pngbin0 -> 1108 bytes
-rw-r--r--res/drawable-hdpi/ic_sim_card_send.pngbin0 -> 488 bytes
-rw-r--r--res/drawable-hdpi/ic_sms_delivery_ok.pngbin0 -> 545 bytes
-rw-r--r--res/drawable-hdpi/ic_sms_failed_light.pngbin0 -> 799 bytes
-rw-r--r--res/drawable-hdpi/ic_sms_light.pngbin0 -> 933 bytes
-rw-r--r--res/drawable-hdpi/ic_sms_multi_light.pngbin0 -> 926 bytes
-rw-r--r--res/drawable-hdpi/ic_tooltip_arrow.pngbin0 -> 703 bytes
-rw-r--r--res/drawable-hdpi/ic_video_play_light.pngbin0 -> 3968 bytes
-rw-r--r--res/drawable-hdpi/ic_wear_reply.pngbin0 -> 1768 bytes
-rw-r--r--res/drawable-hdpi/ic_widget_avatar_shadow.pngbin0 -> 1642 bytes
-rw-r--r--res/drawable-hdpi/ic_widget_list.pngbin0 -> 733 bytes
-rw-r--r--res/drawable-hdpi/msg_bubble_error.9.pngbin0 -> 529 bytes
-rw-r--r--res/drawable-hdpi/msg_bubble_incoming.9.pngbin0 -> 469 bytes
-rw-r--r--res/drawable-hdpi/msg_bubble_input.9.pngbin0 -> 394 bytes
-rw-r--r--res/drawable-hdpi/msg_bubble_outgoing.9.pngbin0 -> 472 bytes
-rw-r--r--res/drawable-hdpi/permissions.pngbin0 -> 22100 bytes
-rw-r--r--res/drawable-hdpi/swipe_shadow.9.pngbin0 -> 194 bytes
-rw-r--r--res/drawable-hdpi/swipe_shadow_drag.9.pngbin0 -> 168 bytes
-rw-r--r--res/drawable-hdpi/sym_keyboard_delete_holo.pngbin0 -> 1147 bytes
-rw-r--r--res/drawable-hdpi/tab_btn_bg_normal.9.pngbin0 -> 164 bytes
-rw-r--r--res/drawable-hdpi/tab_btn_bg_pressed.9.pngbin0 -> 164 bytes
-rw-r--r--res/drawable-hdpi/tab_selected.9.pngbin0 -> 145 bytes
-rw-r--r--res/drawable-hdpi/tab_selected_focused_holo.9.pngbin0 -> 151 bytes
-rw-r--r--res/drawable-hdpi/tab_selected_pressed_focused_holo.9.pngbin0 -> 447 bytes
-rw-r--r--res/drawable-hdpi/tab_selected_pressed_holo.9.pngbin0 -> 149 bytes
-rw-r--r--res/drawable-hdpi/tab_unselected.9.pngbin0 -> 150 bytes
-rw-r--r--res/drawable-hdpi/tab_unselected_focused_holo.9.pngbin0 -> 148 bytes
-rw-r--r--res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.pngbin0 -> 477 bytes
-rw-r--r--res/drawable-hdpi/tab_unselected_pressed_holo.9.pngbin0 -> 145 bytes
-rw-r--r--res/drawable-hdpi/widget_hr.9.pngbin0 -> 240 bytes
-rw-r--r--res/drawable-hdpi/widget_msg_bubble_incoming.9.pngbin0 -> 1697 bytes
-rw-r--r--res/drawable-hdpi/widget_msg_bubble_outgoing.9.pngbin0 -> 1522 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.pngbin0 -> 628 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.pngbin0 -> 637 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/ic_send_light.pngbin0 -> 1093 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.pngbin0 -> 472 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/msg_bubble_input.9.pngbin0 -> 403 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.pngbin0 -> 469 bytes
-rw-r--r--res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.pngbin0 -> 691 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.pngbin0 -> 539 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.pngbin0 -> 565 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/ic_send_light.pngbin0 -> 761 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.pngbin0 -> 344 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/msg_bubble_input.9.pngbin0 -> 272 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.pngbin0 -> 339 bytes
-rw-r--r--res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.pngbin0 -> 583 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.pngbin0 -> 787 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.pngbin0 -> 782 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/ic_send_light.pngbin0 -> 1262 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.pngbin0 -> 598 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/msg_bubble_input.9.pngbin0 -> 535 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.pngbin0 -> 597 bytes
-rw-r--r--res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.pngbin0 -> 1040 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.pngbin0 -> 1467 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.pngbin0 -> 1480 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/ic_send_light.pngbin0 -> 1833 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.pngbin0 -> 1593 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.pngbin0 -> 1553 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.pngbin0 -> 1578 bytes
-rw-r--r--res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.pngbin0 -> 1730 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.pngbin0 -> 1434 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.pngbin0 -> 1438 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/ic_send_light.pngbin0 -> 1990 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.pngbin0 -> 1751 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.pngbin0 -> 1769 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.pngbin0 -> 1700 bytes
-rw-r--r--res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.pngbin0 -> 2347 bytes
-rw-r--r--res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.pngbin0 -> 336 bytes
-rw-r--r--res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.pngbin0 -> 379 bytes
-rw-r--r--res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.pngbin0 -> 331 bytes
-rw-r--r--res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.pngbin0 -> 371 bytes
-rw-r--r--res/drawable-mdpi/cab_bg.9.pngbin0 -> 145 bytes
-rw-r--r--res/drawable-mdpi/contact_popup_background.9.pngbin0 -> 209 bytes
-rw-r--r--res/drawable-mdpi/fab_new_message_static_shadow.pngbin0 -> 1463 bytes
-rw-r--r--res/drawable-mdpi/ic_add_gray.pngbin0 -> 559 bytes
-rw-r--r--res/drawable-mdpi/ic_add_white.pngbin0 -> 305 bytes
-rw-r--r--res/drawable-mdpi/ic_archive_small_dark.pngbin0 -> 803 bytes
-rw-r--r--res/drawable-mdpi/ic_archive_small_light.pngbin0 -> 802 bytes
-rw-r--r--res/drawable-mdpi/ic_archive_undo_small_dark.pngbin0 -> 799 bytes
-rw-r--r--res/drawable-mdpi/ic_archive_undo_small_light.pngbin0 -> 788 bytes
-rw-r--r--res/drawable-mdpi/ic_arrow_back_dark.pngbin0 -> 688 bytes
-rw-r--r--res/drawable-mdpi/ic_arrow_back_light.pngbin0 -> 593 bytes
-rw-r--r--res/drawable-mdpi/ic_attachment_dark.pngbin0 -> 723 bytes
-rw-r--r--res/drawable-mdpi/ic_audio_light.pngbin0 -> 725 bytes
-rw-r--r--res/drawable-mdpi/ic_audio_pause.pngbin0 -> 926 bytes
-rw-r--r--res/drawable-mdpi/ic_audio_play.pngbin0 -> 1045 bytes
-rw-r--r--res/drawable-mdpi/ic_camera_front_light.pngbin0 -> 843 bytes
-rw-r--r--res/drawable-mdpi/ic_camera_light.pngbin0 -> 844 bytes
-rw-r--r--res/drawable-mdpi/ic_camera_rear_light.pngbin0 -> 726 bytes
-rw-r--r--res/drawable-mdpi/ic_cancel_small_dark.pngbin0 -> 578 bytes
-rw-r--r--res/drawable-mdpi/ic_cancel_small_light.pngbin0 -> 1129 bytes
-rw-r--r--res/drawable-mdpi/ic_checkbox_blank_light.pngbin0 -> 415 bytes
-rw-r--r--res/drawable-mdpi/ic_checkbox_light.pngbin0 -> 698 bytes
-rw-r--r--res/drawable-mdpi/ic_checkbox_outline_light.pngbin0 -> 776 bytes
-rw-r--r--res/drawable-mdpi/ic_checkmark_circle_blue.pngbin0 -> 1544 bytes
-rw-r--r--res/drawable-mdpi/ic_checkmark_large.pngbin0 -> 895 bytes
-rw-r--r--res/drawable-mdpi/ic_checkmark_large_light.pngbin0 -> 2010 bytes
-rw-r--r--res/drawable-mdpi/ic_checkmark_small_blue.pngbin0 -> 641 bytes
-rw-r--r--res/drawable-mdpi/ic_checkmark_small_light.pngbin0 -> 584 bytes
-rw-r--r--res/drawable-mdpi/ic_color_lens.pngbin0 -> 819 bytes
-rw-r--r--res/drawable-mdpi/ic_content_copy_dark.pngbin0 -> 714 bytes
-rw-r--r--res/drawable-mdpi/ic_crying_hero.pngbin0 -> 7630 bytes
-rw-r--r--res/drawable-mdpi/ic_delete_small_dark.pngbin0 -> 679 bytes
-rw-r--r--res/drawable-mdpi/ic_delete_small_light.pngbin0 -> 644 bytes
-rw-r--r--res/drawable-mdpi/ic_dnd_on_dark.pngbin0 -> 989 bytes
-rw-r--r--res/drawable-mdpi/ic_dnd_on_light.pngbin0 -> 876 bytes
-rw-r--r--res/drawable-mdpi/ic_failed_light.pngbin0 -> 497 bytes
-rw-r--r--res/drawable-mdpi/ic_failed_status_red.pngbin0 -> 1246 bytes
-rw-r--r--res/drawable-mdpi/ic_file_download_dark.pngbin0 -> 694 bytes
-rw-r--r--res/drawable-mdpi/ic_file_download_light.pngbin0 -> 655 bytes
-rw-r--r--res/drawable-mdpi/ic_forward_dark.pngbin0 -> 644 bytes
-rw-r--r--res/drawable-mdpi/ic_history_light.pngbin0 -> 904 bytes
-rw-r--r--res/drawable-mdpi/ic_image_light.pngbin0 -> 697 bytes
-rw-r--r--res/drawable-mdpi/ic_ime_dark.pngbin0 -> 745 bytes
-rw-r--r--res/drawable-mdpi/ic_ime_light.pngbin0 -> 701 bytes
-rw-r--r--res/drawable-mdpi/ic_info_dark.pngbin0 -> 837 bytes
-rw-r--r--res/drawable-mdpi/ic_keyboard_arrow_left_light.pngbin0 -> 565 bytes
-rw-r--r--res/drawable-mdpi/ic_keyboard_arrow_right_light.pngbin0 -> 539 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher.pngbin0 -> 3442 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_audio_mic.pngbin0 -> 1094 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_camera_large_light.pngbin0 -> 2275 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_camera_small_light.pngbin0 -> 1305 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_capture_stop_large_light.pngbin0 -> 5095 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_full_screen_light.pngbin0 -> 1280 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_video_large_light.pngbin0 -> 1451 bytes
-rw-r--r--res/drawable-mdpi/ic_mp_video_small_light.pngbin0 -> 925 bytes
-rw-r--r--res/drawable-mdpi/ic_notifications_off_dark.pngbin0 -> 914 bytes
-rw-r--r--res/drawable-mdpi/ic_notifications_off_light.pngbin0 -> 820 bytes
-rw-r--r--res/drawable-mdpi/ic_notifications_off_small_light.pngbin0 -> 613 bytes
-rw-r--r--res/drawable-mdpi/ic_notifications_on_dark.pngbin0 -> 946 bytes
-rw-r--r--res/drawable-mdpi/ic_notifications_on_light.pngbin0 -> 880 bytes
-rw-r--r--res/drawable-mdpi/ic_numeric_dialpad.pngbin0 -> 518 bytes
-rw-r--r--res/drawable-mdpi/ic_oobe_conv_list.pngbin0 -> 3476 bytes
-rw-r--r--res/drawable-mdpi/ic_oobe_freq_list.pngbin0 -> 3153 bytes
-rw-r--r--res/drawable-mdpi/ic_open_in_new.pngbin0 -> 831 bytes
-rw-r--r--res/drawable-mdpi/ic_people_add_light.pngbin0 -> 803 bytes
-rw-r--r--res/drawable-mdpi/ic_person_add_blue.pngbin0 -> 845 bytes
-rw-r--r--res/drawable-mdpi/ic_person_add_dark.pngbin0 -> 819 bytes
-rw-r--r--res/drawable-mdpi/ic_person_add_light.pngbin0 -> 801 bytes
-rw-r--r--res/drawable-mdpi/ic_person_light.pngbin0 -> 749 bytes
-rw-r--r--res/drawable-mdpi/ic_person_light_large.pngbin0 -> 894 bytes
-rw-r--r--res/drawable-mdpi/ic_phone_small_light.pngbin0 -> 781 bytes
-rw-r--r--res/drawable-mdpi/ic_photo_library_light.pngbin0 -> 772 bytes
-rw-r--r--res/drawable-mdpi/ic_preview_pause.pngbin0 -> 1226 bytes
-rw-r--r--res/drawable-mdpi/ic_preview_play.pngbin0 -> 2401 bytes
-rw-r--r--res/drawable-mdpi/ic_remove_light.pngbin0 -> 1840 bytes
-rw-r--r--res/drawable-mdpi/ic_remove_small_light.pngbin0 -> 615 bytes
-rw-r--r--res/drawable-mdpi/ic_save_dark.pngbin0 -> 785 bytes
-rw-r--r--res/drawable-mdpi/ic_save_light.pngbin0 -> 781 bytes
-rw-r--r--res/drawable-mdpi/ic_search_light.pngbin0 -> 774 bytes
-rw-r--r--res/drawable-mdpi/ic_send_dark.pngbin0 -> 791 bytes
-rw-r--r--res/drawable-mdpi/ic_send_light.pngbin0 -> 789 bytes
-rw-r--r--res/drawable-mdpi/ic_share_dark.pngbin0 -> 892 bytes
-rw-r--r--res/drawable-mdpi/ic_share_light.pngbin0 -> 832 bytes
-rw-r--r--res/drawable-mdpi/ic_sim_card_send.pngbin0 -> 466 bytes
-rw-r--r--res/drawable-mdpi/ic_sms_delivery_ok.pngbin0 -> 486 bytes
-rw-r--r--res/drawable-mdpi/ic_sms_failed_light.pngbin0 -> 655 bytes
-rw-r--r--res/drawable-mdpi/ic_sms_light.pngbin0 -> 744 bytes
-rw-r--r--res/drawable-mdpi/ic_sms_multi_light.pngbin0 -> 758 bytes
-rw-r--r--res/drawable-mdpi/ic_tooltip_arrow.pngbin0 -> 537 bytes
-rw-r--r--res/drawable-mdpi/ic_video_play_light.pngbin0 -> 2485 bytes
-rw-r--r--res/drawable-mdpi/ic_wear_reply.pngbin0 -> 1227 bytes
-rw-r--r--res/drawable-mdpi/ic_widget_avatar_shadow.pngbin0 -> 1207 bytes
-rw-r--r--res/drawable-mdpi/ic_widget_list.pngbin0 -> 773 bytes
-rw-r--r--res/drawable-mdpi/msg_bubble_error.9.pngbin0 -> 386 bytes
-rw-r--r--res/drawable-mdpi/msg_bubble_incoming.9.pngbin0 -> 339 bytes
-rw-r--r--res/drawable-mdpi/msg_bubble_input.9.pngbin0 -> 280 bytes
-rw-r--r--res/drawable-mdpi/msg_bubble_outgoing.9.pngbin0 -> 344 bytes
-rw-r--r--res/drawable-mdpi/permissions.pngbin0 -> 12383 bytes
-rw-r--r--res/drawable-mdpi/swipe_shadow.9.pngbin0 -> 166 bytes
-rw-r--r--res/drawable-mdpi/swipe_shadow_drag.9.pngbin0 -> 153 bytes
-rw-r--r--res/drawable-mdpi/sym_keyboard_delete_holo.pngbin0 -> 832 bytes
-rw-r--r--res/drawable-mdpi/tab_btn_bg_normal.9.pngbin0 -> 156 bytes
-rw-r--r--res/drawable-mdpi/tab_btn_bg_pressed.9.pngbin0 -> 165 bytes
-rw-r--r--res/drawable-mdpi/tab_selected.9.pngbin0 -> 140 bytes
-rw-r--r--res/drawable-mdpi/tab_selected_focused_holo.9.pngbin0 -> 150 bytes
-rw-r--r--res/drawable-mdpi/tab_selected_pressed_focused_holo.9.pngbin0 -> 369 bytes
-rw-r--r--res/drawable-mdpi/tab_selected_pressed_holo.9.pngbin0 -> 149 bytes
-rw-r--r--res/drawable-mdpi/tab_unselected.9.pngbin0 -> 138 bytes
-rw-r--r--res/drawable-mdpi/tab_unselected_focused_holo.9.pngbin0 -> 149 bytes
-rw-r--r--res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.pngbin0 -> 385 bytes
-rw-r--r--res/drawable-mdpi/tab_unselected_pressed_holo.9.pngbin0 -> 150 bytes
-rw-r--r--res/drawable-mdpi/widget_hr.9.pngbin0 -> 196 bytes
-rw-r--r--res/drawable-mdpi/widget_msg_bubble_incoming.9.pngbin0 -> 491 bytes
-rw-r--r--res/drawable-mdpi/widget_msg_bubble_outgoing.9.pngbin0 -> 514 bytes
-rw-r--r--res/drawable-nodpi/bg_sms.jpgbin0 -> 36734 bytes
-rw-r--r--res/drawable-nodpi/ic_person_wear.pngbin0 -> 4534 bytes
-rw-r--r--res/drawable-nodpi/messenger_widget_conversation_preview.pngbin0 -> 53652 bytes
-rw-r--r--res/drawable-nodpi/messenger_widget_preview.pngbin0 -> 103381 bytes
-rw-r--r--res/drawable-v21/contact_picker_tab_background_selector.xml18
-rw-r--r--res/drawable-v21/fab_new_message_bg.xml26
-rw-r--r--res/drawable-v21/gallery_image_background_selector.xml21
-rw-r--r--res/drawable-v21/mediapicker_tab_button_background.xml18
-rw-r--r--res/drawable-v21/transparent_button_background.xml18
-rw-r--r--res/drawable-v21/widget_item_background.xml23
-rw-r--r--res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.pngbin0 -> 574 bytes
-rw-r--r--res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.pngbin0 -> 700 bytes
-rw-r--r--res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.pngbin0 -> 587 bytes
-rw-r--r--res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.pngbin0 -> 664 bytes
-rw-r--r--res/drawable-xhdpi/cab_bg.9.pngbin0 -> 145 bytes
-rw-r--r--res/drawable-xhdpi/contact_popup_background.9.pngbin0 -> 169 bytes
-rw-r--r--res/drawable-xhdpi/fab_new_message_static_shadow.pngbin0 -> 2784 bytes
-rw-r--r--res/drawable-xhdpi/ic_add_gray.pngbin0 -> 712 bytes
-rw-r--r--res/drawable-xhdpi/ic_add_white.pngbin0 -> 335 bytes
-rw-r--r--res/drawable-xhdpi/ic_archive_small_dark.pngbin0 -> 1145 bytes
-rw-r--r--res/drawable-xhdpi/ic_archive_small_light.pngbin0 -> 1117 bytes
-rw-r--r--res/drawable-xhdpi/ic_archive_undo_small_dark.pngbin0 -> 1128 bytes
-rw-r--r--res/drawable-xhdpi/ic_archive_undo_small_light.pngbin0 -> 1140 bytes
-rw-r--r--res/drawable-xhdpi/ic_arrow_back_dark.pngbin0 -> 1013 bytes
-rw-r--r--res/drawable-xhdpi/ic_arrow_back_light.pngbin0 -> 840 bytes
-rw-r--r--res/drawable-xhdpi/ic_attachment_dark.pngbin0 -> 1206 bytes
-rw-r--r--res/drawable-xhdpi/ic_audio_light.pngbin0 -> 1274 bytes
-rw-r--r--res/drawable-xhdpi/ic_audio_pause.pngbin0 -> 1749 bytes
-rw-r--r--res/drawable-xhdpi/ic_audio_play.pngbin0 -> 2024 bytes
-rw-r--r--res/drawable-xhdpi/ic_camera_front_light.pngbin0 -> 1357 bytes
-rw-r--r--res/drawable-xhdpi/ic_camera_light.pngbin0 -> 1481 bytes
-rw-r--r--res/drawable-xhdpi/ic_camera_rear_light.pngbin0 -> 1091 bytes
-rw-r--r--res/drawable-xhdpi/ic_cancel_small_dark.pngbin0 -> 839 bytes
-rw-r--r--res/drawable-xhdpi/ic_cancel_small_light.pngbin0 -> 1957 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkbox_blank_light.pngbin0 -> 473 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkbox_light.pngbin0 -> 1108 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkbox_outline_light.pngbin0 -> 1199 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkmark_circle_blue.pngbin0 -> 2966 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkmark_large.pngbin0 -> 1776 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkmark_large_light.pngbin0 -> 3925 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkmark_small_blue.pngbin0 -> 971 bytes
-rw-r--r--res/drawable-xhdpi/ic_checkmark_small_light.pngbin0 -> 798 bytes
-rw-r--r--res/drawable-xhdpi/ic_color_lens.pngbin0 -> 1360 bytes
-rw-r--r--res/drawable-xhdpi/ic_content_copy_dark.pngbin0 -> 971 bytes
-rw-r--r--res/drawable-xhdpi/ic_crying_hero.pngbin0 -> 18895 bytes
-rw-r--r--res/drawable-xhdpi/ic_delete_small_dark.pngbin0 -> 836 bytes
-rw-r--r--res/drawable-xhdpi/ic_delete_small_light.pngbin0 -> 791 bytes
-rw-r--r--res/drawable-xhdpi/ic_dnd_on_dark.pngbin0 -> 2137 bytes
-rw-r--r--res/drawable-xhdpi/ic_dnd_on_light.pngbin0 -> 1807 bytes
-rw-r--r--res/drawable-xhdpi/ic_failed_light.pngbin0 -> 629 bytes
-rw-r--r--res/drawable-xhdpi/ic_failed_status_red.pngbin0 -> 2303 bytes
-rw-r--r--res/drawable-xhdpi/ic_file_download_dark.pngbin0 -> 893 bytes
-rw-r--r--res/drawable-xhdpi/ic_file_download_light.pngbin0 -> 813 bytes
-rw-r--r--res/drawable-xhdpi/ic_forward_dark.pngbin0 -> 860 bytes
-rw-r--r--res/drawable-xhdpi/ic_history_light.pngbin0 -> 1783 bytes
-rw-r--r--res/drawable-xhdpi/ic_image_light.pngbin0 -> 1131 bytes
-rw-r--r--res/drawable-xhdpi/ic_ime_dark.pngbin0 -> 1148 bytes
-rw-r--r--res/drawable-xhdpi/ic_ime_light.pngbin0 -> 953 bytes
-rw-r--r--res/drawable-xhdpi/ic_info_dark.pngbin0 -> 1580 bytes
-rw-r--r--res/drawable-xhdpi/ic_keyboard_arrow_left_light.pngbin0 -> 782 bytes
-rw-r--r--res/drawable-xhdpi/ic_keyboard_arrow_right_light.pngbin0 -> 787 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher.pngbin0 -> 9276 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_audio_mic.pngbin0 -> 2005 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_camera_large_light.pngbin0 -> 5391 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_camera_small_light.pngbin0 -> 2690 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_capture_stop_large_light.pngbin0 -> 11858 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_full_screen_light.pngbin0 -> 2514 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_video_large_light.pngbin0 -> 2739 bytes
-rw-r--r--res/drawable-xhdpi/ic_mp_video_small_light.pngbin0 -> 1667 bytes
-rw-r--r--res/drawable-xhdpi/ic_notifications_off_dark.pngbin0 -> 1671 bytes
-rw-r--r--res/drawable-xhdpi/ic_notifications_off_light.pngbin0 -> 1447 bytes
-rw-r--r--res/drawable-xhdpi/ic_notifications_off_small_light.pngbin0 -> 1060 bytes
-rw-r--r--res/drawable-xhdpi/ic_notifications_on_dark.pngbin0 -> 1660 bytes
-rw-r--r--res/drawable-xhdpi/ic_notifications_on_light.pngbin0 -> 1584 bytes
-rw-r--r--res/drawable-xhdpi/ic_numeric_dialpad.pngbin0 -> 730 bytes
-rw-r--r--res/drawable-xhdpi/ic_oobe_conv_list.pngbin0 -> 6688 bytes
-rw-r--r--res/drawable-xhdpi/ic_oobe_freq_list.pngbin0 -> 5773 bytes
-rw-r--r--res/drawable-xhdpi/ic_open_in_new.pngbin0 -> 1165 bytes
-rw-r--r--res/drawable-xhdpi/ic_people_add_light.pngbin0 -> 1437 bytes
-rw-r--r--res/drawable-xhdpi/ic_person_add_blue.pngbin0 -> 1517 bytes
-rw-r--r--res/drawable-xhdpi/ic_person_add_dark.pngbin0 -> 1409 bytes
-rw-r--r--res/drawable-xhdpi/ic_person_add_light.pngbin0 -> 1369 bytes
-rw-r--r--res/drawable-xhdpi/ic_person_light.pngbin0 -> 1240 bytes
-rw-r--r--res/drawable-xhdpi/ic_person_light_large.pngbin0 -> 1581 bytes
-rw-r--r--res/drawable-xhdpi/ic_phone_small_light.pngbin0 -> 1292 bytes
-rw-r--r--res/drawable-xhdpi/ic_photo_library_light.pngbin0 -> 1170 bytes
-rw-r--r--res/drawable-xhdpi/ic_preview_pause.pngbin0 -> 2047 bytes
-rw-r--r--res/drawable-xhdpi/ic_preview_play.pngbin0 -> 4481 bytes
-rw-r--r--res/drawable-xhdpi/ic_remove_light.pngbin0 -> 4104 bytes
-rw-r--r--res/drawable-xhdpi/ic_remove_small_light.pngbin0 -> 936 bytes
-rw-r--r--res/drawable-xhdpi/ic_save_dark.pngbin0 -> 1258 bytes
-rw-r--r--res/drawable-xhdpi/ic_save_light.pngbin0 -> 1178 bytes
-rw-r--r--res/drawable-xhdpi/ic_search_light.pngbin0 -> 1449 bytes
-rw-r--r--res/drawable-xhdpi/ic_send_dark.pngbin0 -> 1438 bytes
-rw-r--r--res/drawable-xhdpi/ic_send_light.pngbin0 -> 1300 bytes
-rw-r--r--res/drawable-xhdpi/ic_share_dark.pngbin0 -> 1675 bytes
-rw-r--r--res/drawable-xhdpi/ic_share_light.pngbin0 -> 1470 bytes
-rw-r--r--res/drawable-xhdpi/ic_sim_card_send.pngbin0 -> 527 bytes
-rw-r--r--res/drawable-xhdpi/ic_sms_delivery_ok.pngbin0 -> 699 bytes
-rw-r--r--res/drawable-xhdpi/ic_sms_failed_light.pngbin0 -> 903 bytes
-rw-r--r--res/drawable-xhdpi/ic_sms_light.pngbin0 -> 951 bytes
-rw-r--r--res/drawable-xhdpi/ic_sms_multi_light.pngbin0 -> 1019 bytes
-rw-r--r--res/drawable-xhdpi/ic_tooltip_arrow.pngbin0 -> 849 bytes
-rw-r--r--res/drawable-xhdpi/ic_video_play_light.pngbin0 -> 5404 bytes
-rw-r--r--res/drawable-xhdpi/ic_wear_reply.pngbin0 -> 2475 bytes
-rw-r--r--res/drawable-xhdpi/ic_widget_avatar_shadow.pngbin0 -> 2159 bytes
-rw-r--r--res/drawable-xhdpi/ic_widget_list.pngbin0 -> 815 bytes
-rw-r--r--res/drawable-xhdpi/msg_bubble_error.9.pngbin0 -> 743 bytes
-rw-r--r--res/drawable-xhdpi/msg_bubble_incoming.9.pngbin0 -> 597 bytes
-rw-r--r--res/drawable-xhdpi/msg_bubble_input.9.pngbin0 -> 519 bytes
-rw-r--r--res/drawable-xhdpi/msg_bubble_outgoing.9.pngbin0 -> 598 bytes
-rw-r--r--res/drawable-xhdpi/permissions.pngbin0 -> 33848 bytes
-rw-r--r--res/drawable-xhdpi/swipe_shadow.9.pngbin0 -> 203 bytes
-rw-r--r--res/drawable-xhdpi/swipe_shadow_drag.9.pngbin0 -> 187 bytes
-rw-r--r--res/drawable-xhdpi/sym_keyboard_delete_holo.pngbin0 -> 1591 bytes
-rw-r--r--res/drawable-xhdpi/tab_btn_bg_normal.9.pngbin0 -> 166 bytes
-rw-r--r--res/drawable-xhdpi/tab_btn_bg_pressed.9.pngbin0 -> 166 bytes
-rw-r--r--res/drawable-xhdpi/tab_selected.9.pngbin0 -> 148 bytes
-rw-r--r--res/drawable-xhdpi/tab_selected_focused_holo.9.pngbin0 -> 152 bytes
-rw-r--r--res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.pngbin0 -> 540 bytes
-rw-r--r--res/drawable-xhdpi/tab_selected_pressed_holo.9.pngbin0 -> 149 bytes
-rw-r--r--res/drawable-xhdpi/tab_unselected.9.pngbin0 -> 155 bytes
-rw-r--r--res/drawable-xhdpi/tab_unselected_focused_holo.9.pngbin0 -> 149 bytes
-rw-r--r--res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.pngbin0 -> 587 bytes
-rw-r--r--res/drawable-xhdpi/tab_unselected_pressed_holo.9.pngbin0 -> 148 bytes
-rw-r--r--res/drawable-xhdpi/widget_hr.9.pngbin0 -> 293 bytes
-rw-r--r--res/drawable-xhdpi/widget_msg_bubble_incoming.9.pngbin0 -> 998 bytes
-rw-r--r--res/drawable-xhdpi/widget_msg_bubble_outgoing.9.pngbin0 -> 954 bytes
-rw-r--r--res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.pngbin0 -> 1718 bytes
-rw-r--r--res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.pngbin0 -> 1863 bytes
-rw-r--r--res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.pngbin0 -> 1786 bytes
-rw-r--r--res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.pngbin0 -> 1850 bytes
-rw-r--r--res/drawable-xxhdpi/cab_bg.9.pngbin0 -> 1043 bytes
-rw-r--r--res/drawable-xxhdpi/contact_popup_background.9.pngbin0 -> 207 bytes
-rw-r--r--res/drawable-xxhdpi/fab_new_message_static_shadow.pngbin0 -> 4902 bytes
-rw-r--r--res/drawable-xxhdpi/ic_add_gray.pngbin0 -> 1227 bytes
-rw-r--r--res/drawable-xxhdpi/ic_add_white.pngbin0 -> 370 bytes
-rw-r--r--res/drawable-xxhdpi/ic_archive_small_dark.pngbin0 -> 1526 bytes
-rw-r--r--res/drawable-xxhdpi/ic_archive_small_light.pngbin0 -> 1514 bytes
-rw-r--r--res/drawable-xxhdpi/ic_archive_undo_small_dark.pngbin0 -> 1484 bytes
-rw-r--r--res/drawable-xxhdpi/ic_archive_undo_small_light.pngbin0 -> 1511 bytes
-rw-r--r--res/drawable-xxhdpi/ic_arrow_back_dark.pngbin0 -> 1483 bytes
-rw-r--r--res/drawable-xxhdpi/ic_arrow_back_light.pngbin0 -> 1463 bytes
-rw-r--r--res/drawable-xxhdpi/ic_attachment_dark.pngbin0 -> 1958 bytes
-rw-r--r--res/drawable-xxhdpi/ic_audio_light.pngbin0 -> 1772 bytes
-rw-r--r--res/drawable-xxhdpi/ic_audio_pause.pngbin0 -> 2595 bytes
-rw-r--r--res/drawable-xxhdpi/ic_audio_play.pngbin0 -> 2723 bytes
-rw-r--r--res/drawable-xxhdpi/ic_camera_front_light.pngbin0 -> 1859 bytes
-rw-r--r--res/drawable-xxhdpi/ic_camera_light.pngbin0 -> 1886 bytes
-rw-r--r--res/drawable-xxhdpi/ic_camera_rear_light.pngbin0 -> 1534 bytes
-rw-r--r--res/drawable-xxhdpi/ic_cancel_small_dark.pngbin0 -> 1540 bytes
-rw-r--r--res/drawable-xxhdpi/ic_cancel_small_light.pngbin0 -> 2407 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkbox_blank_light.pngbin0 -> 1227 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkbox_light.pngbin0 -> 1510 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkbox_outline_light.pngbin0 -> 1576 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkmark_circle_blue.pngbin0 -> 3371 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkmark_large.pngbin0 -> 2943 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkmark_large_light.pngbin0 -> 4795 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkmark_small_blue.pngbin0 -> 1520 bytes
-rw-r--r--res/drawable-xxhdpi/ic_checkmark_small_light.pngbin0 -> 1434 bytes
-rw-r--r--res/drawable-xxhdpi/ic_color_lens.pngbin0 -> 1953 bytes
-rw-r--r--res/drawable-xxhdpi/ic_content_copy_dark.pngbin0 -> 1396 bytes
-rw-r--r--res/drawable-xxhdpi/ic_crying_hero.pngbin0 -> 29146 bytes
-rw-r--r--res/drawable-xxhdpi/ic_delete_small_dark.pngbin0 -> 1300 bytes
-rw-r--r--res/drawable-xxhdpi/ic_delete_small_light.pngbin0 -> 1277 bytes
-rw-r--r--res/drawable-xxhdpi/ic_dnd_on_dark.pngbin0 -> 2620 bytes
-rw-r--r--res/drawable-xxhdpi/ic_dnd_on_light.pngbin0 -> 2469 bytes
-rw-r--r--res/drawable-xxhdpi/ic_failed_light.pngbin0 -> 1134 bytes
-rw-r--r--res/drawable-xxhdpi/ic_failed_status_red.pngbin0 -> 2769 bytes
-rw-r--r--res/drawable-xxhdpi/ic_file_download_dark.pngbin0 -> 1299 bytes
-rw-r--r--res/drawable-xxhdpi/ic_file_download_light.pngbin0 -> 1285 bytes
-rw-r--r--res/drawable-xxhdpi/ic_forward_dark.pngbin0 -> 1341 bytes
-rw-r--r--res/drawable-xxhdpi/ic_history_light.pngbin0 -> 2765 bytes
-rw-r--r--res/drawable-xxhdpi/ic_image_light.pngbin0 -> 1669 bytes
-rw-r--r--res/drawable-xxhdpi/ic_ime_dark.pngbin0 -> 2008 bytes
-rw-r--r--res/drawable-xxhdpi/ic_ime_light.pngbin0 -> 1023 bytes
-rw-r--r--res/drawable-xxhdpi/ic_info_dark.pngbin0 -> 1990 bytes
-rw-r--r--res/drawable-xxhdpi/ic_keyboard_arrow_left_light.pngbin0 -> 1480 bytes
-rw-r--r--res/drawable-xxhdpi/ic_keyboard_arrow_right_light.pngbin0 -> 1467 bytes
-rw-r--r--res/drawable-xxhdpi/ic_launcher.pngbin0 -> 14674 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_audio_mic.pngbin0 -> 2446 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_camera_large_light.pngbin0 -> 6213 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_camera_small_light.pngbin0 -> 3444 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_capture_stop_large_light.pngbin0 -> 11969 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_full_screen_light.pngbin0 -> 2823 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_video_large_light.pngbin0 -> 3596 bytes
-rw-r--r--res/drawable-xxhdpi/ic_mp_video_small_light.pngbin0 -> 2335 bytes
-rw-r--r--res/drawable-xxhdpi/ic_notifications_off_dark.pngbin0 -> 1967 bytes
-rw-r--r--res/drawable-xxhdpi/ic_notifications_off_light.pngbin0 -> 1902 bytes
-rw-r--r--res/drawable-xxhdpi/ic_notifications_off_small_light.pngbin0 -> 1883 bytes
-rw-r--r--res/drawable-xxhdpi/ic_notifications_on_dark.pngbin0 -> 2077 bytes
-rw-r--r--res/drawable-xxhdpi/ic_notifications_on_light.pngbin0 -> 1938 bytes
-rw-r--r--res/drawable-xxhdpi/ic_numeric_dialpad.pngbin0 -> 995 bytes
-rw-r--r--res/drawable-xxhdpi/ic_oobe_conv_list.pngbin0 -> 6148 bytes
-rw-r--r--res/drawable-xxhdpi/ic_oobe_freq_list.pngbin0 -> 5675 bytes
-rw-r--r--res/drawable-xxhdpi/ic_open_in_new.pngbin0 -> 1497 bytes
-rw-r--r--res/drawable-xxhdpi/ic_people_add_light.pngbin0 -> 1745 bytes
-rw-r--r--res/drawable-xxhdpi/ic_person_add_blue.pngbin0 -> 1847 bytes
-rw-r--r--res/drawable-xxhdpi/ic_person_add_dark.pngbin0 -> 1741 bytes
-rw-r--r--res/drawable-xxhdpi/ic_person_add_light.pngbin0 -> 1706 bytes
-rw-r--r--res/drawable-xxhdpi/ic_person_light.pngbin0 -> 1876 bytes
-rw-r--r--res/drawable-xxhdpi/ic_person_light_large.pngbin0 -> 2206 bytes
-rw-r--r--res/drawable-xxhdpi/ic_phone_small_light.pngbin0 -> 1879 bytes
-rw-r--r--res/drawable-xxhdpi/ic_photo_library_light.pngbin0 -> 1521 bytes
-rw-r--r--res/drawable-xxhdpi/ic_preview_pause.pngbin0 -> 2886 bytes
-rw-r--r--res/drawable-xxhdpi/ic_preview_play.pngbin0 -> 6868 bytes
-rw-r--r--res/drawable-xxhdpi/ic_remove_light.pngbin0 -> 3549 bytes
-rw-r--r--res/drawable-xxhdpi/ic_remove_small_light.pngbin0 -> 1608 bytes
-rw-r--r--res/drawable-xxhdpi/ic_save_dark.pngbin0 -> 1580 bytes
-rw-r--r--res/drawable-xxhdpi/ic_save_light.pngbin0 -> 1487 bytes
-rw-r--r--res/drawable-xxhdpi/ic_search_light.pngbin0 -> 2130 bytes
-rw-r--r--res/drawable-xxhdpi/ic_send_dark.pngbin0 -> 1813 bytes
-rw-r--r--res/drawable-xxhdpi/ic_send_light.pngbin0 -> 1658 bytes
-rw-r--r--res/drawable-xxhdpi/ic_share_dark.pngbin0 -> 2448 bytes
-rw-r--r--res/drawable-xxhdpi/ic_share_light.pngbin0 -> 2149 bytes
-rw-r--r--res/drawable-xxhdpi/ic_sim_card_send.pngbin0 -> 701 bytes
-rw-r--r--res/drawable-xxhdpi/ic_sms_delivery_ok.pngbin0 -> 1442 bytes
-rw-r--r--res/drawable-xxhdpi/ic_sms_failed_light.pngbin0 -> 1333 bytes
-rw-r--r--res/drawable-xxhdpi/ic_sms_light.pngbin0 -> 1155 bytes
-rw-r--r--res/drawable-xxhdpi/ic_sms_multi_light.pngbin0 -> 1111 bytes
-rw-r--r--res/drawable-xxhdpi/ic_tooltip_arrow.pngbin0 -> 1230 bytes
-rw-r--r--res/drawable-xxhdpi/ic_video_play_light.pngbin0 -> 5605 bytes
-rw-r--r--res/drawable-xxhdpi/ic_wear_reply.pngbin0 -> 2945 bytes
-rw-r--r--res/drawable-xxhdpi/ic_widget_avatar_shadow.pngbin0 -> 3494 bytes
-rw-r--r--res/drawable-xxhdpi/ic_widget_list.pngbin0 -> 1013 bytes
-rw-r--r--res/drawable-xxhdpi/msg_bubble_error.9.pngbin0 -> 1122 bytes
-rw-r--r--res/drawable-xxhdpi/msg_bubble_incoming.9.pngbin0 -> 1582 bytes
-rw-r--r--res/drawable-xxhdpi/msg_bubble_input.9.pngbin0 -> 1622 bytes
-rw-r--r--res/drawable-xxhdpi/msg_bubble_outgoing.9.pngbin0 -> 1597 bytes
-rw-r--r--res/drawable-xxhdpi/permissions.pngbin0 -> 65773 bytes
-rw-r--r--res/drawable-xxhdpi/swipe_shadow.9.pngbin0 -> 1163 bytes
-rw-r--r--res/drawable-xxhdpi/swipe_shadow_drag.9.pngbin0 -> 196 bytes
-rw-r--r--res/drawable-xxhdpi/sym_keyboard_delete_holo.pngbin0 -> 4957 bytes
-rw-r--r--res/drawable-xxhdpi/tab_btn_bg_normal.9.pngbin0 -> 1062 bytes
-rw-r--r--res/drawable-xxhdpi/tab_btn_bg_pressed.9.pngbin0 -> 1066 bytes
-rw-r--r--res/drawable-xxhdpi/tab_selected.9.pngbin0 -> 1030 bytes
-rw-r--r--res/drawable-xxhdpi/tab_selected_focused_holo.9.pngbin0 -> 229 bytes
-rw-r--r--res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.pngbin0 -> 679 bytes
-rw-r--r--res/drawable-xxhdpi/tab_selected_pressed_holo.9.pngbin0 -> 227 bytes
-rw-r--r--res/drawable-xxhdpi/tab_unselected.9.pngbin0 -> 1048 bytes
-rw-r--r--res/drawable-xxhdpi/tab_unselected_focused_holo.9.pngbin0 -> 227 bytes
-rw-r--r--res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.pngbin0 -> 744 bytes
-rw-r--r--res/drawable-xxhdpi/tab_unselected_pressed_holo.9.pngbin0 -> 222 bytes
-rw-r--r--res/drawable-xxhdpi/widget_hr.9.pngbin0 -> 396 bytes
-rw-r--r--res/drawable-xxhdpi/widget_msg_bubble_incoming.9.pngbin0 -> 1644 bytes
-rw-r--r--res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.pngbin0 -> 1680 bytes
-rw-r--r--res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.pngbin0 -> 1899 bytes
-rw-r--r--res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.pngbin0 -> 2219 bytes
-rw-r--r--res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.pngbin0 -> 1961 bytes
-rw-r--r--res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.pngbin0 -> 2114 bytes
-rw-r--r--res/drawable-xxxhdpi/cab_bg.9.pngbin0 -> 1050 bytes
-rw-r--r--res/drawable-xxxhdpi/contact_popup_background.9.pngbin0 -> 1172 bytes
-rw-r--r--res/drawable-xxxhdpi/fab_new_message_static_shadow.pngbin0 -> 6233 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_add_white.pngbin0 -> 317 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_archive_small_dark.pngbin0 -> 1823 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_archive_small_light.pngbin0 -> 1793 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_archive_undo_small_dark.pngbin0 -> 1721 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_archive_undo_small_light.pngbin0 -> 1741 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_arrow_back_dark.pngbin0 -> 1485 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_arrow_back_light.pngbin0 -> 1457 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_attachment_dark.pngbin0 -> 2282 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_audio_light.pngbin0 -> 2133 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_audio_pause.pngbin0 -> 3376 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_audio_play.pngbin0 -> 3505 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_camera_front_light.pngbin0 -> 1878 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_camera_light.pngbin0 -> 2333 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_camera_rear_light.pngbin0 -> 1700 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_cancel_small_dark.pngbin0 -> 1642 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_cancel_small_light.pngbin0 -> 1604 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkbox_blank_light.pngbin0 -> 1366 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkbox_light.pngbin0 -> 1829 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkbox_outline_light.pngbin0 -> 1695 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkmark_circle_blue.pngbin0 -> 4729 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkmark_large.pngbin0 -> 3744 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkmark_large_light.pngbin0 -> 6285 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkmark_small_blue.pngbin0 -> 1533 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_checkmark_small_light.pngbin0 -> 1538 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_color_lens.pngbin0 -> 2351 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_content_copy_dark.pngbin0 -> 1511 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_crying_hero.pngbin0 -> 30848 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_delete_small_dark.pngbin0 -> 1371 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_delete_small_light.pngbin0 -> 1331 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_dnd_on_dark.pngbin0 -> 3024 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_dnd_on_light.pngbin0 -> 2694 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_failed_light.pngbin0 -> 1204 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_failed_status_red.pngbin0 -> 3682 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_file_download_dark.pngbin0 -> 1416 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_file_download_light.pngbin0 -> 1389 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_forward_dark.pngbin0 -> 1759 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_history_light.pngbin0 -> 2898 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_image_light.pngbin0 -> 1945 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_ime_dark.pngbin0 -> 2235 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_ime_light.pngbin0 -> 1455 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_info_dark.pngbin0 -> 2348 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.pngbin0 -> 1438 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.pngbin0 -> 1434 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_launcher.pngbin0 -> 14674 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_audio_mic.pngbin0 -> 3012 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_camera_large_light.pngbin0 -> 8264 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_camera_small_light.pngbin0 -> 4455 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.pngbin0 -> 18106 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_full_screen_light.pngbin0 -> 3502 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_video_large_light.pngbin0 -> 4610 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_mp_video_small_light.pngbin0 -> 2938 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_notifications_off_dark.pngbin0 -> 1985 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_notifications_off_light.pngbin0 -> 1938 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_notifications_off_small_light.pngbin0 -> 1726 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_notifications_on_dark.pngbin0 -> 2404 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_notifications_on_light.pngbin0 -> 2257 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_numeric_dialpad.pngbin0 -> 1617 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_oobe_conv_list.pngbin0 -> 11559 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_oobe_freq_list.pngbin0 -> 12015 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_open_in_new.pngbin0 -> 1563 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_people_add_light.pngbin0 -> 2028 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_person_add_blue.pngbin0 -> 2140 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_person_add_dark.pngbin0 -> 2004 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_person_add_light.pngbin0 -> 1918 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_person_light.pngbin0 -> 2206 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_person_light_large.pngbin0 -> 2834 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_phone_small_light.pngbin0 -> 2162 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_photo_library_light.pngbin0 -> 553 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_preview_pause.pngbin0 -> 3071 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_preview_play.pngbin0 -> 4767 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_remove_light.pngbin0 -> 4452 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_remove_small_light.pngbin0 -> 1604 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_save_dark.pngbin0 -> 1729 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_save_light.pngbin0 -> 1711 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_search_light.pngbin0 -> 2503 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_send_dark.pngbin0 -> 2155 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_send_light.pngbin0 -> 2057 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_share_dark.pngbin0 -> 2580 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_share_light.pngbin0 -> 2494 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_sim_card_send.pngbin0 -> 1397 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_sms_delivery_ok.pngbin0 -> 1341 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_sms_failed_light.pngbin0 -> 1685 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_sms_light.pngbin0 -> 1435 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_sms_multi_light.pngbin0 -> 1440 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_tooltip_arrow.pngbin0 -> 388 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_video_play_light.pngbin0 -> 7465 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_wear_reply.pngbin0 -> 3749 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_widget_avatar_shadow.pngbin0 -> 4945 bytes
-rw-r--r--res/drawable-xxxhdpi/ic_widget_list.pngbin0 -> 1617 bytes
-rw-r--r--res/drawable-xxxhdpi/msg_bubble_error.9.pngbin0 -> 2061 bytes
-rw-r--r--res/drawable-xxxhdpi/msg_bubble_incoming.9.pngbin0 -> 1731 bytes
-rw-r--r--res/drawable-xxxhdpi/msg_bubble_input.9.pngbin0 -> 1770 bytes
-rw-r--r--res/drawable-xxxhdpi/msg_bubble_outgoing.9.pngbin0 -> 1755 bytes
-rw-r--r--res/drawable-xxxhdpi/permissions.pngbin0 -> 102985 bytes
-rw-r--r--res/drawable-xxxhdpi/swipe_shadow.9.pngbin0 -> 1155 bytes
-rw-r--r--res/drawable-xxxhdpi/swipe_shadow_drag.9.pngbin0 -> 1102 bytes
-rw-r--r--res/drawable-xxxhdpi/sym_keyboard_delete_holo.pngbin0 -> 3386 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_btn_bg_normal.9.pngbin0 -> 1079 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_btn_bg_pressed.9.pngbin0 -> 1082 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_selected.9.pngbin0 -> 1045 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_selected_focused_holo.9.pngbin0 -> 1064 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.pngbin0 -> 1705 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_selected_pressed_holo.9.pngbin0 -> 1057 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_unselected.9.pngbin0 -> 1061 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_unselected_focused_holo.9.pngbin0 -> 1045 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.pngbin0 -> 1790 bytes
-rw-r--r--res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.pngbin0 -> 1045 bytes
-rw-r--r--res/drawable-xxxhdpi/widget_hr.9.pngbin0 -> 1368 bytes
-rw-r--r--res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.pngbin0 -> 2745 bytes
-rw-r--r--res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.pngbin0 -> 2851 bytes
-rw-r--r--res/drawable/attachment_audio_preview_background.xml20
-rw-r--r--res/drawable/attachment_image_placeholder_background.xml20
-rw-r--r--res/drawable/attachment_more_items_background.xml20
-rw-r--r--res/drawable/attachment_vcard_preview_background.xml20
-rw-r--r--res/drawable/audio_progress_bar_background_incoming.xml22
-rw-r--r--res/drawable/audio_progress_bar_background_outgoing.xml20
-rw-r--r--res/drawable/audio_progress_bar_progress.xml20
-rw-r--r--res/drawable/audio_record_control_button_background.xml26
-rw-r--r--res/drawable/chips_dropdown_background.xml22
-rw-r--r--res/drawable/chips_dropdown_text_color.xml22
-rw-r--r--res/drawable/compose_chips_divider_gradient.xml24
-rw-r--r--res/drawable/contact_picker_tab_background_selector.xml21
-rw-r--r--res/drawable/contacts_fastscroll_label_left.xml28
-rw-r--r--res/drawable/contacts_fastscroll_label_right.xml28
-rw-r--r--res/drawable/conversation_compose_divider_gradient.xml24
-rw-r--r--res/drawable/default_image.xml33
-rw-r--r--res/drawable/exit_button_bg.xml20
-rw-r--r--res/drawable/fab_new_message_bg.xml45
-rw-r--r--res/drawable/fastscroll_preview_left.xml24
-rw-r--r--res/drawable/fastscroll_preview_right.xml24
-rw-r--r--res/drawable/fastscroll_thumb.xml22
-rw-r--r--res/drawable/fastscroll_thumb_pressed.xml22
-rw-r--r--res/drawable/fastscroll_track.xml21
-rw-r--r--res/drawable/gallery_checkbox_selector.xml27
-rw-r--r--res/drawable/gallery_document_picker_item_background.xml19
-rw-r--r--res/drawable/gallery_image_background_selector.xml23
-rw-r--r--res/drawable/generic_video_icon.xml11
-rw-r--r--res/drawable/mediapicker_tab_button_background.xml26
-rw-r--r--res/drawable/message_bubble_incoming_no_arrow.xml20
-rw-r--r--res/drawable/message_bubble_outgoing_no_arrow.xml20
-rw-r--r--res/drawable/send_arrow_background.xml32
-rw-r--r--res/drawable/sim_selector_background_gradient.xml24
-rw-r--r--res/drawable/stat_notify_chat.pngbin0 -> 1468 bytes
-rw-r--r--res/drawable/subject_editor_bubble.xml24
-rw-r--r--res/drawable/tab_btn_bg.xml27
-rw-r--r--res/drawable/tab_indicator.xml67
-rw-r--r--res/drawable/transparent_button_background.xml26
-rw-r--r--res/drawable/widget_bottom_background.xml25
-rw-r--r--res/drawable/widget_top_background.xml25
-rw-r--r--res/layout/action_bar_conversation_name.xml31
-rw-r--r--res/layout/add_contacts_confirmation_dialog_body.xml45
-rw-r--r--res/layout/all_contacts_list_view.xml38
-rw-r--r--res/layout/apn_preference_layout.xml69
-rw-r--r--res/layout/attachment_chooser_activity.xml22
-rw-r--r--res/layout/attachment_chooser_audio.xml32
-rw-r--r--res/layout/attachment_chooser_fragment.xml29
-rw-r--r--res/layout/attachment_chooser_image.xml26
-rw-r--r--res/layout/attachment_chooser_vcard.xml28
-rw-r--r--res/layout/attachment_chooser_video.xml24
-rw-r--r--res/layout/attachment_grid_item_view.xml43
-rw-r--r--res/layout/attachment_more_text_view.xml22
-rw-r--r--res/layout/attachment_multiple_audio.xml32
-rw-r--r--res/layout/attachment_multiple_image.xml29
-rw-r--r--res/layout/attachment_multiple_vcard.xml28
-rw-r--r--res/layout/attachment_multiple_video.xml25
-rw-r--r--res/layout/attachment_pending_item.xml44
-rw-r--r--res/layout/attachment_preview.xml44
-rw-r--r--res/layout/attachment_single_audio.xml29
-rw-r--r--res/layout/attachment_single_image.xml46
-rw-r--r--res/layout/attachment_single_vcard.xml30
-rw-r--r--res/layout/attachment_single_video.xml28
-rw-r--r--res/layout/audio_attachment_view.xml62
-rw-r--r--res/layout/blocked_participant_list_item_view.xml51
-rw-r--r--res/layout/blocked_participants_activity.xml23
-rw-r--r--res/layout/blocked_participants_fragment.xml23
-rw-r--r--res/layout/chips_alternates_dropdown_item.xml71
-rw-r--r--res/layout/compose_message_view.xml187
-rw-r--r--res/layout/contact_list_item_view.xml97
-rw-r--r--res/layout/contact_picker_fragment.xml79
-rw-r--r--res/layout/conversation_activity.xml33
-rw-r--r--res/layout/conversation_fragment.xml68
-rw-r--r--res/layout/conversation_list_activity.xml23
-rw-r--r--res/layout/conversation_list_fragment.xml53
-rw-r--r--res/layout/conversation_list_item_view.xml190
-rw-r--r--res/layout/conversation_message_view.xml171
-rw-r--r--res/layout/copy_contact_dialog_view.xml24
-rw-r--r--res/layout/custom_header_view_pager.xml45
-rw-r--r--res/layout/debug_mmsconfig_activity.xml22
-rw-r--r--res/layout/debug_mmsconfig_fragment.xml23
-rw-r--r--res/layout/debug_mmsconfig_item_view.xml46
-rw-r--r--res/layout/debug_sms_mms_from_dump_file_dialog.xml32
-rw-r--r--res/layout/enter_phone_number_view.xml29
-rw-r--r--res/layout/fastscroll_preview.xml25
-rw-r--r--res/layout/fastscroll_thumb.xml19
-rw-r--r--res/layout/fastscroll_track.xml20
-rw-r--r--res/layout/frequent_contacts_list_view.xml38
-rw-r--r--res/layout/gallery_grid_item_view.xml46
-rw-r--r--res/layout/group_mms_setting_dialog.xml37
-rw-r--r--res/layout/license_activity.xml22
-rw-r--r--res/layout/list_empty_view.xml42
-rw-r--r--res/layout/mediapicker_audio_chooser.xml96
-rw-r--r--res/layout/mediapicker_camera_chooser.xml150
-rw-r--r--res/layout/mediapicker_fragment.xml34
-rw-r--r--res/layout/mediapicker_image_chooser.xml44
-rw-r--r--res/layout/mediapicker_location_chooser.xml22
-rw-r--r--res/layout/mediapicker_location_container.xml31
-rw-r--r--res/layout/mediapicker_tab_button.xml24
-rw-r--r--res/layout/message_audio_attachment.xml22
-rw-r--r--res/layout/message_vcard_attachment.xml23
-rw-r--r--res/layout/message_video_attachment.xml25
-rw-r--r--res/layout/mms_config_debug_fragment.xml53
-rw-r--r--res/layout/open_source_licenses.xml22
-rw-r--r--res/layout/people_and_options_activity.xml23
-rw-r--r--res/layout/people_and_options_fragment.xml23
-rw-r--r--res/layout/people_and_options_section_header.xml38
-rw-r--r--res/layout/people_list_item_view.xml26
-rw-r--r--res/layout/people_options_item_view.xml64
-rw-r--r--res/layout/permission_check_activity.xml80
-rw-r--r--res/layout/person_item_view.xml60
-rw-r--r--res/layout/place_card.xml54
-rw-r--r--res/layout/settings_fragment.xml21
-rw-r--r--res/layout/settings_item_view.xml44
-rw-r--r--res/layout/share_intent_activity.xml21
-rw-r--r--res/layout/share_intent_conversation_list_view.xml44
-rw-r--r--res/layout/sim_selector_item_view.xml65
-rw-r--r--res/layout/sim_selector_view.xml32
-rw-r--r--res/layout/sms_free_storage_action_item_view.xml23
-rw-r--r--res/layout/sms_mms_dump_file_list_item.xml30
-rw-r--r--res/layout/sms_storage_low_warning_dialog.xml32
-rw-r--r--res/layout/snack_bar.xml64
-rw-r--r--res/layout/test_activity.xml28
-rw-r--r--res/layout/vcard_detail_activity.xml23
-rw-r--r--res/layout/vcard_detail_fragment.xml28
-rw-r--r--res/layout/video_thumbnail_view.xml38
-rw-r--r--res/layout/viewgroup_vertical_explode_animation_popup.xml30
-rw-r--r--res/layout/widget_conversation.xml110
-rw-r--r--res/layout/widget_conversation_list.xml65
-rw-r--r--res/layout/widget_conversation_list_item.xml144
-rw-r--r--res/layout/widget_loading.xml31
-rw-r--r--res/layout/widget_message_item_incoming.xml119
-rw-r--r--res/layout/widget_message_item_outgoing.xml120
-rw-r--r--res/layout/widget_missing_permission.xml26
-rw-r--r--res/menu/archived_conversation_list_menu.xml25
-rw-r--r--res/menu/attachment_chooser_menu.xml27
-rw-r--r--res/menu/compose_menu.xml51
-rw-r--r--res/menu/conversation_fragment_select_menu.xml63
-rw-r--r--res/menu/conversation_list_fragment_menu.xml51
-rw-r--r--res/menu/conversation_list_fragment_select_menu.xml63
-rw-r--r--res/menu/conversation_menu.xml60
-rw-r--r--res/menu/gallery_picker_menu.xml34
-rw-r--r--res/menu/photo_view_menu.xml31
-rw-r--r--res/menu/settings_menu.xml27
-rw-r--r--res/menu/vcard_detail_fragment_menu.xml27
-rw-r--r--res/raw/audio_end.wavbin0 -> 192044 bytes
-rw-r--r--res/raw/audio_initiate.wavbin0 -> 66092 bytes
-rw-r--r--res/raw/db_op_debug.wavbin0 -> 11352 bytes
-rw-r--r--res/raw/message_failure.wavbin0 -> 177412 bytes
-rw-r--r--res/raw/message_inc_thread.wavbin0 -> 205648 bytes
-rw-r--r--res/raw/message_sent.wavbin0 -> 117260 bytes
-rw-r--r--res/raw/server_request_debug.wavbin0 -> 53696 bytes
-rw-r--r--res/transition/explode.xml17
-rw-r--r--res/transition/fade.xml17
-rw-r--r--res/values-af/arrays.xml37
-rw-r--r--res/values-af/strings.xml519
-rw-r--r--res/values-am/arrays.xml37
-rw-r--r--res/values-am/strings.xml519
-rw-r--r--res/values-ar/arrays.xml37
-rw-r--r--res/values-ar/strings.xml623
-rw-r--r--res/values-az-rAZ/arrays.xml37
-rw-r--r--res/values-az-rAZ/strings.xml519
-rw-r--r--res/values-bg/arrays.xml37
-rw-r--r--res/values-bg/strings.xml519
-rw-r--r--res/values-bn-rBD/arrays.xml37
-rw-r--r--res/values-bn-rBD/strings.xml519
-rw-r--r--res/values-ca/arrays.xml37
-rw-r--r--res/values-ca/strings.xml519
-rw-r--r--res/values-cs/arrays.xml37
-rw-r--r--res/values-cs/strings.xml571
-rw-r--r--res/values-da/arrays.xml37
-rw-r--r--res/values-da/strings.xml519
-rw-r--r--res/values-de/arrays.xml37
-rw-r--r--res/values-de/strings.xml519
-rw-r--r--res/values-el/arrays.xml37
-rw-r--r--res/values-el/strings.xml519
-rw-r--r--res/values-en-rAU/arrays.xml37
-rw-r--r--res/values-en-rAU/strings.xml519
-rw-r--r--res/values-en-rGB/arrays.xml37
-rw-r--r--res/values-en-rGB/strings.xml519
-rw-r--r--res/values-en-rIN/arrays.xml37
-rw-r--r--res/values-en-rIN/strings.xml519
-rw-r--r--res/values-es-rUS/arrays.xml37
-rw-r--r--res/values-es-rUS/strings.xml519
-rw-r--r--res/values-es/arrays.xml37
-rw-r--r--res/values-es/strings.xml519
-rw-r--r--res/values-et-rEE/arrays.xml37
-rw-r--r--res/values-et-rEE/strings.xml519
-rw-r--r--res/values-eu-rES/arrays.xml37
-rw-r--r--res/values-eu-rES/strings.xml519
-rw-r--r--res/values-fa/arrays.xml37
-rw-r--r--res/values-fa/strings.xml519
-rw-r--r--res/values-fi/arrays.xml37
-rw-r--r--res/values-fi/strings.xml519
-rw-r--r--res/values-fr-rCA/arrays.xml37
-rw-r--r--res/values-fr-rCA/strings.xml519
-rw-r--r--res/values-fr/arrays.xml37
-rw-r--r--res/values-fr/strings.xml519
-rw-r--r--res/values-gl-rES/arrays.xml37
-rw-r--r--res/values-gl-rES/strings.xml519
-rw-r--r--res/values-gu-rIN/arrays.xml37
-rw-r--r--res/values-gu-rIN/strings.xml519
-rw-r--r--res/values-hi/arrays.xml37
-rw-r--r--res/values-hi/strings.xml519
-rw-r--r--res/values-hr/arrays.xml37
-rw-r--r--res/values-hr/strings.xml545
-rw-r--r--res/values-hu/arrays.xml37
-rw-r--r--res/values-hu/strings.xml519
-rw-r--r--res/values-hy-rAM/arrays.xml37
-rw-r--r--res/values-hy-rAM/strings.xml519
-rw-r--r--res/values-in/arrays.xml37
-rw-r--r--res/values-in/strings.xml519
-rw-r--r--res/values-is-rIS/arrays.xml37
-rw-r--r--res/values-is-rIS/strings.xml519
-rw-r--r--res/values-it/arrays.xml37
-rw-r--r--res/values-it/strings.xml519
-rw-r--r--res/values-iw/arrays.xml37
-rw-r--r--res/values-iw/strings.xml571
-rw-r--r--res/values-ja/arrays.xml37
-rw-r--r--res/values-ja/strings.xml519
-rw-r--r--res/values-ka-rGE/arrays.xml37
-rw-r--r--res/values-ka-rGE/strings.xml519
-rw-r--r--res/values-kk-rKZ/arrays.xml37
-rw-r--r--res/values-kk-rKZ/strings.xml519
-rw-r--r--res/values-km-rKH/arrays.xml37
-rw-r--r--res/values-km-rKH/strings.xml519
-rw-r--r--res/values-kn-rIN/arrays.xml37
-rw-r--r--res/values-kn-rIN/strings.xml519
-rw-r--r--res/values-ko/arrays.xml37
-rw-r--r--res/values-ko/strings.xml519
-rw-r--r--res/values-ky-rKG/arrays.xml37
-rw-r--r--res/values-ky-rKG/strings.xml519
-rw-r--r--res/values-ldrtl-v21/styles.xml23
-rw-r--r--res/values-ldrtl/styles.xml273
-rw-r--r--res/values-lo-rLA/arrays.xml37
-rw-r--r--res/values-lo-rLA/strings.xml519
-rw-r--r--res/values-lt/arrays.xml37
-rw-r--r--res/values-lt/strings.xml571
-rw-r--r--res/values-lv/arrays.xml37
-rw-r--r--res/values-lv/strings.xml545
-rw-r--r--res/values-mk-rMK/arrays.xml37
-rw-r--r--res/values-mk-rMK/strings.xml519
-rw-r--r--res/values-ml-rIN/arrays.xml37
-rw-r--r--res/values-ml-rIN/strings.xml519
-rw-r--r--res/values-mn-rMN/arrays.xml37
-rw-r--r--res/values-mn-rMN/strings.xml519
-rw-r--r--res/values-mr-rIN/arrays.xml37
-rw-r--r--res/values-mr-rIN/strings.xml519
-rw-r--r--res/values-ms-rMY/arrays.xml37
-rw-r--r--res/values-ms-rMY/strings.xml519
-rw-r--r--res/values-my-rMM/arrays.xml37
-rw-r--r--res/values-my-rMM/strings.xml519
-rw-r--r--res/values-nb/arrays.xml37
-rw-r--r--res/values-nb/strings.xml519
-rw-r--r--res/values-ne-rNP/arrays.xml37
-rw-r--r--res/values-ne-rNP/strings.xml521
-rw-r--r--res/values-nl/arrays.xml37
-rw-r--r--res/values-nl/strings.xml519
-rw-r--r--res/values-pa-rIN/arrays.xml37
-rw-r--r--res/values-pa-rIN/strings.xml519
-rw-r--r--res/values-pl/arrays.xml37
-rw-r--r--res/values-pl/strings.xml571
-rw-r--r--res/values-pt-rPT/arrays.xml37
-rw-r--r--res/values-pt-rPT/strings.xml519
-rw-r--r--res/values-pt/arrays.xml37
-rw-r--r--res/values-pt/strings.xml519
-rw-r--r--res/values-ro/arrays.xml37
-rw-r--r--res/values-ro/strings.xml545
-rw-r--r--res/values-ru/arrays.xml37
-rw-r--r--res/values-ru/strings.xml571
-rw-r--r--res/values-si-rLK/arrays.xml37
-rw-r--r--res/values-si-rLK/strings.xml519
-rw-r--r--res/values-sk/arrays.xml37
-rw-r--r--res/values-sk/strings.xml571
-rw-r--r--res/values-sl/arrays.xml37
-rw-r--r--res/values-sl/strings.xml571
-rw-r--r--res/values-sq-rAL/arrays.xml37
-rw-r--r--res/values-sq-rAL/strings.xml519
-rw-r--r--res/values-sr/arrays.xml37
-rw-r--r--res/values-sr/strings.xml545
-rw-r--r--res/values-sv/arrays.xml37
-rw-r--r--res/values-sv/strings.xml519
-rw-r--r--res/values-sw/arrays.xml37
-rw-r--r--res/values-sw/strings.xml519
-rw-r--r--res/values-ta-rIN/arrays.xml37
-rw-r--r--res/values-ta-rIN/strings.xml519
-rw-r--r--res/values-te-rIN/arrays.xml37
-rw-r--r--res/values-te-rIN/strings.xml519
-rw-r--r--res/values-th/arrays.xml37
-rw-r--r--res/values-th/strings.xml519
-rw-r--r--res/values-tl/arrays.xml37
-rw-r--r--res/values-tl/strings.xml519
-rw-r--r--res/values-tr/arrays.xml37
-rw-r--r--res/values-tr/strings.xml519
-rw-r--r--res/values-uk/arrays.xml37
-rw-r--r--res/values-uk/strings.xml571
-rw-r--r--res/values-ur-rPK/arrays.xml37
-rw-r--r--res/values-ur-rPK/strings.xml519
-rw-r--r--res/values-uz-rUZ/arrays.xml37
-rw-r--r--res/values-uz-rUZ/strings.xml519
-rw-r--r--res/values-v17/styles.xml77
-rw-r--r--res/values-v21/colors.xml22
-rw-r--r--res/values-v21/dimens.xml22
-rw-r--r--res/values-v21/styles.xml41
-rw-r--r--res/values-vi/arrays.xml37
-rw-r--r--res/values-vi/strings.xml519
-rw-r--r--res/values-zh-rCN/arrays.xml37
-rw-r--r--res/values-zh-rCN/strings.xml519
-rw-r--r--res/values-zh-rHK/arrays.xml37
-rw-r--r--res/values-zh-rHK/strings.xml519
-rw-r--r--res/values-zh-rTW/arrays.xml37
-rw-r--r--res/values-zh-rTW/strings.xml519
-rw-r--r--res/values-zu/arrays.xml37
-rw-r--r--res/values-zu/strings.xml519
-rw-r--r--res/values/arrays.xml42
-rw-r--r--res/values/attrs.xml61
-rw-r--r--res/values/colors.xml174
-rw-r--r--res/values/config.xml21
-rw-r--r--res/values/constants.xml95
-rw-r--r--res/values/dimens.xml191
-rw-r--r--res/values/strings.xml976
-rw-r--r--res/values/styles.xml626
-rw-r--r--res/values/versions.xml25
-rw-r--r--res/xml-mcc204-mnc04/mms_config.xml33
-rw-r--r--res/xml-mcc208-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc208-mnc10/mms_config.xml33
-rw-r--r--res/xml-mcc208-mnc15/mms_config.xml33
-rw-r--r--res/xml-mcc208-mnc20/mms_config.xml33
-rw-r--r--res/xml-mcc208-mnc26/mms_config.xml33
-rw-r--r--res/xml-mcc214-mnc01/mms_config.xml26
-rw-r--r--res/xml-mcc214-mnc03/mms_config.xml33
-rw-r--r--res/xml-mcc214-mnc07/mms_config.xml33
-rw-r--r--res/xml-mcc218-mnc05/mms_config.xml33
-rw-r--r--res/xml-mcc222-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc222-mnc08/mms_config.xml33
-rwxr-xr-xres/xml-mcc234-mnc10/mms_config.xml27
l---------res/xml-mcc234-mnc111
-rwxr-xr-xres/xml-mcc234-mnc15/mms_config.xml27
-rw-r--r--res/xml-mcc238-mnc02/mms_config.xml33
-rw-r--r--res/xml-mcc240-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc240-mnc04/mms_config.xml33
-rw-r--r--res/xml-mcc240-mnc08/mms_config.xml33
-rw-r--r--res/xml-mcc240-mnc24/mms_config.xml33
-rw-r--r--res/xml-mcc242-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc242-mnc02/mms_config.xml33
-rw-r--r--res/xml-mcc262-mnc07/mms_config.xml33
-rw-r--r--res/xml-mcc274-mnc02/mms_config.xml33
-rw-r--r--res/xml-mcc274-mnc03/mms_config.xml33
-rw-r--r--res/xml-mcc286-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc286-mnc03/mms_config.xml33
-rw-r--r--res/xml-mcc294-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc294-mnc02/mms_config.xml33
-rw-r--r--res/xml-mcc294-mnc03/mms_config.xml33
-rw-r--r--res/xml-mcc302-mnc220/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc221/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc270/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc290/mms_config.xml33
-rw-r--r--res/xml-mcc302-mnc320/mms_config.xml39
-rw-r--r--res/xml-mcc302-mnc370/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc490/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc500/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc510/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc520/mms_config.xml39
-rw-r--r--res/xml-mcc302-mnc610/mms_config.xml55
-rw-r--r--res/xml-mcc302-mnc660/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc720/mms_config.xml46
-rw-r--r--res/xml-mcc302-mnc780/mms_config.xml33
-rw-r--r--res/xml-mcc310-mnc004/mms_config.xml83
-rw-r--r--res/xml-mcc310-mnc005/mms_config.xml83
-rw-r--r--res/xml-mcc310-mnc010/mms_config.xml55
-rw-r--r--res/xml-mcc310-mnc026/mms_config.xml28
-rw-r--r--res/xml-mcc310-mnc040/mms_config.xml83
-rwxr-xr-xres/xml-mcc310-mnc070/mms_config.xml32
-rwxr-xr-xres/xml-mcc310-mnc090/mms_config.xml28
-rw-r--r--res/xml-mcc310-mnc120/mms_config.xml75
-rw-r--r--res/xml-mcc310-mnc130/mms_config.xml83
-rw-r--r--res/xml-mcc310-mnc150/mms_config.xml33
-rw-r--r--res/xml-mcc310-mnc170/mms_config.xml32
-rw-r--r--res/xml-mcc310-mnc260/mms_config.xml28
-rw-r--r--res/xml-mcc310-mnc360/mms_config.xml83
-rwxr-xr-xres/xml-mcc310-mnc380/mms_config.xml37
-rwxr-xr-xres/xml-mcc310-mnc410/mms_config.xml32
-rw-r--r--res/xml-mcc310-mnc420/mms_config.xml33
-rw-r--r--res/xml-mcc310-mnc450/mms_config.xml33
-rw-r--r--res/xml-mcc310-mnc490/mms_config.xml28
-rwxr-xr-xres/xml-mcc310-mnc560/mms_config.xml32
-rw-r--r--res/xml-mcc310-mnc580/mms_config.xml83
-rw-r--r--res/xml-mcc310-mnc600/mms_config.xml90
-rwxr-xr-xres/xml-mcc310-mnc680/mms_config.xml32
-rw-r--r--res/xml-mcc310-mnc750/mms_config.xml83
-rw-r--r--res/xml-mcc310-mnc770/mms_config.xml33
-rw-r--r--res/xml-mcc310-mnc920/mms_config.xml83
-rw-r--r--res/xml-mcc310-mnc980/mms_config.xml33
-rw-r--r--res/xml-mcc311-mnc012/mms_config.xml83
-rw-r--r--res/xml-mcc311-mnc070/mms_config.xml83
-rwxr-xr-xres/xml-mcc311-mnc100/mms_config.xml95
-rw-r--r--res/xml-mcc311-mnc180/mms_config.xml37
-rw-r--r--res/xml-mcc311-mnc220/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc221/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc222/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc223/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc224/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc225/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc226/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc227/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc228/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc229/mms_config.xml65
-rwxr-xr-xres/xml-mcc311-mnc230/mms_config.xml95
-rw-r--r--res/xml-mcc311-mnc340/mms_config.xml83
-rw-r--r--res/xml-mcc311-mnc370/mms_config.xml33
-rw-r--r--res/xml-mcc311-mnc410/mms_config.xml83
-rw-r--r--res/xml-mcc311-mnc430/mms_config.xml83
-rw-r--r--res/xml-mcc311-mnc440/mms_config.xml89
-rw-r--r--res/xml-mcc311-mnc480/mms_config.xml70
-rw-r--r--res/xml-mcc311-mnc490/mms_config.xml75
-rw-r--r--res/xml-mcc311-mnc580/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc581/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc582/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc583/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc584/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc585/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc586/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc587/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc588/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc589/mms_config.xml65
-rw-r--r--res/xml-mcc311-mnc590/mms_config.xml83
-rw-r--r--res/xml-mcc311-mnc870/mms_config.xml75
-rw-r--r--res/xml-mcc312-mnc160/mms_config.xml83
-rwxr-xr-xres/xml-mcc312-mnc530/mms_config.xml75
-rw-r--r--res/xml-mcc334-mnc020/mms_config.xml33
-rw-r--r--res/xml-mcc418-mnc05/mms_config.xml33
-rw-r--r--res/xml-mcc418-mnc20/mms_config.xml33
-rw-r--r--res/xml-mcc418-mnc30/mms_config.xml33
-rw-r--r--res/xml-mcc420-mnc04/mms_config.xml33
-rw-r--r--res/xml-mcc440-mnc10/mms_config.xml33
-rw-r--r--res/xml-mcc440-mnc20/mms_config.xml56
-rw-r--r--res/xml-mcc450-mnc00/mms_config.xml33
-rw-r--r--res/xml-mcc450-mnc02/mms_config.xml39
-rw-r--r--res/xml-mcc450-mnc05/mms_config.xml39
-rw-r--r--res/xml-mcc450-mnc06/mms_config.xml42
-rw-r--r--res/xml-mcc450-mnc08/mms_config.xml39
-rw-r--r--res/xml-mcc505-mnc01/mms_config.xml33
-rw-r--r--res/xml-mcc530-mnc05/mms_config.xml36
-rw-r--r--res/xml-mcc604-mnc00/mms_config.xml33
-rw-r--r--res/xml-mcc604-mnc02/mms_config.xml33
-rw-r--r--res/xml-mcc647-mnc10/mms_config.xml33
-rw-r--r--res/xml-v21/preferences_application.xml91
-rw-r--r--res/xml-v21/preferences_per_subscription.xml66
-rw-r--r--res/xml-v23/preferences_application.xml92
-rw-r--r--res/xml/apn_editor.xml63
-rw-r--r--res/xml/apn_settings.xml21
-rw-r--r--res/xml/apns.xml3284
-rw-r--r--res/xml/mms_config.xml75
-rw-r--r--res/xml/preferences_application.xml91
-rw-r--r--res/xml/preferences_per_subscription.xml66
-rw-r--r--res/xml/widget_conversation.xml29
-rw-r--r--res/xml/widget_conversation_list.xml28
-rw-r--r--src/android/support/v7/mms/ApnException.java39
-rw-r--r--src/android/support/v7/mms/ApnSettingsLoader.java63
-rw-r--r--src/android/support/v7/mms/ApnsXmlParser.java73
-rw-r--r--src/android/support/v7/mms/CarrierConfigValuesLoader.java198
-rw-r--r--src/android/support/v7/mms/CarrierConfigXmlParser.java68
-rw-r--r--src/android/support/v7/mms/DefaultApnSettingsLoader.java497
-rw-r--r--src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java126
-rw-r--r--src/android/support/v7/mms/DefaultUserAgentInfoLoader.java88
-rw-r--r--src/android/support/v7/mms/DownloadRequest.java132
-rw-r--r--src/android/support/v7/mms/MmsHttpClient.java523
-rw-r--r--src/android/support/v7/mms/MmsHttpException.java50
-rw-r--r--src/android/support/v7/mms/MmsManager.java256
-rw-r--r--src/android/support/v7/mms/MmsNetworkException.java39
-rw-r--r--src/android/support/v7/mms/MmsNetworkManager.java382
-rw-r--r--src/android/support/v7/mms/MmsRequest.java391
-rw-r--r--src/android/support/v7/mms/MmsService.java465
-rw-r--r--src/android/support/v7/mms/MmsXmlResourceParser.java143
-rw-r--r--src/android/support/v7/mms/PhoneNumberHelper.java54
-rw-r--r--src/android/support/v7/mms/SendRequest.java163
-rw-r--r--src/android/support/v7/mms/UserAgentInfoLoader.java40
-rw-r--r--src/android/support/v7/mms/Utils.java180
-rw-r--r--src/android/support/v7/mms/pdu/AcknowledgeInd.java87
-rw-r--r--src/android/support/v7/mms/pdu/Base64.java167
-rw-r--r--src/android/support/v7/mms/pdu/CharacterSets.java172
-rw-r--r--src/android/support/v7/mms/pdu/ContentType.java230
-rw-r--r--src/android/support/v7/mms/pdu/DeliveryInd.java136
-rw-r--r--src/android/support/v7/mms/pdu/EncodedStringValue.java283
-rw-r--r--src/android/support/v7/mms/pdu/GenericPdu.java111
-rw-r--r--src/android/support/v7/mms/pdu/InvalidHeaderValueException.java41
-rw-r--r--src/android/support/v7/mms/pdu/MmsException.java60
-rw-r--r--src/android/support/v7/mms/pdu/MultimediaMessagePdu.java148
-rw-r--r--src/android/support/v7/mms/pdu/NotificationInd.java283
-rw-r--r--src/android/support/v7/mms/pdu/NotifyRespInd.java112
-rw-r--r--src/android/support/v7/mms/pdu/PduBody.java191
-rw-r--r--src/android/support/v7/mms/pdu/PduContentTypes.java110
-rw-r--r--src/android/support/v7/mms/pdu/PduHeaders.java719
-rwxr-xr-xsrc/android/support/v7/mms/pdu/PduParser.java2008
-rw-r--r--src/android/support/v7/mms/pdu/PduPart.java414
-rw-r--r--src/android/support/v7/mms/pdu/QuotedPrintable.java68
-rw-r--r--src/android/support/v7/mms/pdu/ReadOrigInd.java151
-rw-r--r--src/android/support/v7/mms/pdu/ReadRecInd.java142
-rw-r--r--src/android/support/v7/mms/pdu/RetrieveConf.java298
-rw-r--r--src/android/support/v7/mms/pdu/SendConf.java115
-rw-r--r--src/android/support/v7/mms/pdu/SendReq.java343
-rw-r--r--src/com/android/messaging/BugleApplication.java262
-rw-r--r--src/com/android/messaging/Factory.java75
-rw-r--r--src/com/android/messaging/FactoryImpl.java245
-rw-r--r--src/com/android/messaging/annotation/VisibleForAnimation.java23
-rw-r--r--src/com/android/messaging/datamodel/BitmapPool.java364
-rw-r--r--src/com/android/messaging/datamodel/BoundCursorLoader.java46
-rw-r--r--src/com/android/messaging/datamodel/BugleDatabaseOperations.java1919
-rw-r--r--src/com/android/messaging/datamodel/BugleNotifications.java1221
-rw-r--r--src/com/android/messaging/datamodel/BugleRecipientEntry.java64
-rw-r--r--src/com/android/messaging/datamodel/ConversationImagePartsView.java120
-rw-r--r--src/com/android/messaging/datamodel/CursorQueryData.java82
-rw-r--r--src/com/android/messaging/datamodel/DataModel.java158
-rw-r--r--src/com/android/messaging/datamodel/DataModelException.java101
-rw-r--r--src/com/android/messaging/datamodel/DataModelImpl.java236
-rw-r--r--src/com/android/messaging/datamodel/DatabaseHelper.java813
-rw-r--r--src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java42
-rw-r--r--src/com/android/messaging/datamodel/DatabaseWrapper.java482
-rw-r--r--src/com/android/messaging/datamodel/FileProvider.java151
-rw-r--r--src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java179
-rw-r--r--src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java114
-rw-r--r--src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java49
-rw-r--r--src/com/android/messaging/datamodel/MediaScratchFileProvider.java132
-rw-r--r--src/com/android/messaging/datamodel/MemoryCacheManager.java75
-rw-r--r--src/com/android/messaging/datamodel/MessageNotificationState.java1342
-rw-r--r--src/com/android/messaging/datamodel/MessageTextStats.java92
-rw-r--r--src/com/android/messaging/datamodel/MessagingContentProvider.java476
-rw-r--r--src/com/android/messaging/datamodel/MmsFileProvider.java69
-rw-r--r--src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java148
-rw-r--r--src/com/android/messaging/datamodel/NotificationState.java149
-rw-r--r--src/com/android/messaging/datamodel/ParticipantRefresh.java738
-rw-r--r--src/com/android/messaging/datamodel/SyncManager.java478
-rw-r--r--src/com/android/messaging/datamodel/action/Action.java295
-rw-r--r--src/com/android/messaging/datamodel/action/ActionMonitor.java477
-rw-r--r--src/com/android/messaging/datamodel/action/ActionService.java63
-rw-r--r--src/com/android/messaging/datamodel/action/ActionServiceImpl.java341
-rw-r--r--src/com/android/messaging/datamodel/action/BackgroundWorker.java32
-rw-r--r--src/com/android/messaging/datamodel/action/BackgroundWorkerService.java168
-rw-r--r--src/com/android/messaging/datamodel/action/BugleActionToasts.java172
-rw-r--r--src/com/android/messaging/datamodel/action/DeleteConversationAction.java205
-rw-r--r--src/com/android/messaging/datamodel/action/DeleteMessageAction.java135
-rw-r--r--src/com/android/messaging/datamodel/action/DownloadMmsAction.java340
-rw-r--r--src/com/android/messaging/datamodel/action/DumpDatabaseAction.java124
-rw-r--r--src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java114
-rw-r--r--src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java173
-rw-r--r--src/com/android/messaging/datamodel/action/HandleLowStorageAction.java94
-rw-r--r--src/com/android/messaging/datamodel/action/InsertNewMessageAction.java480
-rw-r--r--src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java153
-rw-r--r--src/com/android/messaging/datamodel/action/MarkAsReadAction.java113
-rw-r--r--src/com/android/messaging/datamodel/action/MarkAsSeenAction.java126
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java122
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java573
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java470
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java310
-rw-r--r--src/com/android/messaging/datamodel/action/ReadDraftDataAction.java166
-rw-r--r--src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java197
-rw-r--r--src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java198
-rw-r--r--src/com/android/messaging/datamodel/action/RedownloadMmsAction.java128
-rw-r--r--src/com/android/messaging/datamodel/action/ResendMessageAction.java128
-rw-r--r--src/com/android/messaging/datamodel/action/SendMessageAction.java447
-rw-r--r--src/com/android/messaging/datamodel/action/SyncCursorPair.java712
-rw-r--r--src/com/android/messaging/datamodel/action/SyncMessageBatch.java383
-rw-r--r--src/com/android/messaging/datamodel/action/SyncMessagesAction.java637
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java93
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java156
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java148
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java63
-rw-r--r--src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java103
-rw-r--r--src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java104
-rw-r--r--src/com/android/messaging/datamodel/binding/BindableData.java72
-rw-r--r--src/com/android/messaging/datamodel/binding/BindableOnceData.java42
-rw-r--r--src/com/android/messaging/datamodel/binding/Binding.java94
-rw-r--r--src/com/android/messaging/datamodel/binding/BindingBase.java84
-rw-r--r--src/com/android/messaging/datamodel/binding/DetachableBinding.java56
-rw-r--r--src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java83
-rw-r--r--src/com/android/messaging/datamodel/data/BlockedParticipantsData.java103
-rw-r--r--src/com/android/messaging/datamodel/data/ContactListItemData.java160
-rw-r--r--src/com/android/messaging/datamodel/data/ContactPickerData.java194
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationData.java849
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationListData.java211
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationListItemData.java510
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java37
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationMessageData.java917
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationParticipantsData.java125
-rw-r--r--src/com/android/messaging/datamodel/data/DraftMessageData.java855
-rw-r--r--src/com/android/messaging/datamodel/data/GalleryGridItemData.java128
-rw-r--r--src/com/android/messaging/datamodel/data/LaunchConversationData.java90
-rw-r--r--src/com/android/messaging/datamodel/data/MediaPickerData.java175
-rw-r--r--src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java64
-rw-r--r--src/com/android/messaging/datamodel/data/MessageData.java922
-rw-r--r--src/com/android/messaging/datamodel/data/MessagePartData.java534
-rw-r--r--src/com/android/messaging/datamodel/data/ParticipantData.java569
-rw-r--r--src/com/android/messaging/datamodel/data/ParticipantListItemData.java95
-rw-r--r--src/com/android/messaging/datamodel/data/PendingAttachmentData.java176
-rw-r--r--src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java210
-rw-r--r--src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java155
-rw-r--r--src/com/android/messaging/datamodel/data/PersonItemData.java67
-rw-r--r--src/com/android/messaging/datamodel/data/SelfParticipantsData.java107
-rw-r--r--src/com/android/messaging/datamodel/data/SettingsData.java223
-rw-r--r--src/com/android/messaging/datamodel/data/SubscriptionListData.java128
-rw-r--r--src/com/android/messaging/datamodel/data/VCardContactItemData.java185
-rw-r--r--src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java74
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java159
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarRequest.java189
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java60
-rw-r--r--src/com/android/messaging/datamodel/media/BindableMediaRequest.java63
-rw-r--r--src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java54
-rw-r--r--src/com/android/messaging/datamodel/media/CompositeImageRequest.java109
-rw-r--r--src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java61
-rw-r--r--src/com/android/messaging/datamodel/media/CustomVCardEntry.java48
-rw-r--r--src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java133
-rw-r--r--src/com/android/messaging/datamodel/media/DecodedImageResource.java254
-rw-r--r--src/com/android/messaging/datamodel/media/EncodedImageResource.java162
-rw-r--r--src/com/android/messaging/datamodel/media/FileImageRequest.java108
-rw-r--r--src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java68
-rw-r--r--src/com/android/messaging/datamodel/media/GifImageResource.java110
-rw-r--r--src/com/android/messaging/datamodel/media/ImageRequest.java258
-rw-r--r--src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java113
-rw-r--r--src/com/android/messaging/datamodel/media/ImageResource.java63
-rw-r--r--src/com/android/messaging/datamodel/media/MediaBytes.java42
-rw-r--r--src/com/android/messaging/datamodel/media/MediaCache.java113
-rw-r--r--src/com/android/messaging/datamodel/media/MediaCacheManager.java69
-rw-r--r--src/com/android/messaging/datamodel/media/MediaRequest.java70
-rw-r--r--src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java38
-rw-r--r--src/com/android/messaging/datamodel/media/MediaResourceManager.java325
-rw-r--r--src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java64
-rw-r--r--src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java38
-rw-r--r--src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java120
-rw-r--r--src/com/android/messaging/datamodel/media/PoolableImageCache.java419
-rw-r--r--src/com/android/messaging/datamodel/media/RefCountedMediaResource.java164
-rw-r--r--src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java117
-rw-r--r--src/com/android/messaging/datamodel/media/UriImageRequest.java58
-rw-r--r--src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java95
-rw-r--r--src/com/android/messaging/datamodel/media/VCardRequest.java328
-rw-r--r--src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java35
-rw-r--r--src/com/android/messaging/datamodel/media/VCardResource.java52
-rw-r--r--src/com/android/messaging/datamodel/media/VCardResourceEntry.java389
-rw-r--r--src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java78
-rw-r--r--src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java44
-rw-r--r--src/com/android/messaging/mmslib/Downloads.java807
-rw-r--r--src/com/android/messaging/mmslib/InvalidHeaderValueException.java41
-rw-r--r--src/com/android/messaging/mmslib/MmsException.java60
-rw-r--r--src/com/android/messaging/mmslib/SqliteWrapper.java88
-rw-r--r--src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java89
-rw-r--r--src/com/android/messaging/mmslib/pdu/Base64.java167
-rw-r--r--src/com/android/messaging/mmslib/pdu/CharacterSets.java452
-rw-r--r--src/com/android/messaging/mmslib/pdu/DeliveryInd.java138
-rw-r--r--src/com/android/messaging/mmslib/pdu/EncodedStringValue.java298
-rw-r--r--src/com/android/messaging/mmslib/pdu/GenericPdu.java113
-rw-r--r--src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java159
-rw-r--r--src/com/android/messaging/mmslib/pdu/NotificationInd.java285
-rw-r--r--src/com/android/messaging/mmslib/pdu/NotifyRespInd.java114
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduBody.java87
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduComposer.java1260
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduContentTypes.java110
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduHeaders.java743
-rwxr-xr-xsrc/com/android/messaging/mmslib/pdu/PduParser.java2044
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduPart.java389
-rw-r--r--src/com/android/messaging/mmslib/pdu/PduPersister.java1683
-rw-r--r--src/com/android/messaging/mmslib/pdu/QuotedPrintable.java68
-rw-r--r--src/com/android/messaging/mmslib/pdu/ReadOrigInd.java153
-rw-r--r--src/com/android/messaging/mmslib/pdu/ReadRecInd.java144
-rw-r--r--src/com/android/messaging/mmslib/pdu/RetrieveConf.java305
-rw-r--r--src/com/android/messaging/mmslib/pdu/SendConf.java117
-rw-r--r--src/com/android/messaging/mmslib/pdu/SendReq.java346
-rw-r--r--src/com/android/messaging/mmslib/util/AbstractCache.java112
-rw-r--r--src/com/android/messaging/mmslib/util/DownloadDrmHelper.java111
-rw-r--r--src/com/android/messaging/mmslib/util/DrmConvertSession.java174
-rw-r--r--src/com/android/messaging/mmslib/util/PduCache.java262
-rw-r--r--src/com/android/messaging/mmslib/util/PduCacheEntry.java44
-rw-r--r--src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java43
-rw-r--r--src/com/android/messaging/receiver/AbortSmsReceiver.java41
-rw-r--r--src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java49
-rw-r--r--src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java32
-rw-r--r--src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java43
-rw-r--r--src/com/android/messaging/receiver/MmsWapPushReceiver.java58
-rw-r--r--src/com/android/messaging/receiver/NotificationReceiver.java57
-rw-r--r--src/com/android/messaging/receiver/SendStatusReceiver.java96
-rw-r--r--src/com/android/messaging/receiver/SmsDeliverReceiver.java31
-rw-r--r--src/com/android/messaging/receiver/SmsReceiver.java375
-rw-r--r--src/com/android/messaging/receiver/StorageStatusReceiver.java38
-rw-r--r--src/com/android/messaging/sms/ApnDatabase.java374
-rw-r--r--src/com/android/messaging/sms/ApnsXmlProcessor.java329
-rw-r--r--src/com/android/messaging/sms/BugleApnSettingsLoader.java646
-rw-r--r--src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java201
-rw-r--r--src/com/android/messaging/sms/BugleUserAgentInfoLoader.java96
-rw-r--r--src/com/android/messaging/sms/DatabaseMessages.java1006
-rwxr-xr-xsrc/com/android/messaging/sms/MmsConfig.java309
-rw-r--r--src/com/android/messaging/sms/MmsFailureException.java102
-rw-r--r--src/com/android/messaging/sms/MmsSender.java312
-rw-r--r--src/com/android/messaging/sms/MmsSmsUtils.java204
-rw-r--r--src/com/android/messaging/sms/MmsUtils.java2747
-rw-r--r--src/com/android/messaging/sms/SmsException.java59
-rw-r--r--src/com/android/messaging/sms/SmsReleaseStorage.java166
-rw-r--r--src/com/android/messaging/sms/SmsSender.java315
-rw-r--r--src/com/android/messaging/sms/SmsStorageStatusManager.java102
-rw-r--r--src/com/android/messaging/sms/SystemProperties.java54
-rw-r--r--src/com/android/messaging/ui/AsyncImageView.java457
-rw-r--r--src/com/android/messaging/ui/AttachmentPreview.java331
-rw-r--r--src/com/android/messaging/ui/AttachmentPreviewFactory.java299
-rw-r--r--src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java59
-rw-r--r--src/com/android/messaging/ui/AudioAttachmentView.java329
-rw-r--r--src/com/android/messaging/ui/AudioPlaybackProgressBar.java121
-rw-r--r--src/com/android/messaging/ui/BaseBugleActivity.java51
-rw-r--r--src/com/android/messaging/ui/BaseBugleFragmentActivity.java43
-rw-r--r--src/com/android/messaging/ui/BasePagerViewHolder.java106
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantListItemView.java64
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantsActivity.java57
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantsFragment.java102
-rw-r--r--src/com/android/messaging/ui/BugleActionBarActivity.java356
-rw-r--r--src/com/android/messaging/ui/BugleAnimationTags.java38
-rw-r--r--src/com/android/messaging/ui/ClassZeroActivity.java205
-rw-r--r--src/com/android/messaging/ui/CompositeAdapter.java288
-rw-r--r--src/com/android/messaging/ui/ContactIconView.java152
-rw-r--r--src/com/android/messaging/ui/ConversationDrawables.java177
-rw-r--r--src/com/android/messaging/ui/CursorRecyclerAdapter.java333
-rw-r--r--src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java136
-rw-r--r--src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java26
-rw-r--r--src/com/android/messaging/ui/CustomHeaderViewPager.java91
-rw-r--r--src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java33
-rw-r--r--src/com/android/messaging/ui/FixedViewPagerAdapter.java132
-rw-r--r--src/com/android/messaging/ui/ImeDetectFrameLayout.java46
-rw-r--r--src/com/android/messaging/ui/LicenseActivity.java35
-rw-r--r--src/com/android/messaging/ui/LineWrapLayout.java232
-rw-r--r--src/com/android/messaging/ui/ListEmptyView.java72
-rw-r--r--src/com/android/messaging/ui/MaxHeightScrollView.java50
-rw-r--r--src/com/android/messaging/ui/MultiAttachmentLayout.java424
-rw-r--r--src/com/android/messaging/ui/OrientedBitmapDrawable.java104
-rw-r--r--src/com/android/messaging/ui/PagerViewHolder.java34
-rw-r--r--src/com/android/messaging/ui/PagingAwareViewPager.java95
-rw-r--r--src/com/android/messaging/ui/PermissionCheckActivity.java141
-rw-r--r--src/com/android/messaging/ui/PersistentInstanceState.java39
-rw-r--r--src/com/android/messaging/ui/PersonItemView.java242
-rw-r--r--src/com/android/messaging/ui/PlaceholderInsetDrawable.java72
-rw-r--r--src/com/android/messaging/ui/PlainTextEditText.java85
-rw-r--r--src/com/android/messaging/ui/PlaybackStateView.java43
-rw-r--r--src/com/android/messaging/ui/RemoteInputEntrypointActivity.java58
-rw-r--r--src/com/android/messaging/ui/SmsStorageLowWarningActivity.java36
-rw-r--r--src/com/android/messaging/ui/SmsStorageLowWarningFragment.java267
-rw-r--r--src/com/android/messaging/ui/SnackBar.java314
-rw-r--r--src/com/android/messaging/ui/SnackBarInteraction.java67
-rw-r--r--src/com/android/messaging/ui/SnackBarManager.java365
-rw-r--r--src/com/android/messaging/ui/TestActivity.java88
-rw-r--r--src/com/android/messaging/ui/UIIntents.java378
-rw-r--r--src/com/android/messaging/ui/UIIntentsImpl.java577
-rw-r--r--src/com/android/messaging/ui/VCardDetailActivity.java60
-rw-r--r--src/com/android/messaging/ui/VCardDetailAdapter.java120
-rw-r--r--src/com/android/messaging/ui/VCardDetailFragment.java197
-rw-r--r--src/com/android/messaging/ui/VideoThumbnailView.java343
-rw-r--r--src/com/android/messaging/ui/ViewPagerTabStrip.java102
-rw-r--r--src/com/android/messaging/ui/ViewPagerTabs.java236
-rw-r--r--src/com/android/messaging/ui/WidgetPickConversationActivity.java114
-rw-r--r--src/com/android/messaging/ui/animation/PopupTransitionAnimation.java302
-rw-r--r--src/com/android/messaging/ui/animation/RectEvaluatorCompat.java45
-rw-r--r--src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java208
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnEditorActivity.java463
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnPreference.java151
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java406
-rw-r--r--src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java262
-rw-r--r--src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java92
-rw-r--r--src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java246
-rw-r--r--src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java116
-rw-r--r--src/com/android/messaging/ui/appsettings/SettingsActivity.java178
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java56
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java183
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java119
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java172
-rw-r--r--src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java85
-rw-r--r--src/com/android/messaging/ui/contact/AllContactsListViewHolder.java62
-rw-r--r--src/com/android/messaging/ui/contact/ContactDropdownLayouter.java138
-rw-r--r--src/com/android/messaging/ui/contact/ContactListAdapter.java86
-rw-r--r--src/com/android/messaging/ui/contact/ContactListItemView.java177
-rw-r--r--src/com/android/messaging/ui/contact/ContactPickerFragment.java607
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientAdapter.java286
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java289
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java96
-rw-r--r--src/com/android/messaging/ui/contact/ContactSectionIndexer.java169
-rw-r--r--src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java63
-rw-r--r--src/com/android/messaging/ui/conversation/ComposeMessageView.java962
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivity.java379
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivityUiState.java306
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFastScroller.java489
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFragment.java1662
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationInput.java103
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationInputManager.java550
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java117
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java132
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageView.java1206
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationSimSelector.java128
-rw-r--r--src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java92
-rw-r--r--src/com/android/messaging/ui/conversation/LaunchConversationActivity.java134
-rw-r--r--src/com/android/messaging/ui/conversation/MessageBubbleBackground.java47
-rw-r--r--src/com/android/messaging/ui/conversation/MessageDetailsDialog.java381
-rw-r--r--src/com/android/messaging/ui/conversation/SimIconView.java51
-rw-r--r--src/com/android/messaging/ui/conversation/SimSelectorItemView.java90
-rw-r--r--src/com/android/messaging/ui/conversation/SimSelectorView.java169
-rw-r--r--src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java339
-rw-r--r--src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java96
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListActivity.java144
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java77
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListFragment.java446
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListItemView.java643
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java462
-rw-r--r--src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java81
-rw-r--r--src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java219
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java177
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java138
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java163
-rw-r--r--src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java66
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java66
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java329
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java99
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java34
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java147
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java134
-rw-r--r--src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java171
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioLevelSource.java73
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java130
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioRecordView.java351
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraManager.java1200
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java481
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java102
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraPreview.java152
-rw-r--r--src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java128
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java62
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java159
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridView.java315
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java230
-rw-r--r--src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java118
-rw-r--r--src/com/android/messaging/ui/mediapicker/ImagePersistTask.java172
-rw-r--r--src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java223
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaChooser.java216
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPicker.java736
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java44
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java563
-rw-r--r--src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java127
-rw-r--r--src/com/android/messaging/ui/mediapicker/PausableChronometer.java75
-rw-r--r--src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java114
-rw-r--r--src/com/android/messaging/ui/mediapicker/SoundLevels.java212
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java24
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java589
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java95
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java202
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java825
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/README.txt3
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java178
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java192
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java38
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java31
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java179
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java89
-rw-r--r--src/com/android/messaging/util/AccessibilityUtil.java170
-rw-r--r--src/com/android/messaging/util/Assert.java214
-rw-r--r--src/com/android/messaging/util/AvatarUriUtil.java320
-rw-r--r--src/com/android/messaging/util/BugleActivityUtil.java88
-rw-r--r--src/com/android/messaging/util/BugleApplicationPrefs.java45
-rw-r--r--src/com/android/messaging/util/BugleGservices.java72
-rw-r--r--src/com/android/messaging/util/BugleGservicesImpl.java68
-rw-r--r--src/com/android/messaging/util/BugleGservicesKeys.java298
-rw-r--r--src/com/android/messaging/util/BuglePrefs.java140
-rw-r--r--src/com/android/messaging/util/BuglePrefsImpl.java135
-rw-r--r--src/com/android/messaging/util/BuglePrefsKeys.java71
-rw-r--r--src/com/android/messaging/util/BugleSubscriptionPrefs.java95
-rw-r--r--src/com/android/messaging/util/BugleWidgetPrefs.java41
-rw-r--r--src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java157
-rw-r--r--src/com/android/messaging/util/CircularArray.java111
-rw-r--r--src/com/android/messaging/util/ConnectivityUtil.java247
-rw-r--r--src/com/android/messaging/util/ContactRecipientEntryUtils.java115
-rw-r--r--src/com/android/messaging/util/ContactUtil.java525
-rw-r--r--src/com/android/messaging/util/ContentType.java185
-rw-r--r--src/com/android/messaging/util/ConversationIdSet.java69
-rw-r--r--src/com/android/messaging/util/CubicBezierInterpolator.java120
-rw-r--r--src/com/android/messaging/util/Dates.java280
-rw-r--r--src/com/android/messaging/util/DebugUtils.java425
-rw-r--r--src/com/android/messaging/util/EmailAddress.java272
-rw-r--r--src/com/android/messaging/util/FallbackStrategies.java91
-rw-r--r--src/com/android/messaging/util/FileUtil.java140
-rw-r--r--src/com/android/messaging/util/GifTranscoder.java94
-rw-r--r--src/com/android/messaging/util/ImageUtils.java908
-rw-r--r--src/com/android/messaging/util/ImeUtil.java88
-rw-r--r--src/com/android/messaging/util/LogSaver.java293
-rw-r--r--src/com/android/messaging/util/LogUtil.java274
-rw-r--r--src/com/android/messaging/util/LoggingTimer.java70
-rw-r--r--src/com/android/messaging/util/LongSparseSet.java59
-rw-r--r--src/com/android/messaging/util/MaterialPalette.java27
-rw-r--r--src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java81
-rw-r--r--src/com/android/messaging/util/MediaUtil.java36
-rw-r--r--src/com/android/messaging/util/MediaUtilImpl.java66
-rw-r--r--src/com/android/messaging/util/NotificationPlayer.java363
-rw-r--r--src/com/android/messaging/util/OsUtil.java269
-rw-r--r--src/com/android/messaging/util/PendingIntentConstants.java40
-rw-r--r--src/com/android/messaging/util/PhoneUtils.java1011
-rw-r--r--src/com/android/messaging/util/RingtoneUtil.java53
-rw-r--r--src/com/android/messaging/util/SafeAsyncTask.java176
-rw-r--r--src/com/android/messaging/util/SwitchCompatUtils.java129
-rw-r--r--src/com/android/messaging/util/TextUtil.java73
-rw-r--r--src/com/android/messaging/util/ThreadUtil.java28
-rw-r--r--src/com/android/messaging/util/TintDrawableWrapper.java70
-rw-r--r--src/com/android/messaging/util/Trace.java115
-rw-r--r--src/com/android/messaging/util/Typefaces.java45
-rw-r--r--src/com/android/messaging/util/UiUtils.java438
-rw-r--r--src/com/android/messaging/util/UriUtil.java393
-rw-r--r--src/com/android/messaging/util/VersionUtil.java67
-rw-r--r--src/com/android/messaging/util/WakeLockHelper.java121
-rw-r--r--src/com/android/messaging/util/YouTubeUtil.java98
-rw-r--r--src/com/android/messaging/util/exif/ByteBufferInputStream.java48
-rw-r--r--src/com/android/messaging/util/exif/CountedDataInputStream.java140
-rw-r--r--src/com/android/messaging/util/exif/ExifData.java349
-rw-r--r--src/com/android/messaging/util/exif/ExifInterface.java2448
-rw-r--r--src/com/android/messaging/util/exif/ExifInvalidFormatException.java23
-rw-r--r--src/com/android/messaging/util/exif/ExifModifier.java196
-rw-r--r--src/com/android/messaging/util/exif/ExifOutputStream.java522
-rw-r--r--src/com/android/messaging/util/exif/ExifParser.java918
-rw-r--r--src/com/android/messaging/util/exif/ExifReader.java93
-rw-r--r--src/com/android/messaging/util/exif/ExifTag.java1008
-rw-r--r--src/com/android/messaging/util/exif/IfdData.java152
-rw-r--r--src/com/android/messaging/util/exif/IfdId.java31
-rw-r--r--src/com/android/messaging/util/exif/JpegHeader.java39
-rw-r--r--src/com/android/messaging/util/exif/OrderedDataOutputStream.java56
-rw-r--r--src/com/android/messaging/util/exif/Rational.java88
-rw-r--r--src/com/android/messaging/widget/BaseWidgetFactory.java232
-rw-r--r--src/com/android/messaging/widget/BaseWidgetProvider.java184
-rw-r--r--src/com/android/messaging/widget/BugleWidgetProvider.java114
-rw-r--r--src/com/android/messaging/widget/WidgetConversationListService.java281
-rw-r--r--src/com/android/messaging/widget/WidgetConversationProvider.java316
-rw-r--r--src/com/android/messaging/widget/WidgetConversationService.java521
-rw-r--r--tests/Android.mk39
-rw-r--r--tests/AndroidManifest.xml31
-rw-r--r--tests/src/com/android/messaging/BugleTestCase.java59
-rw-r--r--tests/src/com/android/messaging/FakeContentProvider.java177
-rw-r--r--tests/src/com/android/messaging/FakeContext.java101
-rw-r--r--tests/src/com/android/messaging/FakeFactory.java288
-rw-r--r--tests/src/com/android/messaging/TestUtil.java84
-rw-r--r--tests/src/com/android/messaging/datamodel/BindingTest.java148
-rw-r--r--tests/src/com/android/messaging/datamodel/BitmapPoolTest.java102
-rw-r--r--tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java52
-rw-r--r--tests/src/com/android/messaging/datamodel/ConversationListTest.java28
-rw-r--r--tests/src/com/android/messaging/datamodel/DataModelTest.java71
-rw-r--r--tests/src/com/android/messaging/datamodel/FakeCursor.java161
-rw-r--r--tests/src/com/android/messaging/datamodel/FakeDataModel.java257
-rw-r--r--tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java90
-rw-r--r--tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java43
-rw-r--r--tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java280
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java436
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java275
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionTest.java324
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java191
-rw-r--r--tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java173
-rw-r--r--tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java482
-rw-r--r--tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java99
-rw-r--r--tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java41
-rw-r--r--tests/src/com/android/messaging/datamodel/data/TestDataFactory.java347
-rw-r--r--tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java68
-rw-r--r--tests/src/com/android/messaging/datamodel/media/FakeImageResource.java55
-rw-r--r--tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java36
-rw-r--r--tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java111
-rw-r--r--tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java174
-rw-r--r--tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java37
-rw-r--r--tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java52
-rw-r--r--tests/src/com/android/messaging/ui/BugleActivityTest.java52
-rw-r--r--tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java55
-rw-r--r--tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java61
-rw-r--r--tests/src/com/android/messaging/ui/FakeListViewHolder.java60
-rw-r--r--tests/src/com/android/messaging/ui/FragmentTestCase.java120
-rw-r--r--tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java122
-rw-r--r--tests/src/com/android/messaging/ui/ViewTest.java66
-rw-r--r--tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java172
-rw-r--r--tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java127
-rw-r--r--tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java221
-rw-r--r--tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java184
-rw-r--r--tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java121
-rw-r--r--tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java160
-rw-r--r--tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java190
-rw-r--r--tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java110
-rw-r--r--tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java130
-rw-r--r--tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java181
-rw-r--r--tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java89
-rw-r--r--tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java154
-rw-r--r--tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java145
-rw-r--r--tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java129
-rw-r--r--tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java73
-rw-r--r--tests/src/com/android/messaging/util/BugleGservicesTest.java33
-rw-r--r--tests/src/com/android/messaging/util/ContactUtilTest.java306
-rw-r--r--tests/src/com/android/messaging/util/FakeBugleGservices.java55
-rw-r--r--tests/src/com/android/messaging/util/FakeBuglePrefs.java84
-rw-r--r--tests/src/com/android/messaging/util/FakeMediaUtil.java33
-rw-r--r--tests/src/com/android/messaging/util/YouTubeUtilTest.java62
-rwxr-xr-xtools/buglesql43
-rwxr-xr-xtools/dumpapkversion.sh20
-rwxr-xr-xtools/messagegen/fillsms287
-rwxr-xr-xtools/messagegen/listimages21
-rwxr-xr-xtools/mmssql43
-rw-r--r--version.mk123
1645 files changed, 186271 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..725c89d
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,88 @@
+# 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.
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+ifeq ($(TARGET_BUILD_APPS),)
+ LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
+ LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res
+else
+ LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/appcompat/res
+ LOCAL_RESOURCE_DIR += prebuilts/sdk/current/support/v7/recyclerview/res
+endif
+LOCAL_RESOURCE_DIR += frameworks/opt/chips/res
+LOCAL_RESOURCE_DIR += frameworks/opt/colorpicker/res
+LOCAL_RESOURCE_DIR += frameworks/opt/photoviewer/res
+LOCAL_RESOURCE_DIR += frameworks/opt/photoviewer/activity/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-common
+LOCAL_STATIC_JAVA_LIBRARIES += android-common-framesequence
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-palette
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v13
+LOCAL_STATIC_JAVA_LIBRARIES += com.android.vcard
+LOCAL_STATIC_JAVA_LIBRARIES += guava
+LOCAL_STATIC_JAVA_LIBRARIES += libchips
+LOCAL_STATIC_JAVA_LIBRARIES += libphotoviewer
+LOCAL_STATIC_JAVA_LIBRARIES += libphonenumber
+LOCAL_STATIC_JAVA_LIBRARIES += colorpicker
+
+include $(LOCAL_PATH)/version.mk
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS += --version-name "$(version_name_package)"
+LOCAL_AAPT_FLAGS += --version-code $(version_code_package)
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat
+LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.recyclerview
+LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
+LOCAL_AAPT_FLAGS += --extra-packages com.android.vcard
+LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.photo
+LOCAL_AAPT_FLAGS += --extra-packages com.android.colorpicker
+
+ifdef TARGET_BUILD_APPS
+ LOCAL_JNI_SHARED_LIBRARIES := libframesequence libgiftranscode
+else
+ LOCAL_REQUIRED_MODULES:= libframesequence libgiftranscode
+endif
+
+LOCAL_PROGUARD_FLAGS := -ignorewarnings -include build/core/proguard_basic_keeps.flags
+
+LOCAL_PROGUARD_ENABLED := nosystem
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+ifeq (eng,$(TARGET_BUILD_VARIANT))
+ LOCAL_PROGUARD_FLAG_FILES += proguard-test.flags
+else
+ LOCAL_PROGUARD_FLAG_FILES += proguard-release.flags
+endif
+
+LOCAL_JACK_ENABLED := disabled
+
+LOCAL_PACKAGE_NAME := messaging
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..8fe8fae
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,524 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.messaging"
+ android:installLocation="internalOnly">
+
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
+
+ <!-- Application holds CPU wakelock while working in background -->
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <!-- Application needs SMS/MMS permissions -->
+ <uses-permission android:name="android.permission.READ_SMS"/>
+ <uses-permission android:name="android.permission.WRITE_SMS"/>
+ <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+ <uses-permission android:name="android.permission.RECEIVE_MMS"/>
+ <uses-permission android:name="android.permission.SEND_SMS"/>
+ <!-- Application needs access to MMS network -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <!-- Application needs CONTACT permissions -->
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+ <!-- Application needs to read profiles for the user itself from CP2 -->
+ <uses-permission android:name="android.permission.READ_PROFILE"/>
+ <uses-permission android:name="android.permission.VIBRATE"/>
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+ <!-- Optional features -->
+ <uses-feature android:name="android.hardware.camera" android:required="false" />
+ <uses-feature android:name="android.hardware.camera.front" android:required="false" />
+ <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
+ <uses-feature android:name="android.hardware.microphone" android:required="false" />
+ <uses-feature android:name="android.hardware.screen.portrait" android:required="false" />
+
+ <application
+ android:name="com.android.messaging.BugleApplication"
+ android:allowBackup="false"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/BugleTheme"
+ android:supportsRtl="true">
+
+ <!-- Displays a list of conversations -->
+ <activity
+ android:name=".ui.conversationlist.ConversationListActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/app_name"
+ android:theme="@style/BugleTheme.ConversationListActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.APP_MESSAGING" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".ui.PermissionCheckActivity"
+ android:screenOrientation="portrait"
+ android:configChanges="orientation|screenSize|keyboardHidden" />
+
+ <!-- Launches a conversation (ensures correct app name shown in recents) -->
+ <activity
+ android:name=".ui.conversation.LaunchConversationActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:theme="@style/Invisible"
+ android:noHistory="true"
+ android:documentLaunchMode="always">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.SENDTO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="sms" />
+ <data android:scheme="smsto" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.SENDTO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsto" />
+ </intent-filter>
+ </activity>
+
+ <!-- Displays a list of archived conversations -->
+ <activity
+ android:name=".ui.conversationlist.ArchivedConversationListActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/archived_activity_title"
+ android:theme="@style/BugleTheme.ArchivedConversationListActivity"
+ android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.conversationlist.ConversationListActivity" />
+ </activity>
+
+ <!-- Displays the contents of a single conversation -->
+ <activity
+ android:name=".ui.conversation.ConversationActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:windowSoftInputMode="stateHidden|adjustResize"
+ android:theme="@style/BugleTheme.ConversationActivity"
+ android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.conversationlist.ConversationListActivity" />
+ </activity>
+
+ <!-- Blocked Participants -->
+ <activity
+ android:name=".ui.BlockedParticipantsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/blocked_contacts_title"
+ android:theme="@style/BugleTheme"
+ android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.conversationlist.ConversationListActivity" />
+ </activity>
+
+ <!-- Full-screen photo viewer -->
+ <activity
+ android:name=".ui.photoviewer.BuglePhotoViewActivity"
+ android:label="@string/photo_view_activity_title"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:theme="@style/BuglePhotoViewTheme"
+ />
+
+ <!-- Settings -->
+ <activity
+ android:name=".ui.appsettings.SettingsActivity"
+ android:label="@string/settings_activity_title"
+ android:theme="@style/BugleTheme.SettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.conversationlist.ConversationListActivity" />
+ </activity>
+
+ <activity
+ android:name=".ui.appsettings.PerSubscriptionSettingsActivity"
+ android:label="@string/advanced_settings_activity_title"
+ android:theme="@style/BugleTheme.SettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.appsettings.SettingsActivity" />
+ </activity>
+
+ <activity
+ android:name=".ui.appsettings.ApplicationSettingsActivity"
+ android:label="@string/general_settings_activity_title"
+ android:theme="@style/BugleTheme.SettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.appsettings.SettingsActivity" />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
+ </intent-filter>
+ </activity>
+
+ <!-- Handles sharing intent -->
+ <activity
+ android:name=".ui.conversationlist.ShareIntentActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:theme="@style/BugleTheme.DialogActivity"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="always">
+ <intent-filter
+ android:label="@string/share_intent_label">
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="text/plain" />
+ <data android:mimeType="text/x-vCard" />
+ <data android:mimeType="text/x-vcard" />
+ <data android:mimeType="image/*" />
+ <data android:mimeType="audio/*" />
+ <data android:mimeType="application/ogg" />
+ </intent-filter>
+ <intent-filter
+ android:label="@string/share_intent_label">
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ </intent-filter>
+ </activity>
+
+ <!-- People & Options -->
+ <activity
+ android:name=".ui.conversationsettings.PeopleAndOptionsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/people_and_options_activity_title"
+ android:theme="@style/BugleTheme"
+ android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.conversation.ConversationActivity" />
+ </activity>
+
+ <!-- License -->
+ <activity android:name=".ui.LicenseActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.Holo.Light.Dialog"
+ android:label="@string/menu_license">
+ </activity>
+
+ <!-- Message Forwarding -->
+ <activity
+ android:name=".ui.conversationlist.ForwardMessageActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/forward_message_activity_title"
+ android:theme="@style/BugleTheme.DialogActivity">
+ </activity>
+
+ <!-- Entry point for handling remote input/actions. Currently, this is only used by Android
+ Wear to send voice replies. Since that uses PendingIntents, we don't need to export
+ this activity. If we want other apps to be able to use this activity at will,
+ we'll need to guard it with a signature-matching protected permission. We would also
+ need to add an intent filter and remove the android:exported attribute. -->
+ <activity
+ android:name=".ui.RemoteInputEntrypointActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:exported="false"
+ android:theme="@style/Invisible">
+ </activity>
+
+ <!-- VCard details -->
+ <activity
+ android:name=".ui.VCardDetailActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/vcard_detail_activity_title"
+ android:theme="@style/BugleTheme">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.conversation.ConversationActivity" />
+ </activity>
+
+ <!-- Attachment chooser -->
+ <activity
+ android:name=".ui.attachmentchooser.AttachmentChooserActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/attachment_chooser_activity_title"
+ android:theme="@style/BugleTheme"
+ android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity">
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="com.android.messaging.ui.conversation.ConversationActivity" />
+ </activity>
+
+ <!-- Test activity that we use to host fragments/views. Unfortunately, apparently necessary
+ because Android framework test cases want activity to be in the instrumented package.
+ See http://developer.android.com/reference/android/test/ActivityInstrumentationTestCase2.html
+ -->
+ <activity
+ android:name=".ui.TestActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden">
+ </activity>
+
+ <activity
+ android:name=".ui.debug.DebugMmsConfigActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:theme="@style/BugleTheme.DialogActivity"
+ android:exported="false">
+ </activity>
+
+ <provider android:name=".datamodel.MessagingContentProvider" android:label="@string/app_name"
+ android:authorities="com.android.messaging.datamodel.MessagingContentProvider"
+ android:exported="false" >
+ </provider>
+
+ <provider android:name=".datamodel.MmsFileProvider"
+ android:authorities="com.android.messaging.datamodel.MmsFileProvider"
+ android:grantUriPermissions="true" />
+
+ <provider android:name=".datamodel.MediaScratchFileProvider"
+ android:authorities="com.android.messaging.datamodel.MediaScratchFileProvider"
+ android:grantUriPermissions="true" />
+
+
+ <!-- Action Services -->
+ <service android:name=".datamodel.action.ActionServiceImpl"/>
+ <service android:name=".datamodel.action.BackgroundWorkerService"/>
+
+ <!-- Sms and Mms related items -->
+
+ <!-- Intents for Notification and Pre-KLP Delivery -->
+ <!-- Registered with the highest possible priority (max_int) -->
+ <receiver android:name=".receiver.MmsWapPushReceiver"
+ android:enabled="false"
+ android:permission="android.permission.BROADCAST_WAP_PUSH">
+ <intent-filter android:priority="2147483647">
+ <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".receiver.SmsReceiver"
+ android:enabled="false"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter android:priority="2147483647">
+ <action android:name="android.provider.Telephony.SMS_RECEIVED" />
+ </intent-filter>
+ <intent-filter android:priority="2147483647">
+ <action android:name="android.provider.Telephony.MMS_DOWNLOADED" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Intents for aborting SMS/MMS broadcasts pre-KLP -->
+ <!-- Registered for a priority just ahead of inbox Messaging apps (2) -->
+ <receiver android:name=".receiver.AbortMmsWapPushReceiver"
+ android:enabled="false"
+ android:permission="android.permission.BROADCAST_WAP_PUSH">
+ <intent-filter android:priority="3">
+ <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".receiver.AbortSmsReceiver"
+ android:enabled="false"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter android:priority="3">
+ <action android:name="android.provider.Telephony.SMS_RECEIVED" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Intents for KLP+ Delivery -->
+ <receiver android:name=".receiver.MmsWapPushDeliverReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".receiver.SmsDeliverReceiver"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".receiver.SendStatusReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT" />
+ <data android:scheme="content" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.messaging.receiver.SendStatusReceiver.MMS_SENT" />
+ <data android:scheme="content" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
+ <data android:scheme="content" />
+ </intent-filter>
+ </receiver>
+
+ <service android:name=".datamodel.NoConfirmationSmsSendService"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="sms" />
+ <data android:scheme="smsto" />
+ </intent-filter>
+ </service>
+
+ <activity android:name=".ui.ClassZeroActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:label="@string/class_0_message_activity"
+ android:theme="@style/BugleTheme.DialogActivity"
+ android:launchMode="singleTask"
+ android:excludeFromRecents="true">
+ </activity>
+
+ <activity android:name=".ui.SmsStorageLowWarningActivity"
+ android:theme="@style/Invisible"
+ android:configChanges="orientation|screenSize|keyboardHidden" />
+
+ <activity android:name=".ui.appsettings.ApnSettingsActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:theme="@style/BugleTheme"
+ android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity" />
+
+ <activity android:name=".ui.appsettings.ApnEditorActivity"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:screenOrientation="user"
+ android:theme="@style/BugleTheme"
+ android:parentActivityName="com.android.messaging.ui.appsettings.ApnSettingsActivity"/>
+
+ <receiver android:name=".receiver.StorageStatusReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DEVICE_STORAGE_OK" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".receiver.BootAndPackageReplacedReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
+ </intent-filter>
+ </receiver>
+
+ <!-- Broadcast receiver that will be notified to reset notifications -->
+ <receiver
+ android:name=".receiver.NotificationReceiver"
+ android:exported="false">
+ </receiver>
+
+ <!-- Broadcast receiver that will be notified for ActionService alarms. -->
+ <receiver
+ android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="com.android.messaging.datamodel.PENDING_ACTION" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name=".receiver.DefaultSmsSubscriptionChangeReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED"/>
+ </intent-filter>
+ </receiver>
+
+ <!-- Widget that displays the conversation list -->
+ <receiver android:name=".widget.BugleWidgetProvider"
+ android:label="@string/widget_conversation_name">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.Bugle.intent.action.ACTION_NOTIFY_CONVERSATIONS_CHANGED" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/widget_conversation_list" />
+ </receiver>
+
+ <!-- Widget that displays the messages of a single conversation -->
+ <receiver android:name=".widget.WidgetConversationProvider"
+ android:label="@string/widget_conversation_name">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="com.android.Bugle.intent.action.ACTION_NOTIFY_MESSAGES_CHANGED" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/widget_conversation" />
+ </receiver>
+
+ <service android:name=".widget.WidgetConversationListService"
+ android:permission="android.permission.BIND_REMOTEVIEWS"
+ android:exported="false" />
+
+ <service android:name=".widget.WidgetConversationService"
+ android:permission="android.permission.BIND_REMOTEVIEWS"
+ android:exported="false" />
+
+ <activity android:name=".ui.WidgetPickConversationActivity"
+ android:theme="@style/BugleTheme"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <service android:name="android.support.v7.mms.MmsService"/>
+ </application>
+
+</manifest>
diff --git a/ForceProguard.mk b/ForceProguard.mk
new file mode 100755
index 0000000..e6f4fea
--- /dev/null
+++ b/ForceProguard.mk
@@ -0,0 +1,40 @@
+# 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.
+
+# Check to see if we need to force proguard to re-run, typically after using tapas to
+# switch to/from eng builds. This is determined by comparing the flag files used in the previous
+# build. If the flag files have changed, proguard is rerun.
+
+# If the LOCAL_MODULE being setup isn't a build target, then don't run ForceProguard.
+ifneq (,$(findstring $(LOCAL_MODULE), $(TARGET_BUILD_APPS)))
+
+PREVIOUS_FLAG_FILES_USED_DIR := $(call local-intermediates-dir,1)
+
+# If the local intermediates dir doesn't exist, ForceProguard won't work.
+ifneq ($(wildcard $(PREVIOUS_FLAG_FILES_USED_DIR)),)
+PREVIOUS_FLAG_FILES_USED_FILE := $(PREVIOUS_FLAG_FILES_USED_DIR)/previous_proguard_flag_files
+PREVIOUS_FLAG_FILES_USED := $(if $(wildcard $(PREVIOUS_FLAG_FILES_USED_FILE)), \
+ $(shell cat $(PREVIOUS_FLAG_FILES_USED_FILE)))
+
+ifneq ($(strip $(PREVIOUS_FLAG_FILES_USED)), $(strip $(LOCAL_PROGUARD_FLAG_FILES)))
+$(info *** Flag files used for proguard have changed; forcing proguard to rerun.)
+$(shell touch $(LOCAL_PATH)/proguard.flags)
+$(shell echo $(LOCAL_PROGUARD_FLAG_FILES) > $(PREVIOUS_FLAG_FILES_USED_FILE))
+endif
+
+endif
+# End local intermediates directory existence check
+
+endif
+# End LOCAL_MODULE is a build target check
diff --git a/assets/licenses.html b/assets/licenses.html
new file mode 100644
index 0000000..aa9c3b7
--- /dev/null
+++ b/assets/licenses.html
@@ -0,0 +1,666 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>
+</title>
+<style>
+body { font-family: sans-serif; }
+pre { background-color: #eeeeee; padding: 1em; white-space: pre-wrap; }
+</style>
+</head>
+<body>
+<h3>Notice for AOSP:</h3>
+<pre>
+ Copyright (c) 2005-2008, 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.
+
+ 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.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+</pre>
+
+<h3>Notices for Guava: Google Core Libraries for Java</h3>
+<pre>
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+</pre>
+
+<h3>Notices for JSR 305</h3>
+<pre>
+Copyright (c) 2007-2009, JSR305 expert group
+All rights reserved.
+
+http://www.opensource.org/licenses/bsd-license.php
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the JSR305 expert group nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+<h3>Notices for libphonenumber</h3>
+<pre>
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+</pre>
+
+<h3>Notices for GIFLIB</h3>
+<pre>
+The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+</body></html>
diff --git a/build/README b/build/README
new file mode 100644
index 0000000..3398014
--- /dev/null
+++ b/build/README
@@ -0,0 +1,8 @@
+Files in this folder are build extensions specific to the Bugle project, but currently could be merged into the core Android build.
+
+gcheckstyle is a tool from google3 to validate java from google3. The config file is baked into the jar, but can be updated with a little hackery.
+
+Files last pulled around CL 66008148:
+gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml is the config file which came from /home/build/nonconf/google3/tools/java/checkstyle and was modified to use Android specific styles. It must keep the same name and path in order to correctly update the jar
+
+google-style-checker_deploy.jar is pulled from the build share at /google/data/ro/teams/devtools/glint/linters/live/google-style-checker_deploy.jar and can be pulled from there as needed.
diff --git a/build/android_lint.mk b/build/android_lint.mk
new file mode 100644
index 0000000..3a9a3d7
--- /dev/null
+++ b/build/android_lint.mk
@@ -0,0 +1,59 @@
+# 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.
+
+# Android lint checks for common code/resource errors specific to Android
+
+# Lint tool expects the intermediates dir to match the project name Bugle rather
+# than $(LOCAL_PACKAGE_NAME) which is messaging
+# Create a symbolic link to redirect the tool
+$(LOCAL_PACKAGE_NAME)BUGLE_RENAME := $(subst $(LOCAL_PACKAGE_NAME)_intermediates,Bugle_intermediates,$(intermediates.COMMON))
+$($(LOCAL_PACKAGE_NAME)BUGLE_RENAME): SOURCE_PATH := $(abspath $(intermediates.COMMON)/)
+$($(LOCAL_PACKAGE_NAME)BUGLE_RENAME): DST_PATH := $($(LOCAL_PACKAGE_NAME)BUGLE_RENAME)
+$($(LOCAL_PACKAGE_NAME)BUGLE_RENAME) :
+ ln -f -s $(SOURCE_PATH) $(DST_PATH)
+
+# Lint tool expects api-versions.xml from the SDK to be in development/sdk but
+# it's not. Create a symbolic link to the android SDK to fix it
+API_VERSIONS_XML := $(abspath development/sdk/api-versions.xml)
+$(API_VERSIONS_XML):
+ ln -f -s $(abspath prebuilts/fullsdk/linux/platform-tools/api/api-versions.xml) $(API_VERSIONS_XML)
+
+# The output xml file from the lint tool
+$(LOCAL_PACKAGE_NAME)LINT_XML := $(intermediates.COMMON)/$(LOCAL_PACKAGE_NAME)_android_lint.xml
+
+# The transformed text file from the output xml
+$(LOCAL_PACKAGE_NAME)LINT_TXT := $(intermediates.COMMON)/$(LOCAL_PACKAGE_NAME)_android_lint.txt
+
+# Creates the output xml from the lint tool by running the linting tool if the
+# package has been updated
+$($(LOCAL_PACKAGE_NAME)LINT_XML): PRIVATE_PATH := $(LOCAL_PATH)
+$($(LOCAL_PACKAGE_NAME)LINT_XML): LINT_CMD = $(LINT) --quiet -Wall --disable UnusedIds,UnusedResources,MissingTranslation $(PRIVATE_PATH)
+$($(LOCAL_PACKAGE_NAME)LINT_XML) : $(LOCAL_BUILT_MODULE) $($(LOCAL_PACKAGE_NAME)BUGLE_RENAME) $(API_VERSIONS_XML)
+ $(LINT_CMD) --xml $@ > /dev/null
+
+# Creates the transformed text file from the output xml by running an xslt on it
+# which filters out issues from the support library and formats it for console
+# output
+$($(LOCAL_PACKAGE_NAME)LINT_TXT): PRIVATE_PATH := $(LOCAL_PATH)
+$($(LOCAL_PACKAGE_NAME)LINT_TXT): INPUT := $($(LOCAL_PACKAGE_NAME)LINT_XML)
+$($(LOCAL_PACKAGE_NAME)LINT_TXT): XSLT_CMD = xsltproc $(PRIVATE_PATH)/build/android_lint.xslt $(INPUT)
+$($(LOCAL_PACKAGE_NAME)LINT_TXT) : $($(LOCAL_PACKAGE_NAME)LINT_XML)
+ $(hide) $(XSLT_CMD) > $@
+
+# The root of the lint rule which just prints the lint errors txt file to the
+# console
+$(LOCAL_PACKAGE_NAME)lint: PRIVATE_PATH := $(LOCAL_PATH)
+$(LOCAL_PACKAGE_NAME)lint :: $($(LOCAL_PACKAGE_NAME)LINT_TXT)
+ $(hide) $(PRIVATE_PATH)/build/colorize_errors.py < $<
+
diff --git a/build/android_lint.xslt b/build/android_lint.xslt
new file mode 100644
index 0000000..d0ddc1c
--- /dev/null
+++ b/build/android_lint.xslt
@@ -0,0 +1,40 @@
+<!--
+ 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.
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text" version="1.0" encoding="UTF-8" omit-xml-declaration="yes"/>
+<xsl:template match="/issues">
+ <xsl:for-each select="issue">
+ <!-- Exclude errors/warnings with
+ /android/support in the location, these are outside our control
+ /com/google/common in the location, these are outside our control
+ res/values- in the location, these are localized resources, and we only need to be notified of the neutral resource issues
+ .class in the location, these don't have source we can do anything about -->
+ <xsl:if test="not(./location) or (not(contains(./location/@file, '/android/support/')) and not(contains(./location/@file, '/com/google/common/')) and not(starts-with(./location/@file, 'res/values-')) and not(contains(./location/@file, '.class')))">
+ <xsl:value-of select="@severity" />: <xsl:value-of select="@message" disable-output-escaping="yes"/><xsl:text> </xsl:text>[<xsl:value-of select="@id" />]<xsl:text>&#xa;</xsl:text>
+ <xsl:for-each select="./location">
+ <xsl:text> </xsl:text><xsl:value-of select="@file" />
+ <xsl:if test="@line">
+ <xsl:text>:</xsl:text><xsl:value-of select="@line" />
+ <xsl:if test="@column">
+ <xsl:text>,</xsl:text><xsl:value-of select="@column" />
+ </xsl:if>
+ </xsl:if>
+ <xsl:text>&#xa;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:for-each>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/build/colorize_errors.py b/build/colorize_errors.py
new file mode 100755
index 0000000..e33edc0
--- /dev/null
+++ b/build/colorize_errors.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+# 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.
+#
+# Given an input stream look for Error: and Warning: lines and colorize those to
+# the output
+
+import fileinput
+import re
+import sys
+
+RED = "\033[31m"
+YELLOW = "\033[33m"
+RESET = "\033[0m"
+
+ERROR = re.compile(r"^Error:")
+WARNING = re.compile(r"^Warning:")
+STARTS_WITH_WS = re.compile(r"^\s")
+
+for line in fileinput.input():
+ if ERROR.match(line):
+ print RED + line,
+ elif WARNING.match(line):
+ print YELLOW + line,
+ elif STARTS_WITH_WS.match(line):
+ # If the line starts with a space use the same coloring as the previous line
+ print line,
+ else:
+ print RESET + line,
+print RESET
+
diff --git a/build/gcheckstyle.mk b/build/gcheckstyle.mk
new file mode 100644
index 0000000..61890ee
--- /dev/null
+++ b/build/gcheckstyle.mk
@@ -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.
+
+# The config file to use when checking style issues
+STYLE_CONFIG := $(LOCAL_PATH)/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml
+
+# The jar file to use to perform the checking
+STYLE_JAR := $(LOCAL_PATH)/build/gcheckstyle/google-style-checker_deploy.jar
+
+# The output file to cache the results of style error checking
+$(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT := $(intermediates.COMMON)/$(LOCAL_PACKAGE_NAME)_style_errors.txt
+
+# The set of input files to check
+$(LOCAL_PACKAGE_NAME)JAVA_FILES = $(shell find $(LOCAL_PATH)/src/ -name '*.java')
+
+# Updates the JAR file with the new config if the config has changed
+# The config file has to be packaged into the jar, and jar uf command expects the current working directory
+# to match the jar structure
+$(STYLE_JAR): PRIVATE_PATH := $(LOCAL_PATH)
+$(STYLE_JAR) : $(STYLE_CONFIG)
+ $(hide) pushd $(PRIVATE_PATH)/build/gcheckstyle && \
+ jar uf google-style-checker_deploy.jar tools/java/checkstyle/googlestyle-5.0.xml && \
+ popd
+
+# Rebuilds the style errors text if the style checker or any of the java files have changed
+# FLAG: It may be more efficient to cache individual file results rather than grouping them all together
+$($(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT): SOURCES := $($(LOCAL_PACKAGE_NAME)JAVA_FILES)
+$($(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT) : $($(LOCAL_PACKAGE_NAME)JAVA_FILES) $(STYLE_JAR)
+ $(hide) -/usr/local/buildtools/java/jdk/bin/java -jar $(STYLE_JAR) $(SOURCES) > $@
+
+# The root of the lint rule which just prints the style errors txt file to the console
+$(LOCAL_PACKAGE_NAME)lint :: $($(LOCAL_PACKAGE_NAME)STYLE_ERRORS_TXT)
+ $(hide) $(PRIVATE_PATH)/build/process_style_output.py -omit $(abspath $(PRIVATE_PATH))/ < $< | $(PRIVATE_PATH)/build/colorize_errors.py
+
diff --git a/build/gcheckstyle/google-style-checker_deploy.jar b/build/gcheckstyle/google-style-checker_deploy.jar
new file mode 100644
index 0000000..86afcbc
--- /dev/null
+++ b/build/gcheckstyle/google-style-checker_deploy.jar
Binary files differ
diff --git a/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml b/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml
new file mode 100644
index 0000000..6960849
--- /dev/null
+++ b/build/gcheckstyle/tools/java/checkstyle/googlestyle-5.0.xml
@@ -0,0 +1,411 @@
+<?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.
+-->
+<!DOCTYPE module PUBLIC
+ "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!-- This is a checkstyle configuration file. For descriptions of
+what the following rules do, please see the checkstyle configuration
+page at http://checkstyle.sourceforge.net/config.html -->
+
+<!-- Checks with numbered comments refer to recommendations made
+by Joshua Bloch in his book Effective Java -->
+
+<module name="Checker">
+ <property name="charset" value="UTF-8"/>
+ <module name="SuppressionCommentFilter"/>
+ <module name="SuppressionCommentFilter">
+ <property name="offCommentFormat" value="__Generated_"/>
+ <property name="onCommentFormat" value="END GENERATED CODE"/>
+ </module>
+ <module name="FileTabCharacter">
+ <!-- Checks that there are no tab characters in the file.
+ -->
+ </module>
+
+ <module name="RegexpSingleline">
+ <!-- Checks that FIXME is not used in comments. TODO is preferred.
+ -->
+ <property name="format" value="((//.*)|(\*.*))FIXME" />
+ <property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
+ </module>
+
+ <module name="RegexpSingleline">
+ <!-- Checks that TODOs are properly formatted.
+
+ The (?&lt;!TODO\(.{0,100}) makes the regex ignore any secondary TODO's on the line
+ so that things like //TODO(bob): remove this TODO on 1/1/2020 don't trigger a warning
+ because of the second TODO. (The {0,100} is because java doesn't recoginize arbitrary
+ length look backs, but we know each java line should be < 100 chars.)
+ -->
+ <property name="format" value="((//.*)|(\*.*))(?&lt;!TODO\(.{0,100})(TODO[^(])|(TODO\([^)]*$)" />
+ <property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
+ </module>
+
+
+ <!-- All Java AST specific tests live under TreeWalker module. -->
+ <module name="TreeWalker">
+ <module name="FileContentsHolder"/>
+ <!--module name="SuppressWarningsFilter" /-->
+ <!--module name="SuppressWarningsHolder" /-->
+ <!--
+
+ IMPORT CHECKS
+
+ -->
+
+ <module name="RedundantImport">
+ <property name="severity" value="error"/>
+ </module>
+
+ <module name="AvoidStarImport">
+ <property name="severity" value="error"/>
+ </module>
+
+ <module name="UnusedImports">
+ <!-- DPL is a notable violator of this rule. -->
+ <property name="severity" value="error"/>
+ <!-- Imports used only in Javadoc are tolerated. See http://b/838496 -->
+ <property name="processJavadoc" value="true"/>
+ <message
+ key="import.unused"
+ value="Unused import: {0}." />
+ </module>
+
+ <module name="checkstyle.patches.checks.imports.ImportOrder">
+ <!-- Checks for out of order import statements. -->
+
+ <metadata name="altname" value="ImportOrder"/>
+ <property name="severity" value="warning"/>
+ <property name="groups" value="android,com.android,com.google,*,java,javax"/>
+ <!-- This ensures that static imports go first. -->
+ <property name="option" value="top"/>
+ <property name="tokens" value="STATIC_IMPORT, IMPORT"/>
+ <message
+ key="import.ordering"
+ value="Wrong order for {0} import." />
+ </module>
+
+ <!-- Checks that nested classes are not statically imported. -->
+ <module name="com.google.devtools.checkstyle.checks.imports.StaticImportNestedClassCheck">
+ <metadata name="altname" value="StaticImportNestedClass"/>
+ <property name="severity" value="warning"/>
+ <message key="import.staticNestedClass"
+ value="Use of static import for nested classes is disallowed."/>
+ </module>
+
+ <!--
+
+ JAVADOC CHECKS
+
+ -->
+
+ <module name="com.google.devtools.checkstyle.checks.javadoc.GoogleJavadocTypeCheck">
+ <!-- Item 28 - Write doc comments for all exposed API elements. -->
+ <!-- Ensure all classes with visability greater than or equal to
+ protected have class level documentation. -->
+ <property name="scope" value="protected"/>
+ <property name="severity" value="ignore"/>
+ <!-- Style guide doesn't prohibit custom tags. Typos will be caught by other tools. -->
+ <property name="allowUnknownTags" value="true"/>
+ <property name="allowMissingParamTags" value="true"/>
+ </module>
+
+ <!--
+
+ NAMING CHECKS
+
+ -->
+
+ <!-- Item 38 - Adhere to generally accepted naming conventions -->
+
+ <module name="PackageName">
+ <!-- Validates identifiers for package names against the
+ supplied expression. -->
+ <!-- Here the default checkstyle rule restricts package name parts to
+ seven characters, this is not in line with common practice at Google.
+ -->
+ <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
+ <property name="severity" value="warning"/>
+ </module>
+
+ <module name="TypeNameCheck">
+ <!-- Validates static, final fields against the
+ expression "^[A-Z][a-zA-Z0-9]*$". -->
+ <metadata name="altname" value="TypeName"/>
+ <property name="severity" value="warning"/>
+ </module>
+
+ <module name="com.google.devtools.checkstyle.checks.coding.ConstantNameCheck">
+ <!-- Validates that constant fields are named in ALL_CAPS. -->
+ <metadata name="altname" value="ConstantName"/>
+ <property name="applyToPublic" value="true"/>
+ <property name="applyToProtected" value="true"/>
+ <property name="applyToPackage" value="true"/>
+ <property name="applyToPrivate" value="true"/>
+ <property name="format" value="^_?[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
+ <message key="name.invalidPattern"
+ value="Variable ''{0}'' is a constant and thus should be in ALL_CAPS."/>
+ <property name="severity" value="ignore"/>
+ </module>
+
+ <module name="StaticVariableNameCheck">
+ <!-- Validates static, non-final fields against the supplied
+ expression "^[a-z][a-zA-Z0-9]*_?$". -->
+ <metadata name="altname" value="StaticVariableName"/>
+ <property name="applyToPublic" value="true"/>
+ <property name="applyToProtected" value="true"/>
+ <property name="applyToPackage" value="true"/>
+ <property name="applyToPrivate" value="true"/>
+ <property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
+ <property name="severity" value="ignore"/>
+ </module>
+
+ <module name="MemberNameCheck">
+ <!-- Validates non-static members against the supplied expression. -->
+ <metadata name="altname" value="MemberName"/>
+ <property name="applyToPublic" value="true"/>
+ <property name="applyToProtected" value="true"/>
+ <property name="applyToPackage" value="true"/>
+ <property name="applyToPrivate" value="true"/>
+ <!-- allows for googles deprecated foo_ member naming scheme -->
+ <property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
+ <property name="severity" value="ignore"/>
+ </module>
+
+ <module name="MethodNameCheck">
+ <!-- Validates identifiers for method names. -->
+ <metadata name="altname" value="MethodName"/>
+ <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
+ <property name="severity" value="warning"/>
+ </module>
+
+ <module name="ParameterName">
+ <!-- Validates identifiers for method parameters against the
+ expression "^[a-z][a-zA-Z0-9]*$". -->
+ <property name="severity" value="warning"/>
+ </module>
+
+ <module name="LocalFinalVariableName">
+ <!-- Validates identifiers for local final variables against the
+ expression "^[a-z][a-zA-Z0-9]*$". -->
+ <property name="severity" value="warning"/>
+ </module>
+
+ <module name="LocalVariableName">
+ <!-- Validates identifiers for local variables against the
+ expression "^[a-z][a-zA-Z0-9]*$". -->
+ <property name="severity" value="warning"/>
+ </module>
+
+
+ <!--
+
+ LENGTH and CODING CHECKS
+
+ -->
+
+ <module name="LineLength">
+ <!-- Checks if a line is too long. -->
+ <property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="100"/>
+ <property name="severity" value="error"/>
+
+ <!--
+ The default ignore pattern exempts the following elements:
+ - import statements
+ - long URLs inside comments
+ - certain JSNI method signatures used in GWT, see http://wiki/Nonconf/GwtInGoogle3Faq#JsniLineBreaks.
+ -->
+
+ <!--
+ The JSNI/GWT exemption only is for the JNI-style signature, that cannot be split across multiple lines.
+ Both the instance expression and the parameter list may and must go on separate lines.
+
+ These examples are exempt:
+ <pre>
+ private native void foo() /*-{
+ // Method call, signature as the only long line (> 100 chars)
+ this.
+ @com.google.gwt.foo.bar.baz.ClassUsingJsni::someMethod(Lcom/google/gwt/foo/bar/baz/ParameterType;)
+ (param);
+
+ // Static method call, signature as the only long line (> 100 chars)
+ @com.google.gwt.foo.bar.baz.ClassUsingJsni::staticMethod(Lcom/google/gwt/foo/bar/baz/ParameterType;)
+ (param);
+
+ // Method reference in dictionary, signature as the only long line
+ var dict = {
+ key:
+ @com.google.gwt.foo.bar.baz.ClassUsingJsni::otherMethod(Lcom/google/gwt/foo/bar/baz/ParameterType;Z),
+ }
+ }-*/;
+ </pre>
+
+ These exampls are NOT exempt, as the line can be broken further:
+ <pre>
+ private native void foo() /*-{
+ // Instance expression on same line
+ this.@com.google.gwt.foo.bar.baz.ClassUsingJsni::someMethod(Lcom/google/gwt/foo/bar/baz/ParameterType;)
+ (param);
+
+ // Parameters on same line
+ this.
+ @com.google.gwt.foo.bar.baz.ClassUsingJsni::someMethod(Lcom/google/gwt/foo/bar/baz/ParameterType;)(param);
+
+ // All on one line
+ this.@com.google.gwt.foo.bar.baz.ClassUsingJsni::someMethod(Lcom/google/gwt/foo/bar/baz/ParameterType;)(param);
+ }-*/;
+ </pre>
+ -->
+ <property name="ignorePattern"
+ value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
+ default="^(package .*;\s*)|(import .*;\s*)|( *\* *https?://.*)|(\s*@[\w\.\$]+::\w+(?:\([^\(]*\)|\(\)\(\))?[,;]?)|(\s+\* \{@(link|see) [^\s][^\}]*\}[\.,;]?)$"/>
+ </module>
+
+ <module name="LeftCurly">
+ <!-- Checks for placement of the left curly brace ('{'). -->
+ <property name="severity" value="warning"/>
+ </module>
+
+ <module name="RightCurly">
+ <!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
+ the same line. e.g., the following example is fine:
+ <pre>
+ if {
+ ...
+ } else
+ </pre>
+ -->
+ <!-- This next example is not fine:
+ <pre>
+ if {
+ ...
+ }
+ else
+ </pre>
+ -->
+ <property name="option" value="same"/>
+ <property name="severity" value="warning"/>
+ </module>
+
+ <!-- Checks for braces around if and else blocks -->
+ <module name="NeedBraces">
+ <property name="severity" value="warning"/>
+ <property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
+ </module>
+
+ <!-- Checks for empty catch blocks in non-test files. -->
+ <module name="com.google.devtools.checkstyle.checks.blocks.EmptyCatchBlockCheck">
+ <metadata name="altname" value="EmptyCatchBlock"/>
+ <property name="severity" value="error"/>
+ </module>
+
+ <module name="UpperEll">
+ <!-- Checks that long constants are defined with an upper ell.-->
+ <property name="severity" value="error"/>
+ </module>
+
+ <module name="FallThrough">
+ <!-- Warn about falling through to the next case statement. Similar to
+ javac -Xlint:fallthrough, but the check is suppressed if there is a single-line comment
+ on the last non-blank line preceding the fallen-into case.
+ -->
+ <property name="reliefPattern"
+ value=".*"/>
+ <property name="severity" value="error"/>
+ </module>
+
+
+ <!--
+
+ MODIFIERS CHECKS
+
+ -->
+
+ <module name="ModifierOrder">
+ <!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
+ 8.4.3. The prescribed order is:
+ public, protected, private, abstract, static, final, transient, volatile,
+ synchronized, native, strictfp
+ -->
+ </module>
+
+
+ <!--
+
+ WHITESPACE CHECKS
+
+ -->
+
+ <module name="WhitespaceAround">
+ <!-- Checks that various tokens are surrounded by whitespace.
+ This includes most binary operators and keywords followed
+ by regular or curly braces.
+ -->
+ <property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
+ BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
+ EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
+ LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
+ LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
+ MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
+ SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
+ <property name="severity" value="error"/>
+ <property name="ignoreEnhancedForColon" value="false"/>
+ </module>
+
+ <module name="WhitespaceAfter">
+ <!-- Checks that commas, semicolons and typecasts are followed by
+ whitespace.
+ -->
+ <property name="tokens" value="COMMA, SEMI, TYPECAST"/>
+ </module>
+
+ <module name="NoWhitespaceAfter">
+ <!-- Checks that there is no whitespace after various unary operators.
+ Linebreaks are allowed.
+ -->
+ <property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
+ UNARY_PLUS"/>
+ <property name="allowLineBreaks" value="true"/>
+ <property name="severity" value="error"/>
+ </module>
+
+ <module name="NoWhitespaceBefore">
+ <!-- Checks that there is no whitespace before various unary operators.
+ Linebreaks are allowed.
+ -->
+ <property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
+ <property name="allowLineBreaks" value="true"/>
+ <property name="severity" value="error"/>
+ </module>
+
+ <module name="ParenPad">
+ <!-- Checks that there is no whitespace before close parens or after
+ open parens.
+ -->
+ <property name="severity" value="warning"/>
+ </module>
+
+ <!--
+
+ MISC CHECKS
+
+ -->
+
+ </module>
+</module>
+
diff --git a/build/process_style_output.py b/build/process_style_output.py
new file mode 100755
index 0000000..63ca9a0
--- /dev/null
+++ b/build/process_style_output.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+# 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.
+#
+# Take the output from gstylechecker and clean up into a more friendly format
+
+import argparse
+import fileinput
+import re
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-omit')
+args = parser.parse_args()
+
+PATTERN = re.compile(r"(?P<file>[^:]+):(?P<line>\d+)(?P<column>:\d+)?:\s(?P<message>.*)")
+
+for line in fileinput.input([]):
+ match = PATTERN.match(line)
+ if match:
+ filename = match.group("file");
+ if args.omit:
+ filename = filename.replace(args.omit, "")
+ message = match.group("message")
+ message = message[0].upper() + message[1:]
+ print message
+ print " " + filename + ':' + match.group("line") + (match.group("column") if match.group("column") else "")
+
diff --git a/jni/Android.mk b/jni/Android.mk
new file mode 100644
index 0000000..cd89b8f
--- /dev/null
+++ b/jni/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_STATIC_LIBRARIES += libgif
+
+# Link to Android logging (liblog.so) and dynamic linker (libdl.so) libraries
+LOCAL_LDFLAGS := -llog -ldl
+
+LOCAL_C_INCLUDES := \
+ external/giflib
+
+LOCAL_MODULE := libgiftranscode
+LOCAL_SRC_FILES := GifTranscoder.cpp
+
+LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wno-switch
+
+LOCAL_SDK_VERSION := 8
+LOCAL_NDK_STL_VARIANT := c++_static # LLVM libc++
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/GifTranscoder.cpp b/jni/GifTranscoder.cpp
new file mode 100644
index 0000000..44fa30c
--- /dev/null
+++ b/jni/GifTranscoder.cpp
@@ -0,0 +1,573 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <time.h>
+#include <stdio.h>
+#include <memory>
+#include <vector>
+
+#include <android/log.h>
+
+#include "GifTranscoder.h"
+
+#define SQUARE(a) (a)*(a)
+
+// GIF does not support partial transparency, so our alpha channels are always 0x0 or 0xff.
+static const ColorARGB TRANSPARENT = 0x0;
+
+#define ALPHA(color) (((color) >> 24) & 0xff)
+#define RED(color) (((color) >> 16) & 0xff)
+#define GREEN(color) (((color) >> 8) & 0xff)
+#define BLUE(color) (((color) >> 0) & 0xff)
+
+#define MAKE_COLOR_ARGB(a, r, g, b) \
+ ((a) << 24 | (r) << 16 | (g) << 8 | (b))
+
+#define MAX_COLOR_DISTANCE 255 * 255 * 255
+
+#define TAG "GifTranscoder.cpp"
+#define LOGD_ENABLED 0
+#if LOGD_ENABLED
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
+#else
+#define LOGD(...) ((void)0)
+#endif
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
+
+// This macro expects the assertion to pass, but logs a FATAL if not.
+#define ASSERT(cond, ...) \
+ ( (__builtin_expect((cond) == 0, 0)) \
+ ? ((void)__android_log_assert(#cond, TAG, ## __VA_ARGS__)) \
+ : (void) 0 )
+#define ASSERT_ENABLED 1
+
+namespace {
+
+// Current time in milliseconds since Unix epoch.
+double now(void) {
+ struct timespec res;
+ clock_gettime(CLOCK_REALTIME, &res);
+ return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
+}
+
+// Gets the pixel at position (x,y) from a buffer that uses row-major order to store an image with
+// the specified width.
+template <typename T>
+T* getPixel(T* buffer, int width, int x, int y) {
+ return buffer + (y * width + x);
+}
+
+} // namespace
+
+int GifTranscoder::transcode(const char* pathIn, const char* pathOut) {
+ int error;
+ double t0;
+ GifFileType* gifIn;
+ GifFileType* gifOut;
+
+ // Automatically closes the GIF files when this method returns
+ GifFilesCloser closer;
+
+ gifIn = DGifOpenFileName(pathIn, &error);
+ if (gifIn) {
+ closer.setGifIn(gifIn);
+ LOGD("Opened input GIF: %s", pathIn);
+ } else {
+ LOGE("Could not open input GIF: %s, error = %d", pathIn, error);
+ return GIF_ERROR;
+ }
+
+ gifOut = EGifOpenFileName(pathOut, false, &error);
+ if (gifOut) {
+ closer.setGifOut(gifOut);
+ LOGD("Opened output GIF: %s", pathOut);
+ } else {
+ LOGE("Could not open output GIF: %s, error = %d", pathOut, error);
+ return GIF_ERROR;
+ }
+
+ t0 = now();
+ if (resizeBoxFilter(gifIn, gifOut)) {
+ LOGD("Resized GIF in %.2f ms", now() - t0);
+ } else {
+ LOGE("Could not resize GIF");
+ return GIF_ERROR;
+ }
+
+ return GIF_OK;
+}
+
+bool GifTranscoder::resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut) {
+ ASSERT(gifIn != NULL, "gifIn cannot be NULL");
+ ASSERT(gifOut != NULL, "gifOut cannot be NULL");
+
+ if (gifIn->SWidth < 0 || gifIn->SHeight < 0) {
+ LOGE("Input GIF has invalid size: %d x %d", gifIn->SWidth, gifIn->SHeight);
+ return false;
+ }
+
+ // Output GIF will be 50% the size of the original.
+ if (EGifPutScreenDesc(gifOut,
+ gifIn->SWidth / 2,
+ gifIn->SHeight / 2,
+ gifIn->SColorResolution,
+ gifIn->SBackGroundColor,
+ gifIn->SColorMap) == GIF_ERROR) {
+ LOGE("Could not write screen descriptor");
+ return false;
+ }
+ LOGD("Wrote screen descriptor");
+
+ // Index of the current image.
+ int imageIndex = 0;
+
+ // Transparent color of the current image.
+ int transparentColor = NO_TRANSPARENT_COLOR;
+
+ // Buffer for reading raw images from the input GIF.
+ std::vector<GifByteType> srcBuffer(gifIn->SWidth * gifIn->SHeight);
+
+ // Buffer for rendering images from the input GIF.
+ std::unique_ptr<ColorARGB> renderBuffer(new ColorARGB[gifIn->SWidth * gifIn->SHeight]);
+
+ // Buffer for writing new images to output GIF (one row at a time).
+ std::unique_ptr<GifByteType> dstRowBuffer(new GifByteType[gifOut->SWidth]);
+
+ // Many GIFs use DISPOSE_DO_NOT to make images draw on top of previous images. They can also
+ // use DISPOSE_BACKGROUND to clear the last image region before drawing the next one. We need
+ // to keep track of the disposal mode as we go along to properly render the GIF.
+ int disposalMode = DISPOSAL_UNSPECIFIED;
+ int prevImageDisposalMode = DISPOSAL_UNSPECIFIED;
+ GifImageDesc prevImageDimens;
+
+ // Background color (applies to entire GIF).
+ ColorARGB bgColor = TRANSPARENT;
+
+ GifRecordType recordType;
+ do {
+ if (DGifGetRecordType(gifIn, &recordType) == GIF_ERROR) {
+ LOGE("Could not get record type");
+ return false;
+ }
+ LOGD("Read record type: %d", recordType);
+ switch (recordType) {
+ case IMAGE_DESC_RECORD_TYPE: {
+ if (DGifGetImageDesc(gifIn) == GIF_ERROR) {
+ LOGE("Could not read image descriptor (%d)", imageIndex);
+ return false;
+ }
+
+ // Sanity-check the current image position.
+ if (gifIn->Image.Left < 0 ||
+ gifIn->Image.Top < 0 ||
+ gifIn->Image.Left + gifIn->Image.Width > gifIn->SWidth ||
+ gifIn->Image.Top + gifIn->Image.Height > gifIn->SHeight) {
+ LOGE("GIF image extends beyond logical screen");
+ return false;
+ }
+
+ // Write the new image descriptor.
+ if (EGifPutImageDesc(gifOut,
+ 0, // Left
+ 0, // Top
+ gifOut->SWidth,
+ gifOut->SHeight,
+ false, // Interlace
+ gifIn->Image.ColorMap) == GIF_ERROR) {
+ LOGE("Could not write image descriptor (%d)", imageIndex);
+ return false;
+ }
+
+ // Read the image from the input GIF. The buffer is already initialized to the
+ // size of the GIF, which is usually equal to the size of all the images inside it.
+ // If not, the call to resize below ensures that the buffer is the right size.
+ srcBuffer.resize(gifIn->Image.Width * gifIn->Image.Height);
+ if (readImage(gifIn, srcBuffer.data()) == false) {
+ LOGE("Could not read image data (%d)", imageIndex);
+ return false;
+ }
+ LOGD("Read image data (%d)", imageIndex);
+ // Render the image from the input GIF.
+ if (renderImage(gifIn,
+ srcBuffer.data(),
+ imageIndex,
+ transparentColor,
+ renderBuffer.get(),
+ bgColor,
+ prevImageDimens,
+ prevImageDisposalMode) == false) {
+ LOGE("Could not render %d", imageIndex);
+ return false;
+ }
+ LOGD("Rendered image (%d)", imageIndex);
+
+ // Generate the image in the output GIF.
+ for (int y = 0; y < gifOut->SHeight; y++) {
+ for (int x = 0; x < gifOut->SWidth; x++) {
+ const GifByteType dstColorIndex = computeNewColorIndex(
+ gifIn, transparentColor, renderBuffer.get(), x, y);
+ *(dstRowBuffer.get() + x) = dstColorIndex;
+ }
+ if (EGifPutLine(gifOut, dstRowBuffer.get(), gifOut->SWidth) == GIF_ERROR) {
+ LOGE("Could not write raster data (%d)", imageIndex);
+ return false;
+ }
+ }
+ LOGD("Wrote raster data (%d)", imageIndex);
+
+ // Save the disposal mode for rendering the next image.
+ // We only support DISPOSE_DO_NOT and DISPOSE_BACKGROUND.
+ prevImageDisposalMode = disposalMode;
+ if (prevImageDisposalMode == DISPOSAL_UNSPECIFIED) {
+ prevImageDisposalMode = DISPOSE_DO_NOT;
+ } else if (prevImageDisposalMode == DISPOSE_PREVIOUS) {
+ prevImageDisposalMode = DISPOSE_BACKGROUND;
+ }
+ if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
+ prevImageDimens.Left = gifIn->Image.Left;
+ prevImageDimens.Top = gifIn->Image.Top;
+ prevImageDimens.Width = gifIn->Image.Width;
+ prevImageDimens.Height = gifIn->Image.Height;
+ }
+
+ if (gifOut->Image.ColorMap) {
+ GifFreeMapObject(gifOut->Image.ColorMap);
+ gifOut->Image.ColorMap = NULL;
+ }
+
+ imageIndex++;
+ } break;
+ case EXTENSION_RECORD_TYPE: {
+ int extCode;
+ GifByteType* ext;
+ if (DGifGetExtension(gifIn, &extCode, &ext) == GIF_ERROR) {
+ LOGE("Could not read extension block");
+ return false;
+ }
+ LOGD("Read extension block, code: %d", extCode);
+ if (extCode == GRAPHICS_EXT_FUNC_CODE) {
+ GraphicsControlBlock gcb;
+ if (DGifExtensionToGCB(ext[0], ext + 1, &gcb) == GIF_ERROR) {
+ LOGE("Could not interpret GCB extension");
+ return false;
+ }
+ transparentColor = gcb.TransparentColor;
+
+ // This logic for setting the background color based on the first GCB
+ // doesn't quite match the GIF spec, but empirically it seems to work and it
+ // matches what libframesequence (Rastermill) does.
+ if (imageIndex == 0 && gifIn->SColorMap) {
+ if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
+ GifColorType bgColorIndex =
+ gifIn->SColorMap->Colors[gifIn->SBackGroundColor];
+ bgColor = gifColorToColorARGB(bgColorIndex);
+ LOGD("Set background color based on first GCB");
+ }
+ }
+
+ // Record the original disposal mode and then update it.
+ disposalMode = gcb.DisposalMode;
+ gcb.DisposalMode = DISPOSE_BACKGROUND;
+ EGifGCBToExtension(&gcb, ext + 1);
+ }
+ if (EGifPutExtensionLeader(gifOut, extCode) == GIF_ERROR) {
+ LOGE("Could not write extension leader");
+ return false;
+ }
+ if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
+ LOGE("Could not write extension block");
+ return false;
+ }
+ LOGD("Wrote extension block");
+ while (ext != NULL) {
+ if (DGifGetExtensionNext(gifIn, &ext) == GIF_ERROR) {
+ LOGE("Could not read extension continuation");
+ return false;
+ }
+ if (ext != NULL) {
+ LOGD("Read extension continuation");
+ if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
+ LOGE("Could not write extension continuation");
+ return false;
+ }
+ LOGD("Wrote extension continuation");
+ }
+ }
+ if (EGifPutExtensionTrailer(gifOut) == GIF_ERROR) {
+ LOGE("Could not write extension trailer");
+ return false;
+ }
+ } break;
+ }
+
+ } while (recordType != TERMINATE_RECORD_TYPE);
+ LOGD("No more records");
+
+ return true;
+}
+
+bool GifTranscoder::readImage(GifFileType* gifIn, GifByteType* rasterBits) {
+ if (gifIn->Image.Interlace) {
+ int interlacedOffset[] = { 0, 4, 2, 1 };
+ int interlacedJumps[] = { 8, 8, 4, 2 };
+
+ // Need to perform 4 passes on the image
+ for (int i = 0; i < 4; i++) {
+ for (int j = interlacedOffset[i]; j < gifIn->Image.Height; j += interlacedJumps[i]) {
+ if (DGifGetLine(gifIn,
+ rasterBits + j * gifIn->Image.Width,
+ gifIn->Image.Width) == GIF_ERROR) {
+ LOGE("Could not read interlaced raster data");
+ return false;
+ }
+ }
+ }
+ } else {
+ if (DGifGetLine(gifIn, rasterBits, gifIn->Image.Width * gifIn->Image.Height) == GIF_ERROR) {
+ LOGE("Could not read raster data");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool GifTranscoder::renderImage(GifFileType* gifIn,
+ GifByteType* rasterBits,
+ int imageIndex,
+ int transparentColorIndex,
+ ColorARGB* renderBuffer,
+ ColorARGB bgColor,
+ GifImageDesc prevImageDimens,
+ int prevImageDisposalMode) {
+ ASSERT(imageIndex < gifIn->ImageCount,
+ "Image index %d is out of bounds (count=%d)", imageIndex, gifIn->ImageCount);
+
+ ColorMapObject* colorMap = getColorMap(gifIn);
+ if (colorMap == NULL) {
+ LOGE("No GIF color map found");
+ return false;
+ }
+
+ // Clear all or part of the background, before drawing the first image and maybe before drawing
+ // subsequent images (depending on the DisposalMode).
+ if (imageIndex == 0) {
+ fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
+ 0, 0, gifIn->SWidth, gifIn->SHeight, bgColor);
+ } else if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
+ fillRect(renderBuffer, gifIn->SWidth, gifIn->SHeight,
+ prevImageDimens.Left, prevImageDimens.Top,
+ prevImageDimens.Width, prevImageDimens.Height, TRANSPARENT);
+ }
+
+ // Paint this image onto the canvas
+ for (int y = 0; y < gifIn->Image.Height; y++) {
+ for (int x = 0; x < gifIn->Image.Width; x++) {
+ GifByteType colorIndex = *getPixel(rasterBits, gifIn->Image.Width, x, y);
+
+ // This image may be smaller than the GIF's "logical screen"
+ int renderX = x + gifIn->Image.Left;
+ int renderY = y + gifIn->Image.Top;
+
+ // Skip drawing transparent pixels if this image renders on top of the last one
+ if (imageIndex > 0 && prevImageDisposalMode == DISPOSE_DO_NOT &&
+ colorIndex == transparentColorIndex) {
+ continue;
+ }
+
+ ColorARGB* renderPixel = getPixel(renderBuffer, gifIn->SWidth, renderX, renderY);
+ *renderPixel = getColorARGB(colorMap, transparentColorIndex, colorIndex);
+ }
+ }
+ return true;
+}
+
+void GifTranscoder::fillRect(ColorARGB* renderBuffer,
+ int imageWidth,
+ int imageHeight,
+ int left,
+ int top,
+ int width,
+ int height,
+ ColorARGB color) {
+ ASSERT(left + width <= imageWidth, "Rectangle is outside image bounds");
+ ASSERT(top + height <= imageHeight, "Rectangle is outside image bounds");
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ ColorARGB* renderPixel = getPixel(renderBuffer, imageWidth, x + left, y + top);
+ *renderPixel = color;
+ }
+ }
+}
+
+GifByteType GifTranscoder::computeNewColorIndex(GifFileType* gifIn,
+ int transparentColorIndex,
+ ColorARGB* renderBuffer,
+ int x,
+ int y) {
+ ColorMapObject* colorMap = getColorMap(gifIn);
+
+ // Compute the average color of 4 adjacent pixels from the input image.
+ ColorARGB c1 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2);
+ ColorARGB c2 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2);
+ ColorARGB c3 = *getPixel(renderBuffer, gifIn->SWidth, x * 2, y * 2 + 1);
+ ColorARGB c4 = *getPixel(renderBuffer, gifIn->SWidth, x * 2 + 1, y * 2 + 1);
+ ColorARGB avgColor = computeAverage(c1, c2, c3, c4);
+
+ // Search the color map for the best match.
+ return findBestColor(colorMap, transparentColorIndex, avgColor);
+}
+
+ColorARGB GifTranscoder::computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4) {
+ char avgAlpha = (char)(((int) ALPHA(c1) + (int) ALPHA(c2) +
+ (int) ALPHA(c3) + (int) ALPHA(c4)) / 4);
+ char avgRed = (char)(((int) RED(c1) + (int) RED(c2) +
+ (int) RED(c3) + (int) RED(c4)) / 4);
+ char avgGreen = (char)(((int) GREEN(c1) + (int) GREEN(c2) +
+ (int) GREEN(c3) + (int) GREEN(c4)) / 4);
+ char avgBlue = (char)(((int) BLUE(c1) + (int) BLUE(c2) +
+ (int) BLUE(c3) + (int) BLUE(c4)) / 4);
+ return MAKE_COLOR_ARGB(avgAlpha, avgRed, avgGreen, avgBlue);
+}
+
+GifByteType GifTranscoder::findBestColor(ColorMapObject* colorMap, int transparentColorIndex,
+ ColorARGB targetColor) {
+ // Return the transparent color if the average alpha is zero.
+ char alpha = ALPHA(targetColor);
+ if (alpha == 0 && transparentColorIndex != NO_TRANSPARENT_COLOR) {
+ return transparentColorIndex;
+ }
+
+ GifByteType closestColorIndex = 0;
+ int closestColorDistance = MAX_COLOR_DISTANCE;
+ for (int i = 0; i < colorMap->ColorCount; i++) {
+ // Skip the transparent color (we've already eliminated that option).
+ if (i == transparentColorIndex) {
+ continue;
+ }
+ ColorARGB indexedColor = gifColorToColorARGB(colorMap->Colors[i]);
+ int distance = computeDistance(targetColor, indexedColor);
+ if (distance < closestColorDistance) {
+ closestColorIndex = i;
+ closestColorDistance = distance;
+ }
+ }
+ return closestColorIndex;
+}
+
+int GifTranscoder::computeDistance(ColorARGB c1, ColorARGB c2) {
+ return SQUARE(RED(c1) - RED(c2)) +
+ SQUARE(GREEN(c1) - GREEN(c2)) +
+ SQUARE(BLUE(c1) - BLUE(c2));
+}
+
+ColorMapObject* GifTranscoder::getColorMap(GifFileType* gifIn) {
+ if (gifIn->Image.ColorMap) {
+ return gifIn->Image.ColorMap;
+ }
+ return gifIn->SColorMap;
+}
+
+ColorARGB GifTranscoder::getColorARGB(ColorMapObject* colorMap, int transparentColorIndex,
+ GifByteType colorIndex) {
+ if (colorIndex == transparentColorIndex) {
+ return TRANSPARENT;
+ }
+ return gifColorToColorARGB(colorMap->Colors[colorIndex]);
+}
+
+ColorARGB GifTranscoder::gifColorToColorARGB(const GifColorType& color) {
+ return MAKE_COLOR_ARGB(0xff, color.Red, color.Green, color.Blue);
+}
+
+GifFilesCloser::~GifFilesCloser() {
+ if (mGifIn) {
+ DGifCloseFile(mGifIn);
+ mGifIn = NULL;
+ }
+ if (mGifOut) {
+ EGifCloseFile(mGifOut);
+ mGifOut = NULL;
+ }
+}
+
+void GifFilesCloser::setGifIn(GifFileType* gifIn) {
+ ASSERT(mGifIn == NULL, "mGifIn is already set");
+ mGifIn = gifIn;
+}
+
+void GifFilesCloser::releaseGifIn() {
+ ASSERT(mGifIn != NULL, "mGifIn is already NULL");
+ mGifIn = NULL;
+}
+
+void GifFilesCloser::setGifOut(GifFileType* gifOut) {
+ ASSERT(mGifOut == NULL, "mGifOut is already set");
+ mGifOut = gifOut;
+}
+
+void GifFilesCloser::releaseGifOut() {
+ ASSERT(mGifOut != NULL, "mGifOut is already NULL");
+ mGifOut = NULL;
+}
+
+// JNI stuff
+
+jboolean transcode(JNIEnv* env, jobject clazz, jstring filePath, jstring outFilePath) {
+ const char* pathIn = env->GetStringUTFChars(filePath, JNI_FALSE);
+ const char* pathOut = env->GetStringUTFChars(outFilePath, JNI_FALSE);
+
+ GifTranscoder transcoder;
+ int gifCode = transcoder.transcode(pathIn, pathOut);
+
+ env->ReleaseStringUTFChars(filePath, pathIn);
+ env->ReleaseStringUTFChars(outFilePath, pathOut);
+
+ return (gifCode == GIF_OK);
+}
+
+const char *kClassPathName = "com/android/messaging/util/GifTranscoder";
+
+JNINativeMethod kMethods[] = {
+ { "transcodeInternal", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)transcode },
+};
+
+int registerNativeMethods(JNIEnv* env, const char* className,
+ JNINativeMethod* gMethods, int numMethods) {
+ jclass clazz = env->FindClass(className);
+ if (clazz == NULL) {
+ return JNI_FALSE;
+ }
+ if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return -1;
+ }
+ if (!registerNativeMethods(env, kClassPathName,
+ kMethods, sizeof(kMethods) / sizeof(kMethods[0]))) {
+ return -1;
+ }
+ return JNI_VERSION_1_6;
+}
diff --git a/jni/GifTranscoder.h b/jni/GifTranscoder.h
new file mode 100644
index 0000000..39ecc24
--- /dev/null
+++ b/jni/GifTranscoder.h
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#ifndef GIF_TRANSCODER_H
+#define GIF_TRANSCODER_H
+
+#include <sys/types.h>
+
+#include "gif_lib.h"
+
+// 24-bit color with alpha, stored in order: A, R, G, B.
+// The internal GIF render buffer stores pixels using this format.
+typedef uint32_t ColorARGB;
+
+// Compresses a GIF (probably animated) so it can be sent via MMS, which generally has a 1 MB limit
+// on attachments. GIF image data is already compressed (LZW), so to achieve further reduction in
+// file size, we reduce the image dimensions.
+//
+// Helpful GIF references:
+// GIF89A spec: http://www.w3.org/Graphics/GIF/spec-gif89a.txt
+// What's in a GIF: http://giflib.sourceforge.net/whatsinagif/index.html
+//
+class GifTranscoder {
+public:
+ GifTranscoder() {}
+ ~GifTranscoder() {}
+
+ // Resizes a GIF's width and height to 50% of their original dimensions. The new file is
+ // written to pathOut.
+ //
+ // The image is resized using a box filter, which averages the colors in each 2x2 box of pixels
+ // in the source to generate the color of the pixel in the destination.
+ //
+ // Returns GIF_OK (1) on success, or GIF_ERROR (0) on failure.
+ int transcode(const char* pathIn, const char* pathOut);
+
+private:
+ // Implementation of the box filter algorithm.
+ static bool resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut);
+
+ // Reads the raster data for the current image of the GIF.
+ static bool readImage(GifFileType* gifIn, GifByteType* rasterBits);
+
+ // Renders the current image of the GIF into the supplied render buffer.
+ static bool renderImage(GifFileType* gifIn,
+ GifByteType* rasterBits,
+ int imageIndex,
+ int transparentColorIndex,
+ ColorARGB* renderBuffer,
+ ColorARGB bgColor,
+ GifImageDesc prevImageDimens,
+ int prevImageDisposalMode);
+
+ // Fills a rectangle in the buffer with a solid color.
+ static void fillRect(ColorARGB* renderBuffer,
+ int imageWidth,
+ int imageHeight,
+ int left,
+ int top,
+ int width,
+ int height,
+ ColorARGB color);
+
+ // Computes the color for the pixel (x,y) in the current image in the output GIF.
+ static GifByteType computeNewColorIndex(GifFileType* gifIn,
+ int transparentColorIndex,
+ ColorARGB* renderBuffer,
+ int x,
+ int y);
+
+ // Computes the average color (by averaging the per-channel (ARGB) values).
+ static ColorARGB computeAverage(ColorARGB c1, ColorARGB c2, ColorARGB c3, ColorARGB c4);
+
+ // Searches a color map for the color closest (Euclidean distance) to the target color.
+ static GifByteType findBestColor(ColorMapObject* colorMap, int transparentColorIndex,
+ ColorARGB targetColor);
+
+ // Computes distance (squared) between 2 colors, considering each channel a separate dimension.
+ static int computeDistance(ColorARGB c1, ColorARGB c2);
+
+ // Returns the local color map of the current image (if any), or else the global color map.
+ static ColorMapObject* getColorMap(GifFileType* gifIn);
+
+ // Returns an indexed color from the color map.
+ static ColorARGB getColorARGB(ColorMapObject* colorMap, int transparentColorIndex,
+ GifByteType colorIndex);
+
+ // Converts a 24-bit GIF color (RGB) to a 32-bit ARGB color.
+ static ColorARGB gifColorToColorARGB(const GifColorType& color);
+};
+
+// Wrapper class that automatically closes the GIF files when the wrapper goes out of scope.
+class GifFilesCloser {
+public:
+ GifFilesCloser() {}
+ ~GifFilesCloser();
+
+ void setGifIn(GifFileType* gifIn);
+ void releaseGifIn();
+
+ void setGifOut(GifFileType* gifOut);
+ void releaseGifOut();
+
+private:
+ GifFileType* mGifIn = NULL;
+ GifFileType* mGifOut = NULL;
+};
+
+#endif // GIF_TRANSCODER_H
diff --git a/proguard-release.flags b/proguard-release.flags
new file mode 100644
index 0000000..36b6720
--- /dev/null
+++ b/proguard-release.flags
@@ -0,0 +1,24 @@
+# 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.
+
+-assumenosideeffects public class com.android.messaging.util.Trace {
+ public void beginSection(...);
+ public void endSection(...);
+}
+-assumenosideeffects public class com.android.messaging.util.Assert {
+ private void setIfEngBuild();
+}
+-assumenosideeffects public class com.android.messaging.util.DebugUtils {
+ public void logCurrentMethod(...);
+}
diff --git a/proguard-test.flags b/proguard-test.flags
new file mode 100755
index 0000000..46e569f
--- /dev/null
+++ b/proguard-test.flags
@@ -0,0 +1,41 @@
+# 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.
+#
+# These flags are in addition to the one in proguard.flags, which is included by the build
+# directives in Android.mk.
+
+-dontobfuscate
+-dontoptimize
+
+
+
+# FLAG(dnotario): Until we rationalize how to handle tests (extensive unit tests will want similar
+# settings to these, but maybe we want to require VisibleForTesting attribute), just expose all
+# non-private methods. This means we cannot run tests on user builds for the moment.
+-keep class com.android.messaging.* {
+ !private *;
+}
+
+-keep class com.android.messaging.*.* {
+ !private *;
+}
+
+-keep class com.android.messaging.*.*.* {
+ !private *;
+}
+
+# Keep the classes needed by emma
+-keep class com.vladium.** { *; }
+
+# End of Test Rules
diff --git a/proguard.flags b/proguard.flags
new file mode 100644
index 0000000..759f2d4
--- /dev/null
+++ b/proguard.flags
@@ -0,0 +1,58 @@
+# 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.
+#
+# Keep enough data for stack traces
+-renamesourcefileattribute SourceFile
+-keepattributes SourceFile,LineNumberTable,*Annotation*
+
+# Keep classes and methods that have the guava @VisibleForTesting annotation
+-keep @com.google.common.annotations.VisibleForTesting class *
+-keepclassmembers class * {
+ @com.google.common.annotations.VisibleForTesting *;
+}
+
+# Keep methods that have the @VisibleForAnimation annotation
+-keep @interface com.android.messaging.annotation.VisibleForAnimation
+-keepclassmembers class * {
+ @com.android.messaging.annotation.VisibleForAnimation *;
+}
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class * extends android.support.v4.app.Fragment
+-keep public class com.android.vcard.* { *; }
+
+-keep class android.support.v4.* { *; }
+-keep class android.support.v4.*.* { *; }
+-keep class android.support.v7.* { *; }
+-keep class android.support.v7.*.* { *; }
+
+# Keep rastermill classes that need to be accessed from native code (JNI)
+-keep class android.support.rastermill.** { *; }
+
+# Preserve the name of the getCaller method so it can find itself in the stack trace it generates
+-keepclassmembers public class com.android.messaging.util.DebugUtils {
+ public static java.lang.StackTraceElement getCaller(...);
+}
+
+# Keep the static fields of referenced inner classes of auto-generated R classes, in case we
+# access those fields by reflection (e.g. EmojiMarkup)
+-keepclassmembers class **.R$* {
+ public static <fields>;
+}
diff --git a/res/animator/fab_anim.xml b/res/animator/fab_anim.xml
new file mode 100644
index 0000000..e06416e
--- /dev/null
+++ b/res/animator/fab_anim.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:state_enabled="true"
+ android:state_pressed="true">
+ <objectAnimator
+ android:duration="@android:integer/config_shortAnimTime"
+ android:propertyName="translationZ"
+ android:valueTo="@dimen/fab_elevation_pressed"
+ android:valueType="floatType" />
+ </item>
+
+ <item>
+ <objectAnimator
+ android:duration="@android:integer/config_shortAnimTime"
+ android:propertyName="translationZ"
+ android:valueTo="@dimen/fab_elevation"
+ android:valueType="floatType" />
+ </item>
+
+</selector>
diff --git a/res/color/tab_text_color.xml b/res/color/tab_text_color.xml
new file mode 100644
index 0000000..dec20dd
--- /dev/null
+++ b/res/color/tab_text_color.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:color="@android:color/white"
+ android:state_selected="true"/>
+ <item
+ android:color="@color/translucent_white" />
+</selector> \ No newline at end of file
diff --git a/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..e5f8942
--- /dev/null
+++ b/res/drawable-hdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..11ef458
--- /dev/null
+++ b/res/drawable-hdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..eb948c1
--- /dev/null
+++ b/res/drawable-hdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..b90188e
--- /dev/null
+++ b/res/drawable-hdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/cab_bg.9.png b/res/drawable-hdpi/cab_bg.9.png
new file mode 100644
index 0000000..1adfbc6
--- /dev/null
+++ b/res/drawable-hdpi/cab_bg.9.png
Binary files differ
diff --git a/res/drawable-hdpi/contact_popup_background.9.png b/res/drawable-hdpi/contact_popup_background.9.png
new file mode 100644
index 0000000..060ffad
--- /dev/null
+++ b/res/drawable-hdpi/contact_popup_background.9.png
Binary files differ
diff --git a/res/drawable-hdpi/fab_new_message_static_shadow.png b/res/drawable-hdpi/fab_new_message_static_shadow.png
new file mode 100644
index 0000000..22e1764
--- /dev/null
+++ b/res/drawable-hdpi/fab_new_message_static_shadow.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_add_gray.png b/res/drawable-hdpi/ic_add_gray.png
new file mode 100644
index 0000000..dbbd78d
--- /dev/null
+++ b/res/drawable-hdpi/ic_add_gray.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_add_white.png b/res/drawable-hdpi/ic_add_white.png
new file mode 100644
index 0000000..b52309f
--- /dev/null
+++ b/res/drawable-hdpi/ic_add_white.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_archive_small_dark.png b/res/drawable-hdpi/ic_archive_small_dark.png
new file mode 100644
index 0000000..b679ebb
--- /dev/null
+++ b/res/drawable-hdpi/ic_archive_small_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_archive_small_light.png b/res/drawable-hdpi/ic_archive_small_light.png
new file mode 100644
index 0000000..783126c
--- /dev/null
+++ b/res/drawable-hdpi/ic_archive_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_archive_undo_small_dark.png b/res/drawable-hdpi/ic_archive_undo_small_dark.png
new file mode 100644
index 0000000..505988d
--- /dev/null
+++ b/res/drawable-hdpi/ic_archive_undo_small_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_archive_undo_small_light.png b/res/drawable-hdpi/ic_archive_undo_small_light.png
new file mode 100644
index 0000000..2c91e44
--- /dev/null
+++ b/res/drawable-hdpi/ic_archive_undo_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_arrow_back_dark.png b/res/drawable-hdpi/ic_arrow_back_dark.png
new file mode 100644
index 0000000..4e5dc89
--- /dev/null
+++ b/res/drawable-hdpi/ic_arrow_back_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_arrow_back_light.png b/res/drawable-hdpi/ic_arrow_back_light.png
new file mode 100644
index 0000000..fa1f67b
--- /dev/null
+++ b/res/drawable-hdpi/ic_arrow_back_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_attachment_dark.png b/res/drawable-hdpi/ic_attachment_dark.png
new file mode 100644
index 0000000..10a1028
--- /dev/null
+++ b/res/drawable-hdpi/ic_attachment_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_audio_light.png b/res/drawable-hdpi/ic_audio_light.png
new file mode 100644
index 0000000..9605a5c
--- /dev/null
+++ b/res/drawable-hdpi/ic_audio_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_audio_pause.png b/res/drawable-hdpi/ic_audio_pause.png
new file mode 100644
index 0000000..8797ad0
--- /dev/null
+++ b/res/drawable-hdpi/ic_audio_pause.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_audio_play.png b/res/drawable-hdpi/ic_audio_play.png
new file mode 100644
index 0000000..552dc9c
--- /dev/null
+++ b/res/drawable-hdpi/ic_audio_play.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_camera_front_light.png b/res/drawable-hdpi/ic_camera_front_light.png
new file mode 100644
index 0000000..c0ddaf6
--- /dev/null
+++ b/res/drawable-hdpi/ic_camera_front_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_camera_light.png b/res/drawable-hdpi/ic_camera_light.png
new file mode 100644
index 0000000..d0406e4
--- /dev/null
+++ b/res/drawable-hdpi/ic_camera_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_camera_rear_light.png b/res/drawable-hdpi/ic_camera_rear_light.png
new file mode 100644
index 0000000..6624c55
--- /dev/null
+++ b/res/drawable-hdpi/ic_camera_rear_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_cancel_small_dark.png b/res/drawable-hdpi/ic_cancel_small_dark.png
new file mode 100644
index 0000000..5de5cfb
--- /dev/null
+++ b/res/drawable-hdpi/ic_cancel_small_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_cancel_small_light.png b/res/drawable-hdpi/ic_cancel_small_light.png
new file mode 100644
index 0000000..05c6b19
--- /dev/null
+++ b/res/drawable-hdpi/ic_cancel_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkbox_blank_light.png b/res/drawable-hdpi/ic_checkbox_blank_light.png
new file mode 100644
index 0000000..879a770
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkbox_blank_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkbox_light.png b/res/drawable-hdpi/ic_checkbox_light.png
new file mode 100644
index 0000000..7aa9f5f
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkbox_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkbox_outline_light.png b/res/drawable-hdpi/ic_checkbox_outline_light.png
new file mode 100644
index 0000000..44ed34e
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkbox_outline_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkmark_circle_blue.png b/res/drawable-hdpi/ic_checkmark_circle_blue.png
new file mode 100644
index 0000000..55f277e
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkmark_circle_blue.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkmark_large.png b/res/drawable-hdpi/ic_checkmark_large.png
new file mode 100644
index 0000000..b8ecc06
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkmark_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkmark_large_light.png b/res/drawable-hdpi/ic_checkmark_large_light.png
new file mode 100644
index 0000000..48bf283
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkmark_large_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkmark_small_blue.png b/res/drawable-hdpi/ic_checkmark_small_blue.png
new file mode 100644
index 0000000..8661489
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkmark_small_blue.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_checkmark_small_light.png b/res/drawable-hdpi/ic_checkmark_small_light.png
new file mode 100644
index 0000000..ae0bf6d
--- /dev/null
+++ b/res/drawable-hdpi/ic_checkmark_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_color_lens.png b/res/drawable-hdpi/ic_color_lens.png
new file mode 100644
index 0000000..69a4287
--- /dev/null
+++ b/res/drawable-hdpi/ic_color_lens.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_content_copy_dark.png b/res/drawable-hdpi/ic_content_copy_dark.png
new file mode 100644
index 0000000..22d2516
--- /dev/null
+++ b/res/drawable-hdpi/ic_content_copy_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_crying_hero.png b/res/drawable-hdpi/ic_crying_hero.png
new file mode 100644
index 0000000..804b4e6
--- /dev/null
+++ b/res/drawable-hdpi/ic_crying_hero.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_delete_small_dark.png b/res/drawable-hdpi/ic_delete_small_dark.png
new file mode 100644
index 0000000..901f992
--- /dev/null
+++ b/res/drawable-hdpi/ic_delete_small_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_delete_small_light.png b/res/drawable-hdpi/ic_delete_small_light.png
new file mode 100644
index 0000000..d2ed6d3
--- /dev/null
+++ b/res/drawable-hdpi/ic_delete_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_dnd_on_dark.png b/res/drawable-hdpi/ic_dnd_on_dark.png
new file mode 100644
index 0000000..181df02
--- /dev/null
+++ b/res/drawable-hdpi/ic_dnd_on_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_dnd_on_light.png b/res/drawable-hdpi/ic_dnd_on_light.png
new file mode 100644
index 0000000..cadf5b5
--- /dev/null
+++ b/res/drawable-hdpi/ic_dnd_on_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_failed_light.png b/res/drawable-hdpi/ic_failed_light.png
new file mode 100644
index 0000000..3614fbe
--- /dev/null
+++ b/res/drawable-hdpi/ic_failed_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_failed_status_red.png b/res/drawable-hdpi/ic_failed_status_red.png
new file mode 100644
index 0000000..aa2d138
--- /dev/null
+++ b/res/drawable-hdpi/ic_failed_status_red.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_file_download_dark.png b/res/drawable-hdpi/ic_file_download_dark.png
new file mode 100644
index 0000000..028c85e
--- /dev/null
+++ b/res/drawable-hdpi/ic_file_download_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_file_download_light.png b/res/drawable-hdpi/ic_file_download_light.png
new file mode 100644
index 0000000..ffa5b82
--- /dev/null
+++ b/res/drawable-hdpi/ic_file_download_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_forward_dark.png b/res/drawable-hdpi/ic_forward_dark.png
new file mode 100644
index 0000000..e8b718a
--- /dev/null
+++ b/res/drawable-hdpi/ic_forward_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_history_light.png b/res/drawable-hdpi/ic_history_light.png
new file mode 100644
index 0000000..4b512fe
--- /dev/null
+++ b/res/drawable-hdpi/ic_history_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_image_light.png b/res/drawable-hdpi/ic_image_light.png
new file mode 100644
index 0000000..744f920
--- /dev/null
+++ b/res/drawable-hdpi/ic_image_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ime_dark.png b/res/drawable-hdpi/ic_ime_dark.png
new file mode 100644
index 0000000..a539d28
--- /dev/null
+++ b/res/drawable-hdpi/ic_ime_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_ime_light.png b/res/drawable-hdpi/ic_ime_light.png
new file mode 100644
index 0000000..da65039
--- /dev/null
+++ b/res/drawable-hdpi/ic_ime_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_info_dark.png b/res/drawable-hdpi/ic_info_dark.png
new file mode 100644
index 0000000..1453228
--- /dev/null
+++ b/res/drawable-hdpi/ic_info_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_keyboard_arrow_left_light.png b/res/drawable-hdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..50f280e
--- /dev/null
+++ b/res/drawable-hdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_keyboard_arrow_right_light.png b/res/drawable-hdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..3e31c08
--- /dev/null
+++ b/res/drawable-hdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..2c52f06
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_audio_mic.png b/res/drawable-hdpi/ic_mp_audio_mic.png
new file mode 100644
index 0000000..9c005d6
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_audio_mic.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_camera_large_light.png b/res/drawable-hdpi/ic_mp_camera_large_light.png
new file mode 100644
index 0000000..721b760
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_camera_large_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_camera_small_light.png b/res/drawable-hdpi/ic_mp_camera_small_light.png
new file mode 100644
index 0000000..6e3b09b
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_camera_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_capture_stop_large_light.png b/res/drawable-hdpi/ic_mp_capture_stop_large_light.png
new file mode 100644
index 0000000..5e29fdd
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_capture_stop_large_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_full_screen_light.png b/res/drawable-hdpi/ic_mp_full_screen_light.png
new file mode 100644
index 0000000..9c69eb8
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_full_screen_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_video_large_light.png b/res/drawable-hdpi/ic_mp_video_large_light.png
new file mode 100644
index 0000000..d6db42c
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_video_large_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_mp_video_small_light.png b/res/drawable-hdpi/ic_mp_video_small_light.png
new file mode 100644
index 0000000..9816af7
--- /dev/null
+++ b/res/drawable-hdpi/ic_mp_video_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_notifications_off_dark.png b/res/drawable-hdpi/ic_notifications_off_dark.png
new file mode 100644
index 0000000..9bcdc62
--- /dev/null
+++ b/res/drawable-hdpi/ic_notifications_off_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_notifications_off_light.png b/res/drawable-hdpi/ic_notifications_off_light.png
new file mode 100644
index 0000000..433b6a9
--- /dev/null
+++ b/res/drawable-hdpi/ic_notifications_off_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_notifications_off_small_light.png b/res/drawable-hdpi/ic_notifications_off_small_light.png
new file mode 100644
index 0000000..fbd0ad5
--- /dev/null
+++ b/res/drawable-hdpi/ic_notifications_off_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_notifications_on_dark.png b/res/drawable-hdpi/ic_notifications_on_dark.png
new file mode 100644
index 0000000..21e48a5
--- /dev/null
+++ b/res/drawable-hdpi/ic_notifications_on_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_notifications_on_light.png b/res/drawable-hdpi/ic_notifications_on_light.png
new file mode 100644
index 0000000..6b39fec
--- /dev/null
+++ b/res/drawable-hdpi/ic_notifications_on_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_numeric_dialpad.png b/res/drawable-hdpi/ic_numeric_dialpad.png
new file mode 100644
index 0000000..5460fd7
--- /dev/null
+++ b/res/drawable-hdpi/ic_numeric_dialpad.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_oobe_conv_list.png b/res/drawable-hdpi/ic_oobe_conv_list.png
new file mode 100644
index 0000000..3b904ba
--- /dev/null
+++ b/res/drawable-hdpi/ic_oobe_conv_list.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_oobe_freq_list.png b/res/drawable-hdpi/ic_oobe_freq_list.png
new file mode 100644
index 0000000..148a9ea
--- /dev/null
+++ b/res/drawable-hdpi/ic_oobe_freq_list.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_open_in_new.png b/res/drawable-hdpi/ic_open_in_new.png
new file mode 100644
index 0000000..06e5f71
--- /dev/null
+++ b/res/drawable-hdpi/ic_open_in_new.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_people_add_light.png b/res/drawable-hdpi/ic_people_add_light.png
new file mode 100644
index 0000000..c506e9a
--- /dev/null
+++ b/res/drawable-hdpi/ic_people_add_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_person_add_blue.png b/res/drawable-hdpi/ic_person_add_blue.png
new file mode 100644
index 0000000..dfd2bc1
--- /dev/null
+++ b/res/drawable-hdpi/ic_person_add_blue.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_person_add_dark.png b/res/drawable-hdpi/ic_person_add_dark.png
new file mode 100644
index 0000000..71ca2a0
--- /dev/null
+++ b/res/drawable-hdpi/ic_person_add_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_person_add_light.png b/res/drawable-hdpi/ic_person_add_light.png
new file mode 100644
index 0000000..bb8ad3e
--- /dev/null
+++ b/res/drawable-hdpi/ic_person_add_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_person_light.png b/res/drawable-hdpi/ic_person_light.png
new file mode 100644
index 0000000..b439c93
--- /dev/null
+++ b/res/drawable-hdpi/ic_person_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_person_light_large.png b/res/drawable-hdpi/ic_person_light_large.png
new file mode 100644
index 0000000..5ddf265
--- /dev/null
+++ b/res/drawable-hdpi/ic_person_light_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_phone_small_light.png b/res/drawable-hdpi/ic_phone_small_light.png
new file mode 100644
index 0000000..35cf121
--- /dev/null
+++ b/res/drawable-hdpi/ic_phone_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_photo_library_light.png b/res/drawable-hdpi/ic_photo_library_light.png
new file mode 100644
index 0000000..1f71a62
--- /dev/null
+++ b/res/drawable-hdpi/ic_photo_library_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_preview_pause.png b/res/drawable-hdpi/ic_preview_pause.png
new file mode 100644
index 0000000..fd49749
--- /dev/null
+++ b/res/drawable-hdpi/ic_preview_pause.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_preview_play.png b/res/drawable-hdpi/ic_preview_play.png
new file mode 100644
index 0000000..db2b2ac
--- /dev/null
+++ b/res/drawable-hdpi/ic_preview_play.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_remove_light.png b/res/drawable-hdpi/ic_remove_light.png
new file mode 100644
index 0000000..3992231
--- /dev/null
+++ b/res/drawable-hdpi/ic_remove_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_remove_small_light.png b/res/drawable-hdpi/ic_remove_small_light.png
new file mode 100644
index 0000000..07e668c
--- /dev/null
+++ b/res/drawable-hdpi/ic_remove_small_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_save_dark.png b/res/drawable-hdpi/ic_save_dark.png
new file mode 100644
index 0000000..7cf01da
--- /dev/null
+++ b/res/drawable-hdpi/ic_save_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_save_light.png b/res/drawable-hdpi/ic_save_light.png
new file mode 100644
index 0000000..b76adb2
--- /dev/null
+++ b/res/drawable-hdpi/ic_save_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_light.png b/res/drawable-hdpi/ic_search_light.png
new file mode 100644
index 0000000..c35418e
--- /dev/null
+++ b/res/drawable-hdpi/ic_search_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_send_dark.png b/res/drawable-hdpi/ic_send_dark.png
new file mode 100644
index 0000000..62108ef
--- /dev/null
+++ b/res/drawable-hdpi/ic_send_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_send_light.png b/res/drawable-hdpi/ic_send_light.png
new file mode 100644
index 0000000..724783d
--- /dev/null
+++ b/res/drawable-hdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_share_dark.png b/res/drawable-hdpi/ic_share_dark.png
new file mode 100644
index 0000000..bd27042
--- /dev/null
+++ b/res/drawable-hdpi/ic_share_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_share_light.png b/res/drawable-hdpi/ic_share_light.png
new file mode 100644
index 0000000..dadb3e8
--- /dev/null
+++ b/res/drawable-hdpi/ic_share_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sim_card_send.png b/res/drawable-hdpi/ic_sim_card_send.png
new file mode 100644
index 0000000..426b153
--- /dev/null
+++ b/res/drawable-hdpi/ic_sim_card_send.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sms_delivery_ok.png b/res/drawable-hdpi/ic_sms_delivery_ok.png
new file mode 100644
index 0000000..e038563
--- /dev/null
+++ b/res/drawable-hdpi/ic_sms_delivery_ok.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sms_failed_light.png b/res/drawable-hdpi/ic_sms_failed_light.png
new file mode 100644
index 0000000..4973427
--- /dev/null
+++ b/res/drawable-hdpi/ic_sms_failed_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sms_light.png b/res/drawable-hdpi/ic_sms_light.png
new file mode 100644
index 0000000..aaeec4e
--- /dev/null
+++ b/res/drawable-hdpi/ic_sms_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sms_multi_light.png b/res/drawable-hdpi/ic_sms_multi_light.png
new file mode 100644
index 0000000..81ac2e7
--- /dev/null
+++ b/res/drawable-hdpi/ic_sms_multi_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_tooltip_arrow.png b/res/drawable-hdpi/ic_tooltip_arrow.png
new file mode 100644
index 0000000..870d502
--- /dev/null
+++ b/res/drawable-hdpi/ic_tooltip_arrow.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_play_light.png b/res/drawable-hdpi/ic_video_play_light.png
new file mode 100644
index 0000000..4a74f37
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_play_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wear_reply.png b/res/drawable-hdpi/ic_wear_reply.png
new file mode 100644
index 0000000..19ce472
--- /dev/null
+++ b/res/drawable-hdpi/ic_wear_reply.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_avatar_shadow.png b/res/drawable-hdpi/ic_widget_avatar_shadow.png
new file mode 100644
index 0000000..9e0d519
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_avatar_shadow.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_list.png b/res/drawable-hdpi/ic_widget_list.png
new file mode 100644
index 0000000..06f9732
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_list.png
Binary files differ
diff --git a/res/drawable-hdpi/msg_bubble_error.9.png b/res/drawable-hdpi/msg_bubble_error.9.png
new file mode 100644
index 0000000..6a26c30
--- /dev/null
+++ b/res/drawable-hdpi/msg_bubble_error.9.png
Binary files differ
diff --git a/res/drawable-hdpi/msg_bubble_incoming.9.png b/res/drawable-hdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..7fed0a8
--- /dev/null
+++ b/res/drawable-hdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-hdpi/msg_bubble_input.9.png b/res/drawable-hdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..01076da
--- /dev/null
+++ b/res/drawable-hdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-hdpi/msg_bubble_outgoing.9.png b/res/drawable-hdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..8e7ccc0
--- /dev/null
+++ b/res/drawable-hdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-hdpi/permissions.png b/res/drawable-hdpi/permissions.png
new file mode 100644
index 0000000..20697cf
--- /dev/null
+++ b/res/drawable-hdpi/permissions.png
Binary files differ
diff --git a/res/drawable-hdpi/swipe_shadow.9.png b/res/drawable-hdpi/swipe_shadow.9.png
new file mode 100644
index 0000000..8405721
--- /dev/null
+++ b/res/drawable-hdpi/swipe_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi/swipe_shadow_drag.9.png b/res/drawable-hdpi/swipe_shadow_drag.9.png
new file mode 100644
index 0000000..950ec71
--- /dev/null
+++ b/res/drawable-hdpi/swipe_shadow_drag.9.png
Binary files differ
diff --git a/res/drawable-hdpi/sym_keyboard_delete_holo.png b/res/drawable-hdpi/sym_keyboard_delete_holo.png
new file mode 100644
index 0000000..9e74a64
--- /dev/null
+++ b/res/drawable-hdpi/sym_keyboard_delete_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_btn_bg_normal.9.png b/res/drawable-hdpi/tab_btn_bg_normal.9.png
new file mode 100644
index 0000000..791bb40
--- /dev/null
+++ b/res/drawable-hdpi/tab_btn_bg_normal.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_btn_bg_pressed.9.png b/res/drawable-hdpi/tab_btn_bg_pressed.9.png
new file mode 100644
index 0000000..b2b488c
--- /dev/null
+++ b/res/drawable-hdpi/tab_btn_bg_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected.9.png b/res/drawable-hdpi/tab_selected.9.png
new file mode 100644
index 0000000..84e63df
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_focused_holo.9.png b/res/drawable-hdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..5cdc5b4
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..ca3eb7b
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected_pressed_holo.9.png b/res/drawable-hdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..08e5edb
--- /dev/null
+++ b/res/drawable-hdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected.9.png b/res/drawable-hdpi/tab_unselected.9.png
new file mode 100644
index 0000000..bbcfb2c
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..a0c8bc3
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..6cf948e
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected_pressed_holo.9.png b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..a5dce50
--- /dev/null
+++ b/res/drawable-hdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_hr.9.png b/res/drawable-hdpi/widget_hr.9.png
new file mode 100644
index 0000000..7590256
--- /dev/null
+++ b/res/drawable-hdpi/widget_hr.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_msg_bubble_incoming.9.png b/res/drawable-hdpi/widget_msg_bubble_incoming.9.png
new file mode 100644
index 0000000..c6e77a7
--- /dev/null
+++ b/res/drawable-hdpi/widget_msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-hdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-hdpi/widget_msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..476efae
--- /dev/null
+++ b/res/drawable-hdpi/widget_msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..3e31c08
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..50f280e
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/ic_send_light.png b/res/drawable-ldrtl-hdpi/ic_send_light.png
new file mode 100644
index 0000000..4ee98a3
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..8e7ccc0
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..c3f68b0
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..7fed0a8
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..f0e1b08
--- /dev/null
+++ b/res/drawable-ldrtl-hdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..9bd625d
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..6004f37
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/ic_send_light.png b/res/drawable-ldrtl-mdpi/ic_send_light.png
new file mode 100644
index 0000000..1267d66
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..5ca9b14
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..6f0b4bc
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..4e7e464
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..346e754
--- /dev/null
+++ b/res/drawable-ldrtl-mdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..51a1903
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..464a0bc
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/ic_send_light.png b/res/drawable-ldrtl-xhdpi/ic_send_light.png
new file mode 100644
index 0000000..178c3cf
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..52cb936
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..4c5f45d
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..0661fdb
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..a662fc2
--- /dev/null
+++ b/res/drawable-ldrtl-xhdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..62ca382
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..09536ed
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/ic_send_light.png b/res/drawable-ldrtl-xxhdpi/ic_send_light.png
new file mode 100644
index 0000000..74ffc46
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..5d8fc74
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..6d59b8f
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..009c721
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..273efd6
--- /dev/null
+++ b/res/drawable-ldrtl-xxhdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..85d005b
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..3737fec
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/ic_send_light.png b/res/drawable-ldrtl-xxxhdpi/ic_send_light.png
new file mode 100644
index 0000000..cd8e266
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..8378db5
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..a707db0
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..f0d0d9e
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.png b/res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.png
new file mode 100644
index 0000000..9203260
--- /dev/null
+++ b/res/drawable-ldrtl-xxxhdpi/sym_keyboard_delete_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..577ab08
--- /dev/null
+++ b/res/drawable-mdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..d8525e4
--- /dev/null
+++ b/res/drawable-mdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..1b029e1
--- /dev/null
+++ b/res/drawable-mdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..4fa69f8
--- /dev/null
+++ b/res/drawable-mdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/cab_bg.9.png b/res/drawable-mdpi/cab_bg.9.png
new file mode 100644
index 0000000..f43a85b
--- /dev/null
+++ b/res/drawable-mdpi/cab_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/contact_popup_background.9.png b/res/drawable-mdpi/contact_popup_background.9.png
new file mode 100644
index 0000000..2989590
--- /dev/null
+++ b/res/drawable-mdpi/contact_popup_background.9.png
Binary files differ
diff --git a/res/drawable-mdpi/fab_new_message_static_shadow.png b/res/drawable-mdpi/fab_new_message_static_shadow.png
new file mode 100644
index 0000000..2e24f0a
--- /dev/null
+++ b/res/drawable-mdpi/fab_new_message_static_shadow.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_add_gray.png b/res/drawable-mdpi/ic_add_gray.png
new file mode 100644
index 0000000..806b6e1
--- /dev/null
+++ b/res/drawable-mdpi/ic_add_gray.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_add_white.png b/res/drawable-mdpi/ic_add_white.png
new file mode 100644
index 0000000..4a14229
--- /dev/null
+++ b/res/drawable-mdpi/ic_add_white.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_archive_small_dark.png b/res/drawable-mdpi/ic_archive_small_dark.png
new file mode 100644
index 0000000..a733f56
--- /dev/null
+++ b/res/drawable-mdpi/ic_archive_small_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_archive_small_light.png b/res/drawable-mdpi/ic_archive_small_light.png
new file mode 100644
index 0000000..fa3ffa9
--- /dev/null
+++ b/res/drawable-mdpi/ic_archive_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_archive_undo_small_dark.png b/res/drawable-mdpi/ic_archive_undo_small_dark.png
new file mode 100644
index 0000000..bc9dead
--- /dev/null
+++ b/res/drawable-mdpi/ic_archive_undo_small_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_archive_undo_small_light.png b/res/drawable-mdpi/ic_archive_undo_small_light.png
new file mode 100644
index 0000000..08b2332
--- /dev/null
+++ b/res/drawable-mdpi/ic_archive_undo_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_dark.png b/res/drawable-mdpi/ic_arrow_back_dark.png
new file mode 100644
index 0000000..75ae1b1
--- /dev/null
+++ b/res/drawable-mdpi/ic_arrow_back_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_light.png b/res/drawable-mdpi/ic_arrow_back_light.png
new file mode 100644
index 0000000..c59a517
--- /dev/null
+++ b/res/drawable-mdpi/ic_arrow_back_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_attachment_dark.png b/res/drawable-mdpi/ic_attachment_dark.png
new file mode 100644
index 0000000..2315e23
--- /dev/null
+++ b/res/drawable-mdpi/ic_attachment_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_audio_light.png b/res/drawable-mdpi/ic_audio_light.png
new file mode 100644
index 0000000..78bc072
--- /dev/null
+++ b/res/drawable-mdpi/ic_audio_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_audio_pause.png b/res/drawable-mdpi/ic_audio_pause.png
new file mode 100644
index 0000000..e74d158
--- /dev/null
+++ b/res/drawable-mdpi/ic_audio_pause.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_audio_play.png b/res/drawable-mdpi/ic_audio_play.png
new file mode 100644
index 0000000..0d986a4
--- /dev/null
+++ b/res/drawable-mdpi/ic_audio_play.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_camera_front_light.png b/res/drawable-mdpi/ic_camera_front_light.png
new file mode 100644
index 0000000..a537897
--- /dev/null
+++ b/res/drawable-mdpi/ic_camera_front_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_camera_light.png b/res/drawable-mdpi/ic_camera_light.png
new file mode 100644
index 0000000..429bef4
--- /dev/null
+++ b/res/drawable-mdpi/ic_camera_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_camera_rear_light.png b/res/drawable-mdpi/ic_camera_rear_light.png
new file mode 100644
index 0000000..4da3328
--- /dev/null
+++ b/res/drawable-mdpi/ic_camera_rear_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_cancel_small_dark.png b/res/drawable-mdpi/ic_cancel_small_dark.png
new file mode 100644
index 0000000..f63f351
--- /dev/null
+++ b/res/drawable-mdpi/ic_cancel_small_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_cancel_small_light.png b/res/drawable-mdpi/ic_cancel_small_light.png
new file mode 100644
index 0000000..33fdf10
--- /dev/null
+++ b/res/drawable-mdpi/ic_cancel_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkbox_blank_light.png b/res/drawable-mdpi/ic_checkbox_blank_light.png
new file mode 100644
index 0000000..baec804
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkbox_blank_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkbox_light.png b/res/drawable-mdpi/ic_checkbox_light.png
new file mode 100644
index 0000000..3762dc4
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkbox_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkbox_outline_light.png b/res/drawable-mdpi/ic_checkbox_outline_light.png
new file mode 100644
index 0000000..4c3c722
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkbox_outline_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkmark_circle_blue.png b/res/drawable-mdpi/ic_checkmark_circle_blue.png
new file mode 100644
index 0000000..85735be
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkmark_circle_blue.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkmark_large.png b/res/drawable-mdpi/ic_checkmark_large.png
new file mode 100644
index 0000000..95206d1
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkmark_large.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkmark_large_light.png b/res/drawable-mdpi/ic_checkmark_large_light.png
new file mode 100644
index 0000000..4c33ce6
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkmark_large_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkmark_small_blue.png b/res/drawable-mdpi/ic_checkmark_small_blue.png
new file mode 100644
index 0000000..ec9dcb1
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkmark_small_blue.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_checkmark_small_light.png b/res/drawable-mdpi/ic_checkmark_small_light.png
new file mode 100644
index 0000000..414fe8f
--- /dev/null
+++ b/res/drawable-mdpi/ic_checkmark_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_color_lens.png b/res/drawable-mdpi/ic_color_lens.png
new file mode 100644
index 0000000..b33c8b6
--- /dev/null
+++ b/res/drawable-mdpi/ic_color_lens.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_content_copy_dark.png b/res/drawable-mdpi/ic_content_copy_dark.png
new file mode 100644
index 0000000..4b99a83
--- /dev/null
+++ b/res/drawable-mdpi/ic_content_copy_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_crying_hero.png b/res/drawable-mdpi/ic_crying_hero.png
new file mode 100644
index 0000000..d2f5eef
--- /dev/null
+++ b/res/drawable-mdpi/ic_crying_hero.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_delete_small_dark.png b/res/drawable-mdpi/ic_delete_small_dark.png
new file mode 100644
index 0000000..86c0002
--- /dev/null
+++ b/res/drawable-mdpi/ic_delete_small_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_delete_small_light.png b/res/drawable-mdpi/ic_delete_small_light.png
new file mode 100644
index 0000000..641adc4
--- /dev/null
+++ b/res/drawable-mdpi/ic_delete_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_dnd_on_dark.png b/res/drawable-mdpi/ic_dnd_on_dark.png
new file mode 100644
index 0000000..4e911a9
--- /dev/null
+++ b/res/drawable-mdpi/ic_dnd_on_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_dnd_on_light.png b/res/drawable-mdpi/ic_dnd_on_light.png
new file mode 100644
index 0000000..4936c2f
--- /dev/null
+++ b/res/drawable-mdpi/ic_dnd_on_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_failed_light.png b/res/drawable-mdpi/ic_failed_light.png
new file mode 100644
index 0000000..02a4d0b
--- /dev/null
+++ b/res/drawable-mdpi/ic_failed_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_failed_status_red.png b/res/drawable-mdpi/ic_failed_status_red.png
new file mode 100644
index 0000000..f6feafe
--- /dev/null
+++ b/res/drawable-mdpi/ic_failed_status_red.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_file_download_dark.png b/res/drawable-mdpi/ic_file_download_dark.png
new file mode 100644
index 0000000..8104657
--- /dev/null
+++ b/res/drawable-mdpi/ic_file_download_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_file_download_light.png b/res/drawable-mdpi/ic_file_download_light.png
new file mode 100644
index 0000000..d066170
--- /dev/null
+++ b/res/drawable-mdpi/ic_file_download_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_forward_dark.png b/res/drawable-mdpi/ic_forward_dark.png
new file mode 100644
index 0000000..7930393
--- /dev/null
+++ b/res/drawable-mdpi/ic_forward_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_history_light.png b/res/drawable-mdpi/ic_history_light.png
new file mode 100644
index 0000000..234b2ce
--- /dev/null
+++ b/res/drawable-mdpi/ic_history_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_image_light.png b/res/drawable-mdpi/ic_image_light.png
new file mode 100644
index 0000000..3bd919e
--- /dev/null
+++ b/res/drawable-mdpi/ic_image_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ime_dark.png b/res/drawable-mdpi/ic_ime_dark.png
new file mode 100644
index 0000000..8c0a156
--- /dev/null
+++ b/res/drawable-mdpi/ic_ime_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_ime_light.png b/res/drawable-mdpi/ic_ime_light.png
new file mode 100644
index 0000000..fd58cb5
--- /dev/null
+++ b/res/drawable-mdpi/ic_ime_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_info_dark.png b/res/drawable-mdpi/ic_info_dark.png
new file mode 100644
index 0000000..67457a3
--- /dev/null
+++ b/res/drawable-mdpi/ic_info_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_keyboard_arrow_left_light.png b/res/drawable-mdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..6004f37
--- /dev/null
+++ b/res/drawable-mdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_keyboard_arrow_right_light.png b/res/drawable-mdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..9bd625d
--- /dev/null
+++ b/res/drawable-mdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a6d2f55
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_audio_mic.png b/res/drawable-mdpi/ic_mp_audio_mic.png
new file mode 100644
index 0000000..918d799
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_audio_mic.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_camera_large_light.png b/res/drawable-mdpi/ic_mp_camera_large_light.png
new file mode 100644
index 0000000..3ec72f9
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_camera_large_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_camera_small_light.png b/res/drawable-mdpi/ic_mp_camera_small_light.png
new file mode 100644
index 0000000..fe1dc7e
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_camera_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_capture_stop_large_light.png b/res/drawable-mdpi/ic_mp_capture_stop_large_light.png
new file mode 100644
index 0000000..a9705c1
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_capture_stop_large_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_full_screen_light.png b/res/drawable-mdpi/ic_mp_full_screen_light.png
new file mode 100644
index 0000000..5462c59
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_full_screen_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_video_large_light.png b/res/drawable-mdpi/ic_mp_video_large_light.png
new file mode 100644
index 0000000..fa8e3e2
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_video_large_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_mp_video_small_light.png b/res/drawable-mdpi/ic_mp_video_small_light.png
new file mode 100644
index 0000000..9204827
--- /dev/null
+++ b/res/drawable-mdpi/ic_mp_video_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_notifications_off_dark.png b/res/drawable-mdpi/ic_notifications_off_dark.png
new file mode 100644
index 0000000..4a5a7e9
--- /dev/null
+++ b/res/drawable-mdpi/ic_notifications_off_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_notifications_off_light.png b/res/drawable-mdpi/ic_notifications_off_light.png
new file mode 100644
index 0000000..33e0b4a
--- /dev/null
+++ b/res/drawable-mdpi/ic_notifications_off_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_notifications_off_small_light.png b/res/drawable-mdpi/ic_notifications_off_small_light.png
new file mode 100644
index 0000000..4bd9563
--- /dev/null
+++ b/res/drawable-mdpi/ic_notifications_off_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_notifications_on_dark.png b/res/drawable-mdpi/ic_notifications_on_dark.png
new file mode 100644
index 0000000..77a2a7d
--- /dev/null
+++ b/res/drawable-mdpi/ic_notifications_on_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_notifications_on_light.png b/res/drawable-mdpi/ic_notifications_on_light.png
new file mode 100644
index 0000000..eae03a5
--- /dev/null
+++ b/res/drawable-mdpi/ic_notifications_on_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_numeric_dialpad.png b/res/drawable-mdpi/ic_numeric_dialpad.png
new file mode 100644
index 0000000..5802167
--- /dev/null
+++ b/res/drawable-mdpi/ic_numeric_dialpad.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_oobe_conv_list.png b/res/drawable-mdpi/ic_oobe_conv_list.png
new file mode 100644
index 0000000..1648f2e
--- /dev/null
+++ b/res/drawable-mdpi/ic_oobe_conv_list.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_oobe_freq_list.png b/res/drawable-mdpi/ic_oobe_freq_list.png
new file mode 100644
index 0000000..8d7d47f
--- /dev/null
+++ b/res/drawable-mdpi/ic_oobe_freq_list.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_open_in_new.png b/res/drawable-mdpi/ic_open_in_new.png
new file mode 100644
index 0000000..44ef9d5
--- /dev/null
+++ b/res/drawable-mdpi/ic_open_in_new.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_people_add_light.png b/res/drawable-mdpi/ic_people_add_light.png
new file mode 100644
index 0000000..4bc974f
--- /dev/null
+++ b/res/drawable-mdpi/ic_people_add_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_person_add_blue.png b/res/drawable-mdpi/ic_person_add_blue.png
new file mode 100644
index 0000000..29f9066
--- /dev/null
+++ b/res/drawable-mdpi/ic_person_add_blue.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_person_add_dark.png b/res/drawable-mdpi/ic_person_add_dark.png
new file mode 100644
index 0000000..6f1fd49
--- /dev/null
+++ b/res/drawable-mdpi/ic_person_add_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_person_add_light.png b/res/drawable-mdpi/ic_person_add_light.png
new file mode 100644
index 0000000..ba83709
--- /dev/null
+++ b/res/drawable-mdpi/ic_person_add_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_person_light.png b/res/drawable-mdpi/ic_person_light.png
new file mode 100644
index 0000000..5698835
--- /dev/null
+++ b/res/drawable-mdpi/ic_person_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_person_light_large.png b/res/drawable-mdpi/ic_person_light_large.png
new file mode 100644
index 0000000..7aa150b
--- /dev/null
+++ b/res/drawable-mdpi/ic_person_light_large.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_phone_small_light.png b/res/drawable-mdpi/ic_phone_small_light.png
new file mode 100644
index 0000000..dfcbba0
--- /dev/null
+++ b/res/drawable-mdpi/ic_phone_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_photo_library_light.png b/res/drawable-mdpi/ic_photo_library_light.png
new file mode 100644
index 0000000..322d486
--- /dev/null
+++ b/res/drawable-mdpi/ic_photo_library_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_preview_pause.png b/res/drawable-mdpi/ic_preview_pause.png
new file mode 100644
index 0000000..14046e2
--- /dev/null
+++ b/res/drawable-mdpi/ic_preview_pause.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_preview_play.png b/res/drawable-mdpi/ic_preview_play.png
new file mode 100644
index 0000000..8ad0b02
--- /dev/null
+++ b/res/drawable-mdpi/ic_preview_play.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_remove_light.png b/res/drawable-mdpi/ic_remove_light.png
new file mode 100644
index 0000000..0464885
--- /dev/null
+++ b/res/drawable-mdpi/ic_remove_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_remove_small_light.png b/res/drawable-mdpi/ic_remove_small_light.png
new file mode 100644
index 0000000..2131b6e
--- /dev/null
+++ b/res/drawable-mdpi/ic_remove_small_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_save_dark.png b/res/drawable-mdpi/ic_save_dark.png
new file mode 100644
index 0000000..f9b0de5
--- /dev/null
+++ b/res/drawable-mdpi/ic_save_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_save_light.png b/res/drawable-mdpi/ic_save_light.png
new file mode 100644
index 0000000..4faf5d5
--- /dev/null
+++ b/res/drawable-mdpi/ic_save_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_search_light.png b/res/drawable-mdpi/ic_search_light.png
new file mode 100644
index 0000000..b7e1de0
--- /dev/null
+++ b/res/drawable-mdpi/ic_search_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_send_dark.png b/res/drawable-mdpi/ic_send_dark.png
new file mode 100644
index 0000000..965a9f0
--- /dev/null
+++ b/res/drawable-mdpi/ic_send_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_send_light.png b/res/drawable-mdpi/ic_send_light.png
new file mode 100644
index 0000000..46d2dcd
--- /dev/null
+++ b/res/drawable-mdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_share_dark.png b/res/drawable-mdpi/ic_share_dark.png
new file mode 100644
index 0000000..c6ba4c9
--- /dev/null
+++ b/res/drawable-mdpi/ic_share_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_share_light.png b/res/drawable-mdpi/ic_share_light.png
new file mode 100644
index 0000000..362c862
--- /dev/null
+++ b/res/drawable-mdpi/ic_share_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sim_card_send.png b/res/drawable-mdpi/ic_sim_card_send.png
new file mode 100644
index 0000000..1f26017
--- /dev/null
+++ b/res/drawable-mdpi/ic_sim_card_send.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sms_delivery_ok.png b/res/drawable-mdpi/ic_sms_delivery_ok.png
new file mode 100644
index 0000000..ffccf64
--- /dev/null
+++ b/res/drawable-mdpi/ic_sms_delivery_ok.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sms_failed_light.png b/res/drawable-mdpi/ic_sms_failed_light.png
new file mode 100644
index 0000000..9914e3f
--- /dev/null
+++ b/res/drawable-mdpi/ic_sms_failed_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sms_light.png b/res/drawable-mdpi/ic_sms_light.png
new file mode 100644
index 0000000..518c989
--- /dev/null
+++ b/res/drawable-mdpi/ic_sms_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sms_multi_light.png b/res/drawable-mdpi/ic_sms_multi_light.png
new file mode 100644
index 0000000..bd6f4c6
--- /dev/null
+++ b/res/drawable-mdpi/ic_sms_multi_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_tooltip_arrow.png b/res/drawable-mdpi/ic_tooltip_arrow.png
new file mode 100644
index 0000000..51824f5
--- /dev/null
+++ b/res/drawable-mdpi/ic_tooltip_arrow.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_play_light.png b/res/drawable-mdpi/ic_video_play_light.png
new file mode 100644
index 0000000..e0ac1e4
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_play_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wear_reply.png b/res/drawable-mdpi/ic_wear_reply.png
new file mode 100644
index 0000000..47bb396
--- /dev/null
+++ b/res/drawable-mdpi/ic_wear_reply.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_avatar_shadow.png b/res/drawable-mdpi/ic_widget_avatar_shadow.png
new file mode 100644
index 0000000..1a8dc8e
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_avatar_shadow.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_list.png b/res/drawable-mdpi/ic_widget_list.png
new file mode 100644
index 0000000..9bf4b11
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_list.png
Binary files differ
diff --git a/res/drawable-mdpi/msg_bubble_error.9.png b/res/drawable-mdpi/msg_bubble_error.9.png
new file mode 100644
index 0000000..694d117
--- /dev/null
+++ b/res/drawable-mdpi/msg_bubble_error.9.png
Binary files differ
diff --git a/res/drawable-mdpi/msg_bubble_incoming.9.png b/res/drawable-mdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..4e7e464
--- /dev/null
+++ b/res/drawable-mdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-mdpi/msg_bubble_input.9.png b/res/drawable-mdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..1810ffb
--- /dev/null
+++ b/res/drawable-mdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-mdpi/msg_bubble_outgoing.9.png b/res/drawable-mdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..5ca9b14
--- /dev/null
+++ b/res/drawable-mdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-mdpi/permissions.png b/res/drawable-mdpi/permissions.png
new file mode 100644
index 0000000..2d754ec
--- /dev/null
+++ b/res/drawable-mdpi/permissions.png
Binary files differ
diff --git a/res/drawable-mdpi/swipe_shadow.9.png b/res/drawable-mdpi/swipe_shadow.9.png
new file mode 100644
index 0000000..e2ed6d4
--- /dev/null
+++ b/res/drawable-mdpi/swipe_shadow.9.png
Binary files differ
diff --git a/res/drawable-mdpi/swipe_shadow_drag.9.png b/res/drawable-mdpi/swipe_shadow_drag.9.png
new file mode 100644
index 0000000..94be3f9
--- /dev/null
+++ b/res/drawable-mdpi/swipe_shadow_drag.9.png
Binary files differ
diff --git a/res/drawable-mdpi/sym_keyboard_delete_holo.png b/res/drawable-mdpi/sym_keyboard_delete_holo.png
new file mode 100644
index 0000000..7b3103b
--- /dev/null
+++ b/res/drawable-mdpi/sym_keyboard_delete_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_btn_bg_normal.9.png b/res/drawable-mdpi/tab_btn_bg_normal.9.png
new file mode 100644
index 0000000..d56cd94
--- /dev/null
+++ b/res/drawable-mdpi/tab_btn_bg_normal.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_btn_bg_pressed.9.png b/res/drawable-mdpi/tab_btn_bg_pressed.9.png
new file mode 100644
index 0000000..4d4c932
--- /dev/null
+++ b/res/drawable-mdpi/tab_btn_bg_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected.9.png b/res/drawable-mdpi/tab_selected.9.png
new file mode 100644
index 0000000..4b00f35
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_focused_holo.9.png b/res/drawable-mdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..c77808d
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..1e28bb6
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected_pressed_holo.9.png b/res/drawable-mdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..098bf7d
--- /dev/null
+++ b/res/drawable-mdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected.9.png b/res/drawable-mdpi/tab_unselected.9.png
new file mode 100644
index 0000000..bb45ab9
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..e00e256
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..80e8f0c
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected_pressed_holo.9.png b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..eb9422e
--- /dev/null
+++ b/res/drawable-mdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_hr.9.png b/res/drawable-mdpi/widget_hr.9.png
new file mode 100644
index 0000000..93f3417
--- /dev/null
+++ b/res/drawable-mdpi/widget_hr.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_msg_bubble_incoming.9.png b/res/drawable-mdpi/widget_msg_bubble_incoming.9.png
new file mode 100644
index 0000000..ddadf73
--- /dev/null
+++ b/res/drawable-mdpi/widget_msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-mdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-mdpi/widget_msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..00432c3
--- /dev/null
+++ b/res/drawable-mdpi/widget_msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-nodpi/bg_sms.jpg b/res/drawable-nodpi/bg_sms.jpg
new file mode 100644
index 0000000..d8711e7
--- /dev/null
+++ b/res/drawable-nodpi/bg_sms.jpg
Binary files differ
diff --git a/res/drawable-nodpi/ic_person_wear.png b/res/drawable-nodpi/ic_person_wear.png
new file mode 100644
index 0000000..97be017
--- /dev/null
+++ b/res/drawable-nodpi/ic_person_wear.png
Binary files differ
diff --git a/res/drawable-nodpi/messenger_widget_conversation_preview.png b/res/drawable-nodpi/messenger_widget_conversation_preview.png
new file mode 100644
index 0000000..e26ae97
--- /dev/null
+++ b/res/drawable-nodpi/messenger_widget_conversation_preview.png
Binary files differ
diff --git a/res/drawable-nodpi/messenger_widget_preview.png b/res/drawable-nodpi/messenger_widget_preview.png
new file mode 100644
index 0000000..a5d40b2
--- /dev/null
+++ b/res/drawable-nodpi/messenger_widget_preview.png
Binary files differ
diff --git a/res/drawable-v21/contact_picker_tab_background_selector.xml b/res/drawable-v21/contact_picker_tab_background_selector.xml
new file mode 100644
index 0000000..59bdf95
--- /dev/null
+++ b/res/drawable-v21/contact_picker_tab_background_selector.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/contact_picker_tab_pressed" /> \ No newline at end of file
diff --git a/res/drawable-v21/fab_new_message_bg.xml b/res/drawable-v21/fab_new_message_bg.xml
new file mode 100644
index 0000000..8043f0f
--- /dev/null
+++ b/res/drawable-v21/fab_new_message_bg.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/fab_ripple" >
+
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/fab_color" />
+ </shape>
+ </item>
+
+</ripple> \ No newline at end of file
diff --git a/res/drawable-v21/gallery_image_background_selector.xml b/res/drawable-v21/gallery_image_background_selector.xml
new file mode 100644
index 0000000..7486fad
--- /dev/null
+++ b/res/drawable-v21/gallery_image_background_selector.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/gallery_image_pressed">
+ <item android:id="@android:id/mask"
+ android:drawable="@android:color/white" />
+</ripple>
diff --git a/res/drawable-v21/mediapicker_tab_button_background.xml b/res/drawable-v21/mediapicker_tab_button_background.xml
new file mode 100644
index 0000000..10c4474
--- /dev/null
+++ b/res/drawable-v21/mediapicker_tab_button_background.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/background_item_pressed" />
diff --git a/res/drawable-v21/transparent_button_background.xml b/res/drawable-v21/transparent_button_background.xml
new file mode 100644
index 0000000..10c4474
--- /dev/null
+++ b/res/drawable-v21/transparent_button_background.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/background_item_pressed" />
diff --git a/res/drawable-v21/widget_item_background.xml b/res/drawable-v21/widget_item_background.xml
new file mode 100644
index 0000000..eb20c06
--- /dev/null
+++ b/res/drawable-v21/widget_item_background.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/ripple_material_light" >
+ <item android:id="@android:id/mask">
+ <color android:color="@android:color/white" />
+ </item>
+</ripple>
diff --git a/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..96eb10e
--- /dev/null
+++ b/res/drawable-xhdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..10986ee
--- /dev/null
+++ b/res/drawable-xhdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..12f9a82
--- /dev/null
+++ b/res/drawable-xhdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..e7590b0
--- /dev/null
+++ b/res/drawable-xhdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/cab_bg.9.png b/res/drawable-xhdpi/cab_bg.9.png
new file mode 100644
index 0000000..ba8c446
--- /dev/null
+++ b/res/drawable-xhdpi/cab_bg.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/contact_popup_background.9.png b/res/drawable-xhdpi/contact_popup_background.9.png
new file mode 100644
index 0000000..f8b809a
--- /dev/null
+++ b/res/drawable-xhdpi/contact_popup_background.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/fab_new_message_static_shadow.png b/res/drawable-xhdpi/fab_new_message_static_shadow.png
new file mode 100644
index 0000000..7075cb9
--- /dev/null
+++ b/res/drawable-xhdpi/fab_new_message_static_shadow.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_add_gray.png b/res/drawable-xhdpi/ic_add_gray.png
new file mode 100644
index 0000000..8ce295d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_add_gray.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_add_white.png b/res/drawable-xhdpi/ic_add_white.png
new file mode 100644
index 0000000..7ef4fc1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_add_white.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_archive_small_dark.png b/res/drawable-xhdpi/ic_archive_small_dark.png
new file mode 100644
index 0000000..ea31204
--- /dev/null
+++ b/res/drawable-xhdpi/ic_archive_small_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_archive_small_light.png b/res/drawable-xhdpi/ic_archive_small_light.png
new file mode 100644
index 0000000..b3e628f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_archive_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_archive_undo_small_dark.png b/res/drawable-xhdpi/ic_archive_undo_small_dark.png
new file mode 100644
index 0000000..7508484
--- /dev/null
+++ b/res/drawable-xhdpi/ic_archive_undo_small_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_archive_undo_small_light.png b/res/drawable-xhdpi/ic_archive_undo_small_light.png
new file mode 100644
index 0000000..b7038d8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_archive_undo_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_arrow_back_dark.png b/res/drawable-xhdpi/ic_arrow_back_dark.png
new file mode 100644
index 0000000..c40b832
--- /dev/null
+++ b/res/drawable-xhdpi/ic_arrow_back_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_arrow_back_light.png b/res/drawable-xhdpi/ic_arrow_back_light.png
new file mode 100644
index 0000000..fa3d493
--- /dev/null
+++ b/res/drawable-xhdpi/ic_arrow_back_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_attachment_dark.png b/res/drawable-xhdpi/ic_attachment_dark.png
new file mode 100644
index 0000000..61e565c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_attachment_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_audio_light.png b/res/drawable-xhdpi/ic_audio_light.png
new file mode 100644
index 0000000..fab5ba4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_audio_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_audio_pause.png b/res/drawable-xhdpi/ic_audio_pause.png
new file mode 100644
index 0000000..e33b655
--- /dev/null
+++ b/res/drawable-xhdpi/ic_audio_pause.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_audio_play.png b/res/drawable-xhdpi/ic_audio_play.png
new file mode 100644
index 0000000..ff93063
--- /dev/null
+++ b/res/drawable-xhdpi/ic_audio_play.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_camera_front_light.png b/res/drawable-xhdpi/ic_camera_front_light.png
new file mode 100644
index 0000000..9cd577d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_camera_front_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_camera_light.png b/res/drawable-xhdpi/ic_camera_light.png
new file mode 100644
index 0000000..3b74ad6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_camera_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_camera_rear_light.png b/res/drawable-xhdpi/ic_camera_rear_light.png
new file mode 100644
index 0000000..92727af
--- /dev/null
+++ b/res/drawable-xhdpi/ic_camera_rear_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_cancel_small_dark.png b/res/drawable-xhdpi/ic_cancel_small_dark.png
new file mode 100644
index 0000000..8b34b9b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_cancel_small_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_cancel_small_light.png b/res/drawable-xhdpi/ic_cancel_small_light.png
new file mode 100644
index 0000000..dea984f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_cancel_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkbox_blank_light.png b/res/drawable-xhdpi/ic_checkbox_blank_light.png
new file mode 100644
index 0000000..7f6e917
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkbox_blank_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkbox_light.png b/res/drawable-xhdpi/ic_checkbox_light.png
new file mode 100644
index 0000000..9cb2b7e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkbox_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkbox_outline_light.png b/res/drawable-xhdpi/ic_checkbox_outline_light.png
new file mode 100644
index 0000000..64f0a2b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkbox_outline_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkmark_circle_blue.png b/res/drawable-xhdpi/ic_checkmark_circle_blue.png
new file mode 100644
index 0000000..211a405
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkmark_circle_blue.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkmark_large.png b/res/drawable-xhdpi/ic_checkmark_large.png
new file mode 100644
index 0000000..fd4420b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkmark_large.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkmark_large_light.png b/res/drawable-xhdpi/ic_checkmark_large_light.png
new file mode 100644
index 0000000..d841688
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkmark_large_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkmark_small_blue.png b/res/drawable-xhdpi/ic_checkmark_small_blue.png
new file mode 100644
index 0000000..9fc4a4b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkmark_small_blue.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_checkmark_small_light.png b/res/drawable-xhdpi/ic_checkmark_small_light.png
new file mode 100644
index 0000000..cd084fb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_checkmark_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_color_lens.png b/res/drawable-xhdpi/ic_color_lens.png
new file mode 100644
index 0000000..8887429
--- /dev/null
+++ b/res/drawable-xhdpi/ic_color_lens.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_content_copy_dark.png b/res/drawable-xhdpi/ic_content_copy_dark.png
new file mode 100644
index 0000000..0114555
--- /dev/null
+++ b/res/drawable-xhdpi/ic_content_copy_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_crying_hero.png b/res/drawable-xhdpi/ic_crying_hero.png
new file mode 100644
index 0000000..ccc40ab
--- /dev/null
+++ b/res/drawable-xhdpi/ic_crying_hero.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_delete_small_dark.png b/res/drawable-xhdpi/ic_delete_small_dark.png
new file mode 100644
index 0000000..b9b0cbd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_delete_small_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_delete_small_light.png b/res/drawable-xhdpi/ic_delete_small_light.png
new file mode 100644
index 0000000..7c45f70
--- /dev/null
+++ b/res/drawable-xhdpi/ic_delete_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dnd_on_dark.png b/res/drawable-xhdpi/ic_dnd_on_dark.png
new file mode 100644
index 0000000..c40fe99
--- /dev/null
+++ b/res/drawable-xhdpi/ic_dnd_on_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dnd_on_light.png b/res/drawable-xhdpi/ic_dnd_on_light.png
new file mode 100644
index 0000000..e0527f5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_dnd_on_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_failed_light.png b/res/drawable-xhdpi/ic_failed_light.png
new file mode 100644
index 0000000..33d3436
--- /dev/null
+++ b/res/drawable-xhdpi/ic_failed_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_failed_status_red.png b/res/drawable-xhdpi/ic_failed_status_red.png
new file mode 100644
index 0000000..3c6616a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_failed_status_red.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_file_download_dark.png b/res/drawable-xhdpi/ic_file_download_dark.png
new file mode 100644
index 0000000..c31257a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_file_download_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_file_download_light.png b/res/drawable-xhdpi/ic_file_download_light.png
new file mode 100644
index 0000000..b894fa6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_file_download_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_forward_dark.png b/res/drawable-xhdpi/ic_forward_dark.png
new file mode 100644
index 0000000..b62f0de
--- /dev/null
+++ b/res/drawable-xhdpi/ic_forward_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_history_light.png b/res/drawable-xhdpi/ic_history_light.png
new file mode 100644
index 0000000..23585a2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_history_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_image_light.png b/res/drawable-xhdpi/ic_image_light.png
new file mode 100644
index 0000000..b2aecfb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_image_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ime_dark.png b/res/drawable-xhdpi/ic_ime_dark.png
new file mode 100644
index 0000000..cd5c5c6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ime_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_ime_light.png b/res/drawable-xhdpi/ic_ime_light.png
new file mode 100644
index 0000000..14b57f8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_ime_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_info_dark.png b/res/drawable-xhdpi/ic_info_dark.png
new file mode 100644
index 0000000..31d1e05
--- /dev/null
+++ b/res/drawable-xhdpi/ic_info_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-xhdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..464a0bc
--- /dev/null
+++ b/res/drawable-xhdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-xhdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..51a1903
--- /dev/null
+++ b/res/drawable-xhdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..3c82a06
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_audio_mic.png b/res/drawable-xhdpi/ic_mp_audio_mic.png
new file mode 100644
index 0000000..b35d0f1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_audio_mic.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_camera_large_light.png b/res/drawable-xhdpi/ic_mp_camera_large_light.png
new file mode 100644
index 0000000..5552d92
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_camera_large_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_camera_small_light.png b/res/drawable-xhdpi/ic_mp_camera_small_light.png
new file mode 100644
index 0000000..75094e1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_camera_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_capture_stop_large_light.png b/res/drawable-xhdpi/ic_mp_capture_stop_large_light.png
new file mode 100644
index 0000000..62125b3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_capture_stop_large_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_full_screen_light.png b/res/drawable-xhdpi/ic_mp_full_screen_light.png
new file mode 100644
index 0000000..02cbd48
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_full_screen_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_video_large_light.png b/res/drawable-xhdpi/ic_mp_video_large_light.png
new file mode 100644
index 0000000..723b052
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_video_large_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_mp_video_small_light.png b/res/drawable-xhdpi/ic_mp_video_small_light.png
new file mode 100644
index 0000000..00fde5f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_mp_video_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_notifications_off_dark.png b/res/drawable-xhdpi/ic_notifications_off_dark.png
new file mode 100644
index 0000000..4f3b924
--- /dev/null
+++ b/res/drawable-xhdpi/ic_notifications_off_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_notifications_off_light.png b/res/drawable-xhdpi/ic_notifications_off_light.png
new file mode 100644
index 0000000..16ae132
--- /dev/null
+++ b/res/drawable-xhdpi/ic_notifications_off_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_notifications_off_small_light.png b/res/drawable-xhdpi/ic_notifications_off_small_light.png
new file mode 100644
index 0000000..e54fc52
--- /dev/null
+++ b/res/drawable-xhdpi/ic_notifications_off_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_notifications_on_dark.png b/res/drawable-xhdpi/ic_notifications_on_dark.png
new file mode 100644
index 0000000..40f2909
--- /dev/null
+++ b/res/drawable-xhdpi/ic_notifications_on_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_notifications_on_light.png b/res/drawable-xhdpi/ic_notifications_on_light.png
new file mode 100644
index 0000000..3c44f93
--- /dev/null
+++ b/res/drawable-xhdpi/ic_notifications_on_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_numeric_dialpad.png b/res/drawable-xhdpi/ic_numeric_dialpad.png
new file mode 100644
index 0000000..b22360f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_numeric_dialpad.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_oobe_conv_list.png b/res/drawable-xhdpi/ic_oobe_conv_list.png
new file mode 100644
index 0000000..b41d322
--- /dev/null
+++ b/res/drawable-xhdpi/ic_oobe_conv_list.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_oobe_freq_list.png b/res/drawable-xhdpi/ic_oobe_freq_list.png
new file mode 100644
index 0000000..898daf5
--- /dev/null
+++ b/res/drawable-xhdpi/ic_oobe_freq_list.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_open_in_new.png b/res/drawable-xhdpi/ic_open_in_new.png
new file mode 100644
index 0000000..363a35d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_open_in_new.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_people_add_light.png b/res/drawable-xhdpi/ic_people_add_light.png
new file mode 100644
index 0000000..f57f056
--- /dev/null
+++ b/res/drawable-xhdpi/ic_people_add_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_person_add_blue.png b/res/drawable-xhdpi/ic_person_add_blue.png
new file mode 100644
index 0000000..4e76e64
--- /dev/null
+++ b/res/drawable-xhdpi/ic_person_add_blue.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_person_add_dark.png b/res/drawable-xhdpi/ic_person_add_dark.png
new file mode 100644
index 0000000..6f325cf
--- /dev/null
+++ b/res/drawable-xhdpi/ic_person_add_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_person_add_light.png b/res/drawable-xhdpi/ic_person_add_light.png
new file mode 100644
index 0000000..840f063
--- /dev/null
+++ b/res/drawable-xhdpi/ic_person_add_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_person_light.png b/res/drawable-xhdpi/ic_person_light.png
new file mode 100644
index 0000000..5d23639
--- /dev/null
+++ b/res/drawable-xhdpi/ic_person_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_person_light_large.png b/res/drawable-xhdpi/ic_person_light_large.png
new file mode 100644
index 0000000..22a4ff1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_person_light_large.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_phone_small_light.png b/res/drawable-xhdpi/ic_phone_small_light.png
new file mode 100644
index 0000000..b2cab28
--- /dev/null
+++ b/res/drawable-xhdpi/ic_phone_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_photo_library_light.png b/res/drawable-xhdpi/ic_photo_library_light.png
new file mode 100644
index 0000000..1f5a69c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_photo_library_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_preview_pause.png b/res/drawable-xhdpi/ic_preview_pause.png
new file mode 100644
index 0000000..bc0319d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_preview_pause.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_preview_play.png b/res/drawable-xhdpi/ic_preview_play.png
new file mode 100644
index 0000000..dc3ac1e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_preview_play.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_remove_light.png b/res/drawable-xhdpi/ic_remove_light.png
new file mode 100644
index 0000000..cbaf6d0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_remove_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_remove_small_light.png b/res/drawable-xhdpi/ic_remove_small_light.png
new file mode 100644
index 0000000..222b86c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_remove_small_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_save_dark.png b/res/drawable-xhdpi/ic_save_dark.png
new file mode 100644
index 0000000..7927ba6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_save_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_save_light.png b/res/drawable-xhdpi/ic_save_light.png
new file mode 100644
index 0000000..298bb47
--- /dev/null
+++ b/res/drawable-xhdpi/ic_save_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_light.png b/res/drawable-xhdpi/ic_search_light.png
new file mode 100644
index 0000000..b83d895
--- /dev/null
+++ b/res/drawable-xhdpi/ic_search_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_send_dark.png b/res/drawable-xhdpi/ic_send_dark.png
new file mode 100644
index 0000000..a6b3699
--- /dev/null
+++ b/res/drawable-xhdpi/ic_send_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_send_light.png b/res/drawable-xhdpi/ic_send_light.png
new file mode 100644
index 0000000..a19b335
--- /dev/null
+++ b/res/drawable-xhdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_share_dark.png b/res/drawable-xhdpi/ic_share_dark.png
new file mode 100644
index 0000000..a4b822a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_share_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_share_light.png b/res/drawable-xhdpi/ic_share_light.png
new file mode 100644
index 0000000..711797c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_share_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sim_card_send.png b/res/drawable-xhdpi/ic_sim_card_send.png
new file mode 100644
index 0000000..9fc7e9a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sim_card_send.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sms_delivery_ok.png b/res/drawable-xhdpi/ic_sms_delivery_ok.png
new file mode 100644
index 0000000..c12ae0a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sms_delivery_ok.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sms_failed_light.png b/res/drawable-xhdpi/ic_sms_failed_light.png
new file mode 100644
index 0000000..90c899a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sms_failed_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sms_light.png b/res/drawable-xhdpi/ic_sms_light.png
new file mode 100644
index 0000000..4b24a4b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sms_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sms_multi_light.png b/res/drawable-xhdpi/ic_sms_multi_light.png
new file mode 100644
index 0000000..f3ba620
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sms_multi_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_tooltip_arrow.png b/res/drawable-xhdpi/ic_tooltip_arrow.png
new file mode 100644
index 0000000..4a59931
--- /dev/null
+++ b/res/drawable-xhdpi/ic_tooltip_arrow.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_play_light.png b/res/drawable-xhdpi/ic_video_play_light.png
new file mode 100644
index 0000000..89397a8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_play_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wear_reply.png b/res/drawable-xhdpi/ic_wear_reply.png
new file mode 100644
index 0000000..c909c59
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wear_reply.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_avatar_shadow.png b/res/drawable-xhdpi/ic_widget_avatar_shadow.png
new file mode 100644
index 0000000..37600aa
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_avatar_shadow.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_list.png b/res/drawable-xhdpi/ic_widget_list.png
new file mode 100644
index 0000000..a12764f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_list.png
Binary files differ
diff --git a/res/drawable-xhdpi/msg_bubble_error.9.png b/res/drawable-xhdpi/msg_bubble_error.9.png
new file mode 100644
index 0000000..96b139b
--- /dev/null
+++ b/res/drawable-xhdpi/msg_bubble_error.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/msg_bubble_incoming.9.png b/res/drawable-xhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..0661fdb
--- /dev/null
+++ b/res/drawable-xhdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/msg_bubble_input.9.png b/res/drawable-xhdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..67f92ff
--- /dev/null
+++ b/res/drawable-xhdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/msg_bubble_outgoing.9.png b/res/drawable-xhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..52cb936
--- /dev/null
+++ b/res/drawable-xhdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/permissions.png b/res/drawable-xhdpi/permissions.png
new file mode 100644
index 0000000..39a81bc
--- /dev/null
+++ b/res/drawable-xhdpi/permissions.png
Binary files differ
diff --git a/res/drawable-xhdpi/swipe_shadow.9.png b/res/drawable-xhdpi/swipe_shadow.9.png
new file mode 100644
index 0000000..45d965d
--- /dev/null
+++ b/res/drawable-xhdpi/swipe_shadow.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/swipe_shadow_drag.9.png b/res/drawable-xhdpi/swipe_shadow_drag.9.png
new file mode 100644
index 0000000..b443b5c
--- /dev/null
+++ b/res/drawable-xhdpi/swipe_shadow_drag.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/sym_keyboard_delete_holo.png b/res/drawable-xhdpi/sym_keyboard_delete_holo.png
new file mode 100644
index 0000000..16b58a0
--- /dev/null
+++ b/res/drawable-xhdpi/sym_keyboard_delete_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_btn_bg_normal.9.png b/res/drawable-xhdpi/tab_btn_bg_normal.9.png
new file mode 100644
index 0000000..d9446c6
--- /dev/null
+++ b/res/drawable-xhdpi/tab_btn_bg_normal.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_btn_bg_pressed.9.png b/res/drawable-xhdpi/tab_btn_bg_pressed.9.png
new file mode 100644
index 0000000..de76c06
--- /dev/null
+++ b/res/drawable-xhdpi/tab_btn_bg_pressed.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected.9.png b/res/drawable-xhdpi/tab_selected.9.png
new file mode 100644
index 0000000..95e5f43
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..f98b4d9
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..e8f2cb6
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..1882927
--- /dev/null
+++ b/res/drawable-xhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected.9.png b/res/drawable-xhdpi/tab_unselected.9.png
new file mode 100644
index 0000000..8cede8d
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..28a7ec5
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..b9962b1
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..dadc471
--- /dev/null
+++ b/res/drawable-xhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_hr.9.png b/res/drawable-xhdpi/widget_hr.9.png
new file mode 100644
index 0000000..31fcc7e
--- /dev/null
+++ b/res/drawable-xhdpi/widget_hr.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_msg_bubble_incoming.9.png b/res/drawable-xhdpi/widget_msg_bubble_incoming.9.png
new file mode 100644
index 0000000..2abb496
--- /dev/null
+++ b/res/drawable-xhdpi/widget_msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-xhdpi/widget_msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..35b58cd
--- /dev/null
+++ b/res/drawable-xhdpi/widget_msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..cc880c2
--- /dev/null
+++ b/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..60ccf97
--- /dev/null
+++ b/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..0358907
--- /dev/null
+++ b/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..adcddc9
--- /dev/null
+++ b/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/cab_bg.9.png b/res/drawable-xxhdpi/cab_bg.9.png
new file mode 100644
index 0000000..b75401e
--- /dev/null
+++ b/res/drawable-xxhdpi/cab_bg.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/contact_popup_background.9.png b/res/drawable-xxhdpi/contact_popup_background.9.png
new file mode 100644
index 0000000..bb4f281
--- /dev/null
+++ b/res/drawable-xxhdpi/contact_popup_background.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/fab_new_message_static_shadow.png b/res/drawable-xxhdpi/fab_new_message_static_shadow.png
new file mode 100644
index 0000000..adfd569
--- /dev/null
+++ b/res/drawable-xxhdpi/fab_new_message_static_shadow.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_add_gray.png b/res/drawable-xxhdpi/ic_add_gray.png
new file mode 100644
index 0000000..8273830
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_add_gray.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_add_white.png b/res/drawable-xxhdpi/ic_add_white.png
new file mode 100644
index 0000000..7b4d52c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_add_white.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_archive_small_dark.png b/res/drawable-xxhdpi/ic_archive_small_dark.png
new file mode 100644
index 0000000..64aa9d5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_archive_small_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_archive_small_light.png b/res/drawable-xxhdpi/ic_archive_small_light.png
new file mode 100644
index 0000000..042ed27
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_archive_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_archive_undo_small_dark.png b/res/drawable-xxhdpi/ic_archive_undo_small_dark.png
new file mode 100644
index 0000000..a3f7503
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_archive_undo_small_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_archive_undo_small_light.png b/res/drawable-xxhdpi/ic_archive_undo_small_light.png
new file mode 100644
index 0000000..cd9d0d5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_archive_undo_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_dark.png b/res/drawable-xxhdpi/ic_arrow_back_dark.png
new file mode 100644
index 0000000..1c71cb0
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_arrow_back_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_light.png b/res/drawable-xxhdpi/ic_arrow_back_light.png
new file mode 100644
index 0000000..8b532d0
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_arrow_back_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_attachment_dark.png b/res/drawable-xxhdpi/ic_attachment_dark.png
new file mode 100644
index 0000000..64d9dad
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_attachment_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_audio_light.png b/res/drawable-xxhdpi/ic_audio_light.png
new file mode 100644
index 0000000..9b2f05c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_audio_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_audio_pause.png b/res/drawable-xxhdpi/ic_audio_pause.png
new file mode 100644
index 0000000..d294f3c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_audio_pause.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_audio_play.png b/res/drawable-xxhdpi/ic_audio_play.png
new file mode 100644
index 0000000..cb84ee6
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_audio_play.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_camera_front_light.png b/res/drawable-xxhdpi/ic_camera_front_light.png
new file mode 100644
index 0000000..53616e4
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_camera_front_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_camera_light.png b/res/drawable-xxhdpi/ic_camera_light.png
new file mode 100644
index 0000000..64e9873
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_camera_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_camera_rear_light.png b/res/drawable-xxhdpi/ic_camera_rear_light.png
new file mode 100644
index 0000000..8e478e3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_camera_rear_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_cancel_small_dark.png b/res/drawable-xxhdpi/ic_cancel_small_dark.png
new file mode 100644
index 0000000..c8f583c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_cancel_small_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_cancel_small_light.png b/res/drawable-xxhdpi/ic_cancel_small_light.png
new file mode 100644
index 0000000..b3eacda
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_cancel_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkbox_blank_light.png b/res/drawable-xxhdpi/ic_checkbox_blank_light.png
new file mode 100644
index 0000000..0556578
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkbox_blank_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkbox_light.png b/res/drawable-xxhdpi/ic_checkbox_light.png
new file mode 100644
index 0000000..3a1896b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkbox_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkbox_outline_light.png b/res/drawable-xxhdpi/ic_checkbox_outline_light.png
new file mode 100644
index 0000000..cc77c5e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkbox_outline_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkmark_circle_blue.png b/res/drawable-xxhdpi/ic_checkmark_circle_blue.png
new file mode 100644
index 0000000..bede5b9
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkmark_circle_blue.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkmark_large.png b/res/drawable-xxhdpi/ic_checkmark_large.png
new file mode 100644
index 0000000..1be0e02
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkmark_large.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkmark_large_light.png b/res/drawable-xxhdpi/ic_checkmark_large_light.png
new file mode 100644
index 0000000..b5f4e24
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkmark_large_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkmark_small_blue.png b/res/drawable-xxhdpi/ic_checkmark_small_blue.png
new file mode 100644
index 0000000..65d3e5c
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkmark_small_blue.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_checkmark_small_light.png b/res/drawable-xxhdpi/ic_checkmark_small_light.png
new file mode 100644
index 0000000..7aeac03
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_checkmark_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_color_lens.png b/res/drawable-xxhdpi/ic_color_lens.png
new file mode 100644
index 0000000..6fd56fd
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_color_lens.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_content_copy_dark.png b/res/drawable-xxhdpi/ic_content_copy_dark.png
new file mode 100644
index 0000000..e3518ae
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_content_copy_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_crying_hero.png b/res/drawable-xxhdpi/ic_crying_hero.png
new file mode 100644
index 0000000..46538b5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_crying_hero.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_delete_small_dark.png b/res/drawable-xxhdpi/ic_delete_small_dark.png
new file mode 100644
index 0000000..146ac39
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_delete_small_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_delete_small_light.png b/res/drawable-xxhdpi/ic_delete_small_light.png
new file mode 100644
index 0000000..b739ac9
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_delete_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_dnd_on_dark.png b/res/drawable-xxhdpi/ic_dnd_on_dark.png
new file mode 100644
index 0000000..a9ccb88
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_dnd_on_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_dnd_on_light.png b/res/drawable-xxhdpi/ic_dnd_on_light.png
new file mode 100644
index 0000000..3ddaa13
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_dnd_on_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_failed_light.png b/res/drawable-xxhdpi/ic_failed_light.png
new file mode 100644
index 0000000..7612138
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_failed_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_failed_status_red.png b/res/drawable-xxhdpi/ic_failed_status_red.png
new file mode 100644
index 0000000..26f76cc
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_failed_status_red.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_file_download_dark.png b/res/drawable-xxhdpi/ic_file_download_dark.png
new file mode 100644
index 0000000..d7fc0b3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_file_download_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_file_download_light.png b/res/drawable-xxhdpi/ic_file_download_light.png
new file mode 100644
index 0000000..42eadbc
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_file_download_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_forward_dark.png b/res/drawable-xxhdpi/ic_forward_dark.png
new file mode 100644
index 0000000..cd071be
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_forward_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_history_light.png b/res/drawable-xxhdpi/ic_history_light.png
new file mode 100644
index 0000000..bd430f8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_history_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_image_light.png b/res/drawable-xxhdpi/ic_image_light.png
new file mode 100644
index 0000000..16b7e7b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_image_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ime_dark.png b/res/drawable-xxhdpi/ic_ime_dark.png
new file mode 100644
index 0000000..a32bd46
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ime_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_ime_light.png b/res/drawable-xxhdpi/ic_ime_light.png
new file mode 100644
index 0000000..159d085
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_ime_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_info_dark.png b/res/drawable-xxhdpi/ic_info_dark.png
new file mode 100644
index 0000000..2df42de
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_info_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-xxhdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..09536ed
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-xxhdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..62ca382
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..0d61198
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_audio_mic.png b/res/drawable-xxhdpi/ic_mp_audio_mic.png
new file mode 100644
index 0000000..a01388b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_audio_mic.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_camera_large_light.png b/res/drawable-xxhdpi/ic_mp_camera_large_light.png
new file mode 100644
index 0000000..37fa935
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_camera_large_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_camera_small_light.png b/res/drawable-xxhdpi/ic_mp_camera_small_light.png
new file mode 100644
index 0000000..8012fce
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_camera_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_capture_stop_large_light.png b/res/drawable-xxhdpi/ic_mp_capture_stop_large_light.png
new file mode 100644
index 0000000..d34667a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_capture_stop_large_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_full_screen_light.png b/res/drawable-xxhdpi/ic_mp_full_screen_light.png
new file mode 100644
index 0000000..483a1a3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_full_screen_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_video_large_light.png b/res/drawable-xxhdpi/ic_mp_video_large_light.png
new file mode 100644
index 0000000..0a34b5e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_video_large_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_mp_video_small_light.png b/res/drawable-xxhdpi/ic_mp_video_small_light.png
new file mode 100644
index 0000000..1cb7798
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_mp_video_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_notifications_off_dark.png b/res/drawable-xxhdpi/ic_notifications_off_dark.png
new file mode 100644
index 0000000..27d3754
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_notifications_off_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_notifications_off_light.png b/res/drawable-xxhdpi/ic_notifications_off_light.png
new file mode 100644
index 0000000..a2aedec
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_notifications_off_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_notifications_off_small_light.png b/res/drawable-xxhdpi/ic_notifications_off_small_light.png
new file mode 100644
index 0000000..2a90e07
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_notifications_off_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_notifications_on_dark.png b/res/drawable-xxhdpi/ic_notifications_on_dark.png
new file mode 100644
index 0000000..e64da6b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_notifications_on_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_notifications_on_light.png b/res/drawable-xxhdpi/ic_notifications_on_light.png
new file mode 100644
index 0000000..f8f7d15
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_notifications_on_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_numeric_dialpad.png b/res/drawable-xxhdpi/ic_numeric_dialpad.png
new file mode 100644
index 0000000..a0d9e5e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_numeric_dialpad.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_oobe_conv_list.png b/res/drawable-xxhdpi/ic_oobe_conv_list.png
new file mode 100644
index 0000000..f6fcde1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_oobe_conv_list.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_oobe_freq_list.png b/res/drawable-xxhdpi/ic_oobe_freq_list.png
new file mode 100644
index 0000000..4d7543d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_oobe_freq_list.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_open_in_new.png b/res/drawable-xxhdpi/ic_open_in_new.png
new file mode 100644
index 0000000..9109fa4
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_open_in_new.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_people_add_light.png b/res/drawable-xxhdpi/ic_people_add_light.png
new file mode 100644
index 0000000..915b91d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_people_add_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_person_add_blue.png b/res/drawable-xxhdpi/ic_person_add_blue.png
new file mode 100644
index 0000000..10aa345
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_person_add_blue.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_person_add_dark.png b/res/drawable-xxhdpi/ic_person_add_dark.png
new file mode 100644
index 0000000..1d9bb20
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_person_add_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_person_add_light.png b/res/drawable-xxhdpi/ic_person_add_light.png
new file mode 100644
index 0000000..5a17f08
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_person_add_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_person_light.png b/res/drawable-xxhdpi/ic_person_light.png
new file mode 100644
index 0000000..adab957
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_person_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_person_light_large.png b/res/drawable-xxhdpi/ic_person_light_large.png
new file mode 100644
index 0000000..3cf214f
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_person_light_large.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_phone_small_light.png b/res/drawable-xxhdpi/ic_phone_small_light.png
new file mode 100644
index 0000000..8d7c25f
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_phone_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_photo_library_light.png b/res/drawable-xxhdpi/ic_photo_library_light.png
new file mode 100644
index 0000000..f61bb58
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_photo_library_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_preview_pause.png b/res/drawable-xxhdpi/ic_preview_pause.png
new file mode 100644
index 0000000..4785e1b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_preview_pause.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_preview_play.png b/res/drawable-xxhdpi/ic_preview_play.png
new file mode 100644
index 0000000..3120cd1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_preview_play.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_remove_light.png b/res/drawable-xxhdpi/ic_remove_light.png
new file mode 100644
index 0000000..25bcc74
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_remove_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_remove_small_light.png b/res/drawable-xxhdpi/ic_remove_small_light.png
new file mode 100644
index 0000000..c69e993
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_remove_small_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_save_dark.png b/res/drawable-xxhdpi/ic_save_dark.png
new file mode 100644
index 0000000..8267484
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_save_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_save_light.png b/res/drawable-xxhdpi/ic_save_light.png
new file mode 100644
index 0000000..4b944cf
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_save_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_light.png b/res/drawable-xxhdpi/ic_search_light.png
new file mode 100644
index 0000000..97d77d6
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_search_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_send_dark.png b/res/drawable-xxhdpi/ic_send_dark.png
new file mode 100644
index 0000000..44729b1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_send_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_send_light.png b/res/drawable-xxhdpi/ic_send_light.png
new file mode 100644
index 0000000..a1a6cab
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_share_dark.png b/res/drawable-xxhdpi/ic_share_dark.png
new file mode 100644
index 0000000..790670a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_share_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_share_light.png b/res/drawable-xxhdpi/ic_share_light.png
new file mode 100644
index 0000000..880c6a2
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_share_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sim_card_send.png b/res/drawable-xxhdpi/ic_sim_card_send.png
new file mode 100644
index 0000000..8eae7c3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sim_card_send.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_delivery_ok.png b/res/drawable-xxhdpi/ic_sms_delivery_ok.png
new file mode 100644
index 0000000..2c8c9b0
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_delivery_ok.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_failed_light.png b/res/drawable-xxhdpi/ic_sms_failed_light.png
new file mode 100644
index 0000000..5da454b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_failed_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_light.png b/res/drawable-xxhdpi/ic_sms_light.png
new file mode 100644
index 0000000..7a8b491
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_sms_multi_light.png b/res/drawable-xxhdpi/ic_sms_multi_light.png
new file mode 100644
index 0000000..6496cb8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_sms_multi_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_tooltip_arrow.png b/res/drawable-xxhdpi/ic_tooltip_arrow.png
new file mode 100644
index 0000000..72f1c5b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_tooltip_arrow.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_video_play_light.png b/res/drawable-xxhdpi/ic_video_play_light.png
new file mode 100644
index 0000000..f49c653
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_video_play_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wear_reply.png b/res/drawable-xxhdpi/ic_wear_reply.png
new file mode 100644
index 0000000..0790ee8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wear_reply.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_avatar_shadow.png b/res/drawable-xxhdpi/ic_widget_avatar_shadow.png
new file mode 100644
index 0000000..ea4e26d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_avatar_shadow.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_list.png b/res/drawable-xxhdpi/ic_widget_list.png
new file mode 100644
index 0000000..c370ee8
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_list.png
Binary files differ
diff --git a/res/drawable-xxhdpi/msg_bubble_error.9.png b/res/drawable-xxhdpi/msg_bubble_error.9.png
new file mode 100644
index 0000000..f8bdc96
--- /dev/null
+++ b/res/drawable-xxhdpi/msg_bubble_error.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/msg_bubble_incoming.9.png b/res/drawable-xxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..ab693b3
--- /dev/null
+++ b/res/drawable-xxhdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/msg_bubble_input.9.png b/res/drawable-xxhdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..de41ef4
--- /dev/null
+++ b/res/drawable-xxhdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/msg_bubble_outgoing.9.png b/res/drawable-xxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..5b2a2ff
--- /dev/null
+++ b/res/drawable-xxhdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/permissions.png b/res/drawable-xxhdpi/permissions.png
new file mode 100644
index 0000000..fd61476
--- /dev/null
+++ b/res/drawable-xxhdpi/permissions.png
Binary files differ
diff --git a/res/drawable-xxhdpi/swipe_shadow.9.png b/res/drawable-xxhdpi/swipe_shadow.9.png
new file mode 100644
index 0000000..0491faa
--- /dev/null
+++ b/res/drawable-xxhdpi/swipe_shadow.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/swipe_shadow_drag.9.png b/res/drawable-xxhdpi/swipe_shadow_drag.9.png
new file mode 100644
index 0000000..0153c5b
--- /dev/null
+++ b/res/drawable-xxhdpi/swipe_shadow_drag.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/sym_keyboard_delete_holo.png b/res/drawable-xxhdpi/sym_keyboard_delete_holo.png
new file mode 100644
index 0000000..f2adbec
--- /dev/null
+++ b/res/drawable-xxhdpi/sym_keyboard_delete_holo.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_btn_bg_normal.9.png b/res/drawable-xxhdpi/tab_btn_bg_normal.9.png
new file mode 100644
index 0000000..9f3a0f9
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_btn_bg_normal.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_btn_bg_pressed.9.png b/res/drawable-xxhdpi/tab_btn_bg_pressed.9.png
new file mode 100644
index 0000000..53f226e
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_btn_bg_pressed.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected.9.png b/res/drawable-xxhdpi/tab_selected.9.png
new file mode 100644
index 0000000..e5efc58
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_focused_holo.9.png b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..8762b9e
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..8faa7ff
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..c7a539b
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected.9.png b/res/drawable-xxhdpi/tab_unselected.9.png
new file mode 100644
index 0000000..3891886
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..462ab15
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..58ddbe8
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..de61933
--- /dev/null
+++ b/res/drawable-xxhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_hr.9.png b/res/drawable-xxhdpi/widget_hr.9.png
new file mode 100644
index 0000000..375272b
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_hr.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_msg_bubble_incoming.9.png b/res/drawable-xxhdpi/widget_msg_bubble_incoming.9.png
new file mode 100644
index 0000000..5093c23
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..6ae5608
--- /dev/null
+++ b/res/drawable-xxhdpi/widget_msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..65cd55e
--- /dev/null
+++ b/res/drawable-xxxhdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..b6cac56
--- /dev/null
+++ b/res/drawable-xxxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..f017e03
--- /dev/null
+++ b/res/drawable-xxxhdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.png b/res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..025e694
--- /dev/null
+++ b/res/drawable-xxxhdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/cab_bg.9.png b/res/drawable-xxxhdpi/cab_bg.9.png
new file mode 100644
index 0000000..978cfad
--- /dev/null
+++ b/res/drawable-xxxhdpi/cab_bg.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/contact_popup_background.9.png b/res/drawable-xxxhdpi/contact_popup_background.9.png
new file mode 100644
index 0000000..30919fa
--- /dev/null
+++ b/res/drawable-xxxhdpi/contact_popup_background.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/fab_new_message_static_shadow.png b/res/drawable-xxxhdpi/fab_new_message_static_shadow.png
new file mode 100644
index 0000000..e1d16a6
--- /dev/null
+++ b/res/drawable-xxxhdpi/fab_new_message_static_shadow.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_add_white.png b/res/drawable-xxxhdpi/ic_add_white.png
new file mode 100644
index 0000000..0ed23b1
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_add_white.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_archive_small_dark.png b/res/drawable-xxxhdpi/ic_archive_small_dark.png
new file mode 100644
index 0000000..c380363
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_archive_small_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_archive_small_light.png b/res/drawable-xxxhdpi/ic_archive_small_light.png
new file mode 100644
index 0000000..59ffef9
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_archive_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_archive_undo_small_dark.png b/res/drawable-xxxhdpi/ic_archive_undo_small_dark.png
new file mode 100644
index 0000000..b2e4f8b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_archive_undo_small_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_archive_undo_small_light.png b/res/drawable-xxxhdpi/ic_archive_undo_small_light.png
new file mode 100644
index 0000000..46fd7d8
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_archive_undo_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_dark.png b/res/drawable-xxxhdpi/ic_arrow_back_dark.png
new file mode 100644
index 0000000..e7a9c81
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_arrow_back_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_light.png b/res/drawable-xxxhdpi/ic_arrow_back_light.png
new file mode 100644
index 0000000..54a5687
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_arrow_back_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_attachment_dark.png b/res/drawable-xxxhdpi/ic_attachment_dark.png
new file mode 100644
index 0000000..7cf871a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_attachment_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_audio_light.png b/res/drawable-xxxhdpi/ic_audio_light.png
new file mode 100644
index 0000000..768ac89
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_audio_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_audio_pause.png b/res/drawable-xxxhdpi/ic_audio_pause.png
new file mode 100644
index 0000000..f7ae29f
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_audio_pause.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_audio_play.png b/res/drawable-xxxhdpi/ic_audio_play.png
new file mode 100644
index 0000000..71f21e7
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_audio_play.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_camera_front_light.png b/res/drawable-xxxhdpi/ic_camera_front_light.png
new file mode 100644
index 0000000..3af175b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_camera_front_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_camera_light.png b/res/drawable-xxxhdpi/ic_camera_light.png
new file mode 100644
index 0000000..c3484b6
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_camera_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_camera_rear_light.png b/res/drawable-xxxhdpi/ic_camera_rear_light.png
new file mode 100644
index 0000000..c59b65b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_camera_rear_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_cancel_small_dark.png b/res/drawable-xxxhdpi/ic_cancel_small_dark.png
new file mode 100644
index 0000000..f454d2a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_cancel_small_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_cancel_small_light.png b/res/drawable-xxxhdpi/ic_cancel_small_light.png
new file mode 100644
index 0000000..cce1904
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_cancel_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkbox_blank_light.png b/res/drawable-xxxhdpi/ic_checkbox_blank_light.png
new file mode 100644
index 0000000..ad983af
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkbox_blank_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkbox_light.png b/res/drawable-xxxhdpi/ic_checkbox_light.png
new file mode 100644
index 0000000..c8e8b79
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkbox_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkbox_outline_light.png b/res/drawable-xxxhdpi/ic_checkbox_outline_light.png
new file mode 100644
index 0000000..eb0a803
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkbox_outline_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkmark_circle_blue.png b/res/drawable-xxxhdpi/ic_checkmark_circle_blue.png
new file mode 100644
index 0000000..7bb1501
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkmark_circle_blue.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkmark_large.png b/res/drawable-xxxhdpi/ic_checkmark_large.png
new file mode 100644
index 0000000..8119ff2
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkmark_large.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkmark_large_light.png b/res/drawable-xxxhdpi/ic_checkmark_large_light.png
new file mode 100644
index 0000000..dddac27
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkmark_large_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkmark_small_blue.png b/res/drawable-xxxhdpi/ic_checkmark_small_blue.png
new file mode 100644
index 0000000..3199069
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkmark_small_blue.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_checkmark_small_light.png b/res/drawable-xxxhdpi/ic_checkmark_small_light.png
new file mode 100644
index 0000000..380f4e8
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_checkmark_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_color_lens.png b/res/drawable-xxxhdpi/ic_color_lens.png
new file mode 100644
index 0000000..5c71071
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_color_lens.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_content_copy_dark.png b/res/drawable-xxxhdpi/ic_content_copy_dark.png
new file mode 100644
index 0000000..24dc390
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_content_copy_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_crying_hero.png b/res/drawable-xxxhdpi/ic_crying_hero.png
new file mode 100644
index 0000000..0a428c9
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_crying_hero.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_delete_small_dark.png b/res/drawable-xxxhdpi/ic_delete_small_dark.png
new file mode 100644
index 0000000..497e7a7
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_delete_small_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_delete_small_light.png b/res/drawable-xxxhdpi/ic_delete_small_light.png
new file mode 100644
index 0000000..d8a661f
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_delete_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_dnd_on_dark.png b/res/drawable-xxxhdpi/ic_dnd_on_dark.png
new file mode 100644
index 0000000..de2f17b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_dnd_on_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_dnd_on_light.png b/res/drawable-xxxhdpi/ic_dnd_on_light.png
new file mode 100644
index 0000000..7851985
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_dnd_on_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_failed_light.png b/res/drawable-xxxhdpi/ic_failed_light.png
new file mode 100644
index 0000000..53d374c
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_failed_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_failed_status_red.png b/res/drawable-xxxhdpi/ic_failed_status_red.png
new file mode 100644
index 0000000..ee1ed24
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_failed_status_red.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_file_download_dark.png b/res/drawable-xxxhdpi/ic_file_download_dark.png
new file mode 100644
index 0000000..0012e5e
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_file_download_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_file_download_light.png b/res/drawable-xxxhdpi/ic_file_download_light.png
new file mode 100644
index 0000000..6d7ca08
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_file_download_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_forward_dark.png b/res/drawable-xxxhdpi/ic_forward_dark.png
new file mode 100644
index 0000000..6408fb9
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_forward_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_history_light.png b/res/drawable-xxxhdpi/ic_history_light.png
new file mode 100644
index 0000000..7ff42b7
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_history_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_image_light.png b/res/drawable-xxxhdpi/ic_image_light.png
new file mode 100644
index 0000000..0d229e2
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_image_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_ime_dark.png b/res/drawable-xxxhdpi/ic_ime_dark.png
new file mode 100644
index 0000000..7c051df
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_ime_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_ime_light.png b/res/drawable-xxxhdpi/ic_ime_light.png
new file mode 100644
index 0000000..031d086
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_ime_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_info_dark.png b/res/drawable-xxxhdpi/ic_info_dark.png
new file mode 100644
index 0000000..107385b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_info_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.png b/res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.png
new file mode 100644
index 0000000..3737fec
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_keyboard_arrow_left_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.png b/res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.png
new file mode 100644
index 0000000..85d005b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_keyboard_arrow_right_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_launcher.png b/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..0d61198
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_audio_mic.png b/res/drawable-xxxhdpi/ic_mp_audio_mic.png
new file mode 100644
index 0000000..9f51c4b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_audio_mic.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_camera_large_light.png b/res/drawable-xxxhdpi/ic_mp_camera_large_light.png
new file mode 100644
index 0000000..f915989
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_camera_large_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_camera_small_light.png b/res/drawable-xxxhdpi/ic_mp_camera_small_light.png
new file mode 100644
index 0000000..d6d8c9d
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_camera_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.png b/res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.png
new file mode 100644
index 0000000..30e725f
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_capture_stop_large_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_full_screen_light.png b/res/drawable-xxxhdpi/ic_mp_full_screen_light.png
new file mode 100644
index 0000000..4860109
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_full_screen_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_video_large_light.png b/res/drawable-xxxhdpi/ic_mp_video_large_light.png
new file mode 100644
index 0000000..32c4317
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_video_large_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_mp_video_small_light.png b/res/drawable-xxxhdpi/ic_mp_video_small_light.png
new file mode 100644
index 0000000..6d2a14b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_mp_video_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_notifications_off_dark.png b/res/drawable-xxxhdpi/ic_notifications_off_dark.png
new file mode 100644
index 0000000..10ac318
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_notifications_off_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_notifications_off_light.png b/res/drawable-xxxhdpi/ic_notifications_off_light.png
new file mode 100644
index 0000000..8eb5782
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_notifications_off_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_notifications_off_small_light.png b/res/drawable-xxxhdpi/ic_notifications_off_small_light.png
new file mode 100644
index 0000000..3756943
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_notifications_off_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_notifications_on_dark.png b/res/drawable-xxxhdpi/ic_notifications_on_dark.png
new file mode 100644
index 0000000..3896212
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_notifications_on_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_notifications_on_light.png b/res/drawable-xxxhdpi/ic_notifications_on_light.png
new file mode 100644
index 0000000..47794fd
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_notifications_on_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_numeric_dialpad.png b/res/drawable-xxxhdpi/ic_numeric_dialpad.png
new file mode 100644
index 0000000..5e0986c
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_numeric_dialpad.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_oobe_conv_list.png b/res/drawable-xxxhdpi/ic_oobe_conv_list.png
new file mode 100644
index 0000000..0decb23
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_oobe_conv_list.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_oobe_freq_list.png b/res/drawable-xxxhdpi/ic_oobe_freq_list.png
new file mode 100644
index 0000000..4522cb6
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_oobe_freq_list.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_open_in_new.png b/res/drawable-xxxhdpi/ic_open_in_new.png
new file mode 100644
index 0000000..6d6b59f
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_open_in_new.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_people_add_light.png b/res/drawable-xxxhdpi/ic_people_add_light.png
new file mode 100644
index 0000000..5db76a9
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_people_add_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_person_add_blue.png b/res/drawable-xxxhdpi/ic_person_add_blue.png
new file mode 100644
index 0000000..b358660
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_person_add_blue.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_person_add_dark.png b/res/drawable-xxxhdpi/ic_person_add_dark.png
new file mode 100644
index 0000000..37e252f
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_person_add_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_person_add_light.png b/res/drawable-xxxhdpi/ic_person_add_light.png
new file mode 100644
index 0000000..b279246
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_person_add_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_person_light.png b/res/drawable-xxxhdpi/ic_person_light.png
new file mode 100644
index 0000000..59853fc
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_person_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_person_light_large.png b/res/drawable-xxxhdpi/ic_person_light_large.png
new file mode 100644
index 0000000..44d4809
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_person_light_large.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_phone_small_light.png b/res/drawable-xxxhdpi/ic_phone_small_light.png
new file mode 100644
index 0000000..433e4a7
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_phone_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_photo_library_light.png b/res/drawable-xxxhdpi/ic_photo_library_light.png
new file mode 100644
index 0000000..8627f42
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_photo_library_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_preview_pause.png b/res/drawable-xxxhdpi/ic_preview_pause.png
new file mode 100644
index 0000000..2ba4fe2
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_preview_pause.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_preview_play.png b/res/drawable-xxxhdpi/ic_preview_play.png
new file mode 100644
index 0000000..f44f572
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_preview_play.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_remove_light.png b/res/drawable-xxxhdpi/ic_remove_light.png
new file mode 100644
index 0000000..b9288bf
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_remove_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_remove_small_light.png b/res/drawable-xxxhdpi/ic_remove_small_light.png
new file mode 100644
index 0000000..0e0cb05
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_remove_small_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_save_dark.png b/res/drawable-xxxhdpi/ic_save_dark.png
new file mode 100644
index 0000000..2819de2
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_save_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_save_light.png b/res/drawable-xxxhdpi/ic_save_light.png
new file mode 100644
index 0000000..ba7564b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_save_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_light.png b/res/drawable-xxxhdpi/ic_search_light.png
new file mode 100644
index 0000000..b38785a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_search_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_send_dark.png b/res/drawable-xxxhdpi/ic_send_dark.png
new file mode 100644
index 0000000..5f52b29
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_send_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_send_light.png b/res/drawable-xxxhdpi/ic_send_light.png
new file mode 100644
index 0000000..b4e8563
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_send_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_share_dark.png b/res/drawable-xxxhdpi/ic_share_dark.png
new file mode 100644
index 0000000..a6ddd2d
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_share_dark.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_share_light.png b/res/drawable-xxxhdpi/ic_share_light.png
new file mode 100644
index 0000000..ac63637
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_share_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_sim_card_send.png b/res/drawable-xxxhdpi/ic_sim_card_send.png
new file mode 100644
index 0000000..0db661b
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_sim_card_send.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_sms_delivery_ok.png b/res/drawable-xxxhdpi/ic_sms_delivery_ok.png
new file mode 100644
index 0000000..487525d
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_sms_delivery_ok.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_sms_failed_light.png b/res/drawable-xxxhdpi/ic_sms_failed_light.png
new file mode 100644
index 0000000..2daf2ae
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_sms_failed_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_sms_light.png b/res/drawable-xxxhdpi/ic_sms_light.png
new file mode 100644
index 0000000..98d1bc8
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_sms_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_sms_multi_light.png b/res/drawable-xxxhdpi/ic_sms_multi_light.png
new file mode 100644
index 0000000..e2f4bfc
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_sms_multi_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_tooltip_arrow.png b/res/drawable-xxxhdpi/ic_tooltip_arrow.png
new file mode 100644
index 0000000..84b03ad
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_tooltip_arrow.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_video_play_light.png b/res/drawable-xxxhdpi/ic_video_play_light.png
new file mode 100644
index 0000000..9192f84
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_video_play_light.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_wear_reply.png b/res/drawable-xxxhdpi/ic_wear_reply.png
new file mode 100644
index 0000000..34065f6
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_wear_reply.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_widget_avatar_shadow.png b/res/drawable-xxxhdpi/ic_widget_avatar_shadow.png
new file mode 100644
index 0000000..83d02ce
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_widget_avatar_shadow.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_widget_list.png b/res/drawable-xxxhdpi/ic_widget_list.png
new file mode 100644
index 0000000..f316487
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_widget_list.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/msg_bubble_error.9.png b/res/drawable-xxxhdpi/msg_bubble_error.9.png
new file mode 100644
index 0000000..fb8161c
--- /dev/null
+++ b/res/drawable-xxxhdpi/msg_bubble_error.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/msg_bubble_incoming.9.png b/res/drawable-xxxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 0000000..34130fc
--- /dev/null
+++ b/res/drawable-xxxhdpi/msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/msg_bubble_input.9.png b/res/drawable-xxxhdpi/msg_bubble_input.9.png
new file mode 100644
index 0000000..d330459
--- /dev/null
+++ b/res/drawable-xxxhdpi/msg_bubble_input.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png b/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..cfd5734
--- /dev/null
+++ b/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/permissions.png b/res/drawable-xxxhdpi/permissions.png
new file mode 100644
index 0000000..2a7a0d7
--- /dev/null
+++ b/res/drawable-xxxhdpi/permissions.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/swipe_shadow.9.png b/res/drawable-xxxhdpi/swipe_shadow.9.png
new file mode 100644
index 0000000..4726ef4
--- /dev/null
+++ b/res/drawable-xxxhdpi/swipe_shadow.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/swipe_shadow_drag.9.png b/res/drawable-xxxhdpi/swipe_shadow_drag.9.png
new file mode 100644
index 0000000..d48e311
--- /dev/null
+++ b/res/drawable-xxxhdpi/swipe_shadow_drag.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/sym_keyboard_delete_holo.png b/res/drawable-xxxhdpi/sym_keyboard_delete_holo.png
new file mode 100644
index 0000000..09af002
--- /dev/null
+++ b/res/drawable-xxxhdpi/sym_keyboard_delete_holo.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_btn_bg_normal.9.png b/res/drawable-xxxhdpi/tab_btn_bg_normal.9.png
new file mode 100644
index 0000000..1290a0d
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_btn_bg_normal.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_btn_bg_pressed.9.png b/res/drawable-xxxhdpi/tab_btn_bg_pressed.9.png
new file mode 100644
index 0000000..9fce798
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_btn_bg_pressed.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_selected.9.png b/res/drawable-xxxhdpi/tab_selected.9.png
new file mode 100644
index 0000000..5c20d4d
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_selected.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_selected_focused_holo.9.png b/res/drawable-xxxhdpi/tab_selected_focused_holo.9.png
new file mode 100644
index 0000000..33be972
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_selected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.png b/res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..dc38a52
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_selected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_selected_pressed_holo.9.png b/res/drawable-xxxhdpi/tab_selected_pressed_holo.9.png
new file mode 100644
index 0000000..e4518a8
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_selected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_unselected.9.png b/res/drawable-xxxhdpi/tab_unselected.9.png
new file mode 100644
index 0000000..9df735b
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_unselected.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_unselected_focused_holo.9.png b/res/drawable-xxxhdpi/tab_unselected_focused_holo.9.png
new file mode 100644
index 0000000..f01cfaf
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_unselected_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.png b/res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.png
new file mode 100644
index 0000000..7d85b20
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_unselected_pressed_focused_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.png b/res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.png
new file mode 100644
index 0000000..0ace170
--- /dev/null
+++ b/res/drawable-xxxhdpi/tab_unselected_pressed_holo.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_hr.9.png b/res/drawable-xxxhdpi/widget_hr.9.png
new file mode 100644
index 0000000..80fdaf6
--- /dev/null
+++ b/res/drawable-xxxhdpi/widget_hr.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.png b/res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.png
new file mode 100644
index 0000000..383133e
--- /dev/null
+++ b/res/drawable-xxxhdpi/widget_msg_bubble_incoming.9.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.png b/res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.png
new file mode 100644
index 0000000..daf33b5
--- /dev/null
+++ b/res/drawable-xxxhdpi/widget_msg_bubble_outgoing.9.png
Binary files differ
diff --git a/res/drawable/attachment_audio_preview_background.xml b/res/drawable/attachment_audio_preview_background.xml
new file mode 100644
index 0000000..8b62415
--- /dev/null
+++ b/res/drawable/attachment_audio_preview_background.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="2dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/attachment_image_placeholder_background.xml b/res/drawable/attachment_image_placeholder_background.xml
new file mode 100644
index 0000000..b3315e8
--- /dev/null
+++ b/res/drawable/attachment_image_placeholder_background.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="@dimen/attachment_rounded_corner_radius" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/attachment_more_items_background.xml b/res/drawable/attachment_more_items_background.xml
new file mode 100644
index 0000000..0ef8e61
--- /dev/null
+++ b/res/drawable/attachment_more_items_background.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/attachment_preview_more_items_text_background" />
+ <corners android:radius="@dimen/attachment_rounded_corner_radius" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/attachment_vcard_preview_background.xml b/res/drawable/attachment_vcard_preview_background.xml
new file mode 100644
index 0000000..8b62415
--- /dev/null
+++ b/res/drawable/attachment_vcard_preview_background.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="2dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/audio_progress_bar_background_incoming.xml b/res/drawable/audio_progress_bar_background_incoming.xml
new file mode 100644
index 0000000..990e1de
--- /dev/null
+++ b/res/drawable/audio_progress_bar_background_incoming.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <stroke
+ android:color="@color/conversation_background"
+ android:width="1dp" />
+ <corners android:radius="3dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/audio_progress_bar_background_outgoing.xml b/res/drawable/audio_progress_bar_background_outgoing.xml
new file mode 100644
index 0000000..351cf18
--- /dev/null
+++ b/res/drawable/audio_progress_bar_background_outgoing.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/conversation_background" />
+ <corners android:radius="3dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/audio_progress_bar_progress.xml b/res/drawable/audio_progress_bar_progress.xml
new file mode 100644
index 0000000..a70ff32
--- /dev/null
+++ b/res/drawable/audio_progress_bar_progress.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/audio_progress_bar_color" />
+ <corners android:radius="3dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/audio_record_control_button_background.xml b/res/drawable/audio_record_control_button_background.xml
new file mode 100644
index 0000000..02f329e
--- /dev/null
+++ b/res/drawable/audio_record_control_button_background.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <stroke
+ android:width="1dp"
+ android:color="@color/audio_record_control_button_stroke"/>
+ <solid
+ android:color="@android:color/white"/>
+</shape> \ No newline at end of file
diff --git a/res/drawable/chips_dropdown_background.xml b/res/drawable/chips_dropdown_background.xml
new file mode 100644
index 0000000..48a727d
--- /dev/null
+++ b/res/drawable/chips_dropdown_background.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@color/chips_dropdown_background_activated"
+ android:state_activated="true"/>
+ <item android:drawable="@color/chips_dropdown_background_pressed" android:state_pressed="true"/>
+ <item android:drawable="@android:color/white"/>
+</selector> \ No newline at end of file
diff --git a/res/drawable/chips_dropdown_text_color.xml b/res/drawable/chips_dropdown_text_color.xml
new file mode 100644
index 0000000..5f06751
--- /dev/null
+++ b/res/drawable/chips_dropdown_text_color.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_activated="true" android:color="@android:color/white" />
+ <item android:state_checked="true" android:color="@android:color/white" />
+ <item android:state_selected="true" android:color="@android:color/white" />
+ <item android:color="#333333"/>
+</selector> \ No newline at end of file
diff --git a/res/drawable/compose_chips_divider_gradient.xml b/res/drawable/compose_chips_divider_gradient.xml
new file mode 100644
index 0000000..65c6601
--- /dev/null
+++ b/res/drawable/compose_chips_divider_gradient.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:startColor="@color/compose_contact_divider"
+ android:endColor="@android:color/transparent"
+ android:angle="-90"
+ android:dither="true"
+ />
+</shape> \ No newline at end of file
diff --git a/res/drawable/contact_picker_tab_background_selector.xml b/res/drawable/contact_picker_tab_background_selector.xml
new file mode 100644
index 0000000..78c443b
--- /dev/null
+++ b/res/drawable/contact_picker_tab_background_selector.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true"
+ android:drawable="@color/contact_picker_tab_pressed" />
+</selector> \ No newline at end of file
diff --git a/res/drawable/contacts_fastscroll_label_left.xml b/res/drawable/contacts_fastscroll_label_left.xml
new file mode 100644
index 0000000..5ed079a
--- /dev/null
+++ b/res/drawable/contacts_fastscroll_label_left.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="@dimen/fastscroll_preview_margin_left_right">
+ <shape
+ android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/fastscroll_preview_corner_radius"
+ android:topRightRadius="@dimen/fastscroll_preview_corner_radius"
+ android:bottomRightRadius="@dimen/fastscroll_preview_corner_radius" />
+ <solid android:color="@color/action_bar_background_color" />
+ </shape>
+</inset> \ No newline at end of file
diff --git a/res/drawable/contacts_fastscroll_label_right.xml b/res/drawable/contacts_fastscroll_label_right.xml
new file mode 100644
index 0000000..67c4622
--- /dev/null
+++ b/res/drawable/contacts_fastscroll_label_right.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetRight="@dimen/fastscroll_preview_margin_left_right">
+ <shape
+ android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/fastscroll_preview_corner_radius"
+ android:topRightRadius="@dimen/fastscroll_preview_corner_radius"
+ android:bottomLeftRadius="@dimen/fastscroll_preview_corner_radius" />
+ <solid android:color="@color/action_bar_background_color" />
+ </shape>
+</inset> \ No newline at end of file
diff --git a/res/drawable/conversation_compose_divider_gradient.xml b/res/drawable/conversation_compose_divider_gradient.xml
new file mode 100644
index 0000000..2c8a180
--- /dev/null
+++ b/res/drawable/conversation_compose_divider_gradient.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:startColor="@color/conversation_compose_divider_start"
+ android:endColor="@android:color/transparent"
+ android:angle="90"
+ android:dither="true"
+ />
+</shape> \ No newline at end of file
diff --git a/res/drawable/default_image.xml b/res/drawable/default_image.xml
new file mode 100644
index 0000000..749e73b
--- /dev/null
+++ b/res/drawable/default_image.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<!--
+ This overrides frameworks/opt/photoviewer/res/drawable/default_image.xml
+ as a workaround for http://b/17510888. It should be removed after that
+ but is fixed.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+
+ <!-- these are the dimensions of the original default_image.png -->
+ <size
+ android:width="215dp"
+ android:height="210dp" />
+
+ <solid
+ android:color="#00000000" />
+
+</shape>
diff --git a/res/drawable/exit_button_bg.xml b/res/drawable/exit_button_bg.xml
new file mode 100644
index 0000000..9fa0fb2
--- /dev/null
+++ b/res/drawable/exit_button_bg.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@android:color/white">
+</ripple>
diff --git a/res/drawable/fab_new_message_bg.xml b/res/drawable/fab_new_message_bg.xml
new file mode 100644
index 0000000..741b4b5
--- /dev/null
+++ b/res/drawable/fab_new_message_bg.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <bitmap
+ android:src="@drawable/fab_new_message_static_shadow" />
+ </item>
+ <item
+ android:left="2dp"
+ android:right="2dp"
+ android:bottom="@dimen/fab_padding_bottom">
+ <selector>
+ <item
+ android:state_pressed="true">
+ <shape
+ android:shape="oval" >
+ <solid
+ android:color="@color/fab_pressed_color" />
+ </shape>
+ </item>
+ <item>
+ <shape
+ android:shape="oval" >
+ <solid
+ android:color="@color/fab_color" />
+ </shape>
+ </item>
+ </selector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/res/drawable/fastscroll_preview_left.xml b/res/drawable/fastscroll_preview_left.xml
new file mode 100644
index 0000000..165a222
--- /dev/null
+++ b/res/drawable/fastscroll_preview_left.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/fastscroll_preview_corner_radius"
+ android:topRightRadius="@dimen/fastscroll_preview_corner_radius"
+ android:bottomRightRadius="@dimen/fastscroll_preview_corner_radius" />
+ <solid android:color="@color/color_filter_base_color" />
+</shape>
diff --git a/res/drawable/fastscroll_preview_right.xml b/res/drawable/fastscroll_preview_right.xml
new file mode 100644
index 0000000..7c0dc09
--- /dev/null
+++ b/res/drawable/fastscroll_preview_right.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/fastscroll_preview_corner_radius"
+ android:topRightRadius="@dimen/fastscroll_preview_corner_radius"
+ android:bottomLeftRadius="@dimen/fastscroll_preview_corner_radius" />
+ <solid android:color="@color/color_filter_base_color" />
+</shape>
diff --git a/res/drawable/fastscroll_thumb.xml b/res/drawable/fastscroll_thumb.xml
new file mode 100644
index 0000000..be7c96f
--- /dev/null
+++ b/res/drawable/fastscroll_thumb.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/fastscroll_thumb_color" />
+ <size android:width="@dimen/fastscroll_track_width"
+ android:height="@dimen/fastscroll_thumb_height" />
+</shape>
diff --git a/res/drawable/fastscroll_thumb_pressed.xml b/res/drawable/fastscroll_thumb_pressed.xml
new file mode 100644
index 0000000..39de5a6
--- /dev/null
+++ b/res/drawable/fastscroll_thumb_pressed.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/color_filter_base_color" />
+ <size android:width="@dimen/fastscroll_track_width"
+ android:height="@dimen/fastscroll_thumb_height" />
+</shape>
diff --git a/res/drawable/fastscroll_track.xml b/res/drawable/fastscroll_track.xml
new file mode 100644
index 0000000..f3bf96d
--- /dev/null
+++ b/res/drawable/fastscroll_track.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle" >
+ <solid android:color="@color/fastscroll_track_color" />
+ <size android:width="@dimen/fastscroll_track_width" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/gallery_checkbox_selector.xml b/res/drawable/gallery_checkbox_selector.xml
new file mode 100644
index 0000000..cdd9734
--- /dev/null
+++ b/res/drawable/gallery_checkbox_selector.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_checked="true"
+ android:drawable="@drawable/ic_checkbox_light" />
+ <item
+ android:state_selected="true"
+ android:drawable="@drawable/ic_checkbox_light" />
+ <item
+ android:drawable="@drawable/ic_checkbox_blank_light" />
+</selector> \ No newline at end of file
diff --git a/res/drawable/gallery_document_picker_item_background.xml b/res/drawable/gallery_document_picker_item_background.xml
new file mode 100644
index 0000000..4bbd7fe
--- /dev/null
+++ b/res/drawable/gallery_document_picker_item_background.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <stroke android:color="@color/gallery_image_default_background" android:width="1dp" />
+</shape> \ No newline at end of file
diff --git a/res/drawable/gallery_image_background_selector.xml b/res/drawable/gallery_image_background_selector.xml
new file mode 100644
index 0000000..0b902fd
--- /dev/null
+++ b/res/drawable/gallery_image_background_selector.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true"
+ android:drawable="@color/gallery_image_pressed" />
+ <item
+ android:drawable="@android:color/transparent" />
+</selector> \ No newline at end of file
diff --git a/res/drawable/generic_video_icon.xml b/res/drawable/generic_video_icon.xml
new file mode 100644
index 0000000..e98cb94
--- /dev/null
+++ b/res/drawable/generic_video_icon.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 Google Inc.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/generic_video_icon"/>
+</shape> \ No newline at end of file
diff --git a/res/drawable/mediapicker_tab_button_background.xml b/res/drawable/mediapicker_tab_button_background.xml
new file mode 100644
index 0000000..08dbf5c
--- /dev/null
+++ b/res/drawable/mediapicker_tab_button_background.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true"
+ android:drawable="@color/background_item_pressed" />
+ <item
+ android:state_activated="true"
+ android:drawable="@color/background_item_activated" />
+ <item
+ android:drawable="@color/background_item_transparent" />
+</selector> \ No newline at end of file
diff --git a/res/drawable/message_bubble_incoming_no_arrow.xml b/res/drawable/message_bubble_incoming_no_arrow.xml
new file mode 100644
index 0000000..d234508
--- /dev/null
+++ b/res/drawable/message_bubble_incoming_no_arrow.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/color_filter_base_color" />
+ <corners android:radius="3dp" />
+</shape>
diff --git a/res/drawable/message_bubble_outgoing_no_arrow.xml b/res/drawable/message_bubble_outgoing_no_arrow.xml
new file mode 100644
index 0000000..d234508
--- /dev/null
+++ b/res/drawable/message_bubble_outgoing_no_arrow.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/color_filter_base_color" />
+ <corners android:radius="3dp" />
+</shape>
diff --git a/res/drawable/send_arrow_background.xml b/res/drawable/send_arrow_background.xml
new file mode 100644
index 0000000..4434997
--- /dev/null
+++ b/res/drawable/send_arrow_background.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true">
+ <shape
+ android:shape="oval" >
+ <solid android:color="@color/compose_message_send_color_pressed" />
+ </shape>
+ </item>
+ <item>
+ <shape
+ android:shape="oval" >
+ <solid android:color="@color/compose_message_send_color" />
+ </shape>
+ </item>
+</selector> \ No newline at end of file
diff --git a/res/drawable/sim_selector_background_gradient.xml b/res/drawable/sim_selector_background_gradient.xml
new file mode 100644
index 0000000..ea7e440
--- /dev/null
+++ b/res/drawable/sim_selector_background_gradient.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:startColor="@color/sim_selector_background_start"
+ android:endColor="@color/sim_selector_background_end"
+ android:angle="90"
+ android:dither="true"
+ />
+</shape> \ No newline at end of file
diff --git a/res/drawable/stat_notify_chat.png b/res/drawable/stat_notify_chat.png
new file mode 100644
index 0000000..cc08e21
--- /dev/null
+++ b/res/drawable/stat_notify_chat.png
Binary files differ
diff --git a/res/drawable/subject_editor_bubble.xml b/res/drawable/subject_editor_bubble.xml
new file mode 100644
index 0000000..21f0248
--- /dev/null
+++ b/res/drawable/subject_editor_bubble.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape>
+ <solid android:color="@color/subject_editor_bubble" />
+ <corners android:radius="3dp" />
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/res/drawable/tab_btn_bg.xml b/res/drawable/tab_btn_bg.xml
new file mode 100644
index 0000000..f7f03dd
--- /dev/null
+++ b/res/drawable/tab_btn_bg.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_btn_bg_pressed" />
+ <item
+ android:state_focused="true"
+ android:drawable="@drawable/tab_btn_bg_pressed" />
+ <item
+ android:drawable="@drawable/tab_btn_bg_normal" />
+</selector>
diff --git a/res/drawable/tab_indicator.xml b/res/drawable/tab_indicator.xml
new file mode 100644
index 0000000..28ecda3
--- /dev/null
+++ b/res/drawable/tab_indicator.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Non focused states -->
+ <item
+ android:state_focused="false"
+ android:state_selected="false"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_unselected" />
+ <item
+ android:state_focused="false"
+ android:state_selected="true"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_selected" />
+
+ <!-- Focused states -->
+ <item
+ android:state_focused="true"
+ android:state_selected="false"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_unselected_focused_holo" />
+ <item
+ android:state_focused="true"
+ android:state_selected="true"
+ android:state_pressed="false"
+ android:drawable="@drawable/tab_selected_focused_holo" />
+
+ <!-- Pressed -->
+ <!-- Non focused states -->
+ <item
+ android:state_focused="false"
+ android:state_selected="false"
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_unselected_pressed_holo" />
+ <item
+ android:state_focused="false"
+ android:state_selected="true"
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_selected_pressed_holo" />
+
+ <!-- Focused states -->
+ <item
+ android:state_focused="true"
+ android:state_selected="false"
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_unselected_pressed_focused_holo" />
+ <item
+ android:state_focused="true"
+ android:state_selected="true"
+ android:state_pressed="true"
+ android:drawable="@drawable/tab_selected_pressed_focused_holo" />
+</selector>
diff --git a/res/drawable/transparent_button_background.xml b/res/drawable/transparent_button_background.xml
new file mode 100644
index 0000000..08dbf5c
--- /dev/null
+++ b/res/drawable/transparent_button_background.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true"
+ android:drawable="@color/background_item_pressed" />
+ <item
+ android:state_activated="true"
+ android:drawable="@color/background_item_activated" />
+ <item
+ android:drawable="@color/background_item_transparent" />
+</selector> \ No newline at end of file
diff --git a/res/drawable/widget_bottom_background.xml b/res/drawable/widget_bottom_background.xml
new file mode 100644
index 0000000..198b2d6
--- /dev/null
+++ b/res/drawable/widget_bottom_background.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/widget_background_color" />
+ <corners android:radius="2dp"
+ android:bottomRightRadius="2dp"
+ android:topRightRadius="0dp"
+ android:bottomLeftRadius="2dp"
+ android:topLeftRadius="0dp"
+ />
+</shape> \ No newline at end of file
diff --git a/res/drawable/widget_top_background.xml b/res/drawable/widget_top_background.xml
new file mode 100644
index 0000000..1476c95
--- /dev/null
+++ b/res/drawable/widget_top_background.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/action_bar_background_color" />
+ <corners android:radius="2dp"
+ android:bottomRightRadius="0dp"
+ android:topRightRadius="2dp"
+ android:bottomLeftRadius="0dp"
+ android:topLeftRadius="2dp"
+ />
+</shape> \ No newline at end of file
diff --git a/res/layout/action_bar_conversation_name.xml b/res/layout/action_bar_conversation_name.xml
new file mode 100644
index 0000000..586cf83
--- /dev/null
+++ b/res/layout/action_bar_conversation_name.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/conversation_title_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill_horizontal" >
+
+ <TextView
+ android:id="@+id/conversation_title"
+ style="@style/ConversationActionBarTitleTextStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true" />
+</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/add_contacts_confirmation_dialog_body.xml b/res/layout/add_contacts_confirmation_dialog_body.xml
new file mode 100644
index 0000000..07f3d0f
--- /dev/null
+++ b/res/layout/add_contacts_confirmation_dialog_body.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:padding="20dp" >
+
+ <com.android.messaging.ui.ContactIconView
+ android:id="@+id/contact_icon"
+ android:layout_width="@dimen/conversation_list_contact_icon_size"
+ android:layout_height="@dimen/conversation_list_contact_icon_size"
+ android:importantForAccessibility="no"
+ app:iconSize="large"
+ android:layout_gravity="center_vertical"
+ android:clickable="true" />
+
+ <TextView
+ android:id="@+id/participant_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/AddContactConfirmationTextStyle"
+ android:layout_gravity="center_vertical" />
+
+</LinearLayout>
diff --git a/res/layout/all_contacts_list_view.xml b/res/layout/all_contacts_list_view.xml
new file mode 100644
index 0000000..fe05c4d
--- /dev/null
+++ b/res/layout/all_contacts_list_view.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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:id="@+id/all_contacts_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false"
+ android:divider="@null"
+ android:dividerHeight="0px"
+ android:fastScrollEnabled="true" />
+
+ <include
+ layout="@layout/list_empty_view"
+ android:id="@+id/empty_view"
+ android:visibility="gone" />
+
+</FrameLayout>
diff --git a/res/layout/apn_preference_layout.xml b/res/layout/apn_preference_layout.xml
new file mode 100644
index 0000000..25a0323
--- /dev/null
+++ b/res/layout/apn_preference_layout.xml
@@ -0,0 +1,69 @@
+<?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.
+-->
+
+<!--
+ This is the layout file for an ApnPreference in the ApnSettingsActivity. The preference
+ is a list item.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ style="@style/ApnPreferenceLayoutStyle">
+
+ <RelativeLayout
+ android:id="@+id/text_layout"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:layout_weight="1"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground">
+
+ <TextView
+ android:id="@+android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem" />
+
+ <TextView
+ android:id="@+android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="2" />
+
+ </RelativeLayout>
+
+ <RadioButton
+ android:id="@+id/apn_radiobutton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dip"
+ android:layout_marginEnd="8dip"
+ android:layout_gravity="center_vertical"
+ android:clickable="true" />
+
+</LinearLayout>
diff --git a/res/layout/attachment_chooser_activity.xml b/res/layout/attachment_chooser_activity.xml
new file mode 100644
index 0000000..850716e
--- /dev/null
+++ b/res/layout/attachment_chooser_activity.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment"
+ android:id="@+id/attachment_chooser_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/attachment_chooser_audio.xml b/res/layout/attachment_chooser_audio.xml
new file mode 100644
index 0000000..ea2f431
--- /dev/null
+++ b/res/layout/attachment_chooser_audio.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/audio_attachment_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/attachment_chooser_audio_background">
+
+ <com.android.messaging.ui.AudioAttachmentView
+ android:id="@+id/audio_attachment_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ app:layoutMode="compact" />
+
+</FrameLayout>
diff --git a/res/layout/attachment_chooser_fragment.xml b/res/layout/attachment_chooser_fragment.xml
new file mode 100644
index 0000000..15e249a
--- /dev/null
+++ b/res/layout/attachment_chooser_fragment.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<com.android.messaging.ui.attachmentchooser.AttachmentGridView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/grid"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="6dp"
+ android:columnWidth="@dimen/attachment_grid_image_cell_size"
+ android:numColumns="auto_fit"
+ android:verticalSpacing="6dp"
+ android:horizontalSpacing="6dp"
+ android:stretchMode="columnWidth"
+ android:gravity="center"
+ android:clipToPadding="false" />
diff --git a/res/layout/attachment_chooser_image.xml b/res/layout/attachment_chooser_image.xml
new file mode 100644
index 0000000..b635161
--- /dev/null
+++ b/res/layout/attachment_chooser_image.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<com.android.messaging.ui.AsyncImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/attachment_image_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/single_attachment_min_dimen"
+ android:minHeight="@dimen/single_attachment_min_dimen"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true" />
diff --git a/res/layout/attachment_chooser_vcard.xml b/res/layout/attachment_chooser_vcard.xml
new file mode 100644
index 0000000..fb20f84
--- /dev/null
+++ b/res/layout/attachment_chooser_vcard.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/attachment_chooser_vcard_background">
+
+ <com.android.messaging.ui.PersonItemView
+ android:id="@+id/vcard_attachment_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+
+</FrameLayout>
diff --git a/res/layout/attachment_chooser_video.xml b/res/layout/attachment_chooser_video.xml
new file mode 100644
index 0000000..4ae4b0a
--- /dev/null
+++ b/res/layout/attachment_chooser_video.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<com.android.messaging.ui.VideoThumbnailView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/attachment_video_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:mode="imageThumbnail"
+ app:allowCrop="true" />
diff --git a/res/layout/attachment_grid_item_view.xml b/res/layout/attachment_grid_item_view.xml
new file mode 100644
index 0000000..639ea64
--- /dev/null
+++ b/res/layout/attachment_grid_item_view.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+<com.android.messaging.ui.attachmentchooser.AttachmentGridItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/attachment_grid_image_cell_size"
+ android:clickable="true">
+
+ <FrameLayout
+ android:id="@+id/attachment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:attr/selectableItemBackground" />
+
+ <CheckBox
+ android:id="@+id/checkbox"
+ style="@style/AttachmentGridItemViewCheckBoxStyle"
+ android:button="@drawable/gallery_checkbox_selector"
+ android:background="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:paddingTop="4dp"
+ android:contentDescription="@string/gallery_checkbox_content_description" />
+</com.android.messaging.ui.attachmentchooser.AttachmentGridItemView>
diff --git a/res/layout/attachment_more_text_view.xml b/res/layout/attachment_more_text_view.xml
new file mode 100644
index 0000000..1a99a63
--- /dev/null
+++ b/res/layout/attachment_more_text_view.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/AttachmentPreviewMoreItemsText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:background="@drawable/attachment_more_items_background" /> \ No newline at end of file
diff --git a/res/layout/attachment_multiple_audio.xml b/res/layout/attachment_multiple_audio.xml
new file mode 100644
index 0000000..48440f0
--- /dev/null
+++ b/res/layout/attachment_multiple_audio.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/audio_attachment_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/attachment_audio_preview_background">
+
+ <com.android.messaging.ui.AudioAttachmentView
+ android:id="@+id/audio_attachment_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ app:layoutMode="compact" />
+
+</FrameLayout>
diff --git a/res/layout/attachment_multiple_image.xml b/res/layout/attachment_multiple_image.xml
new file mode 100644
index 0000000..7d911ac
--- /dev/null
+++ b/res/layout/attachment_multiple_image.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<com.android.messaging.ui.AsyncImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/attachment_image_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxWidth="@dimen/multiple_attachment_preview_width"
+ android:maxHeight="@dimen/multiple_attachment_preview_height"
+ android:scaleType="centerCrop"
+ android:adjustViewBounds="true"
+ app:fadeIn="false"
+ app:cornerRadius="@dimen/attachment_rounded_corner_radius"
+ app:placeholderDrawable="@drawable/attachment_image_placeholder_background" />
diff --git a/res/layout/attachment_multiple_vcard.xml b/res/layout/attachment_multiple_vcard.xml
new file mode 100644
index 0000000..e03d47b
--- /dev/null
+++ b/res/layout/attachment_multiple_vcard.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/attachment_vcard_preview_background">
+
+ <com.android.messaging.ui.PersonItemView
+ android:id="@+id/vcard_attachment_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+
+</FrameLayout>
diff --git a/res/layout/attachment_multiple_video.xml b/res/layout/attachment_multiple_video.xml
new file mode 100644
index 0000000..fdbc423
--- /dev/null
+++ b/res/layout/attachment_multiple_video.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<com.android.messaging.ui.VideoThumbnailView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/attachment_video_thumbnail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:mode="imageThumbnail"
+ app:allowCrop="true" />
diff --git a/res/layout/attachment_pending_item.xml b/res/layout/attachment_pending_item.xml
new file mode 100644
index 0000000..3e9bec7
--- /dev/null
+++ b/res/layout/attachment_pending_item.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pending_item_view"
+ android:layout_width="@dimen/pending_attachment_size"
+ android:layout_height="@dimen/pending_attachment_size"
+ android:scaleType="center"
+ android:background="@android:color/white"
+ android:src="@drawable/ic_attachment_dark"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/caption"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:maxLines="3"
+ android:ellipsize="end"
+ android:padding="8dp"
+ android:background="@color/message_bubble_color_outgoing"
+ style="@style/ConversationMessage" />
+
+</LinearLayout>
diff --git a/res/layout/attachment_preview.xml b/res/layout/attachment_preview.xml
new file mode 100644
index 0000000..36ea038
--- /dev/null
+++ b/res/layout/attachment_preview.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<com.android.messaging.ui.AttachmentPreview
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:scrollbars="none">
+
+ <FrameLayout
+ android:id="@+id/attachment_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right">
+ <FrameLayout
+ android:id="@+id/attachment_view"
+ style="@style/AttachmentPreviewAttachmentStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp" />
+
+ <ImageButton
+ android:id="@+id/close_button"
+ style="@style/AttachmentPreviewCloseButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@null"
+ android:src="@drawable/ic_remove_light"
+ android:contentDescription="@null" />
+ </FrameLayout>
+</com.android.messaging.ui.AttachmentPreview> \ No newline at end of file
diff --git a/res/layout/attachment_single_audio.xml b/res/layout/attachment_single_audio.xml
new file mode 100644
index 0000000..3ad02a8
--- /dev/null
+++ b/res/layout/attachment_single_audio.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/audio_attachment_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/attachment_audio_preview_background"
+ style="@style/AudioAttachmentViewStyle">
+
+ <com.android.messaging.ui.AudioAttachmentView
+ android:id="@+id/audio_attachment_view"
+ android:layout_width="match_parent"
+ android:layout_height="70dp" />
+
+</FrameLayout>
diff --git a/res/layout/attachment_single_image.xml b/res/layout/attachment_single_image.xml
new file mode 100644
index 0000000..7338368
--- /dev/null
+++ b/res/layout/attachment_single_image.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <com.android.messaging.ui.AsyncImageView
+ android:id="@+id/attachment_image_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/single_attachment_min_dimen"
+ android:minHeight="@dimen/single_attachment_min_dimen"
+ android:maxHeight="@dimen/single_attachment_max_height"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ app:fadeIn="false"
+ app:cornerRadius="@dimen/attachment_rounded_corner_radius" />
+
+ <TextView
+ android:id="@+id/caption"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:maxLines="3"
+ android:ellipsize="end"
+ android:padding="8dp"
+ android:background="@color/message_bubble_color_outgoing"
+ style="@style/ConversationMessage" />
+
+</LinearLayout>
diff --git a/res/layout/attachment_single_vcard.xml b/res/layout/attachment_single_vcard.xml
new file mode 100644
index 0000000..6016593
--- /dev/null
+++ b/res/layout/attachment_single_vcard.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/attachment_vcard_preview_background">
+
+ <com.android.messaging.ui.PersonItemView
+ android:id="@+id/vcard_attachment_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/message_text_top_padding"
+ android:paddingBottom="@dimen/message_text_bottom_padding"
+ style="@style/VcardAttachmentSingleStyle" />
+
+</FrameLayout>
diff --git a/res/layout/attachment_single_video.xml b/res/layout/attachment_single_video.xml
new file mode 100644
index 0000000..fba594f
--- /dev/null
+++ b/res/layout/attachment_single_video.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<com.android.messaging.ui.VideoThumbnailView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/attachment_video_thumbnail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="120dp"
+ android:minHeight="120dp"
+ android:maxHeight="@dimen/single_attachment_max_height"
+ app:allowCrop="true"
+ app:mode="imageThumbnail" />
diff --git a/res/layout/audio_attachment_view.xml b/res/layout/audio_attachment_view.xml
new file mode 100644
index 0000000..6fcf89c
--- /dev/null
+++ b/res/layout/audio_attachment_view.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+<!-- A reusable view that hosts an inline audio player. The view is used by both the media picker
+ and the conversation to show an audio attachment -->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <com.android.messaging.ui.AudioAttachmentPlayPauseButton
+ android:id="@+id/play_pause_button"
+ style="@style/AudioAttachmentViewPlayPauseButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+ <ImageView
+ android:id="@+id/play_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_audio_play"
+ android:contentDescription="@string/audio_play_content_description"/>
+
+ <ImageView
+ android:id="@+id/pause_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_audio_pause"
+ android:contentDescription="@string/audio_pause_content_description"/>
+ </com.android.messaging.ui.AudioAttachmentPlayPauseButton>
+
+ <com.android.messaging.ui.mediapicker.PausableChronometer
+ android:id="@+id/timer"
+ style="@style/AudioAttachmentTimerText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
+
+ <com.android.messaging.ui.AudioPlaybackProgressBar
+ android:id="@+id/progress"
+ style="@android:style/Widget.ProgressBar.Horizontal"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="@dimen/audio_progress_bar_height"
+ android:layout_gravity="center" />
+
+</merge>
diff --git a/res/layout/blocked_participant_list_item_view.xml b/res/layout/blocked_participant_list_item_view.xml
new file mode 100644
index 0000000..7ac0ec9
--- /dev/null
+++ b/res/layout/blocked_participant_list_item_view.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+<com.android.messaging.ui.BlockedParticipantListItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="@dimen/blocked_participant_list_item_view_padding">
+ <com.android.messaging.ui.ContactIconView
+ android:id="@+id/contact_icon"
+ android:layout_width="@dimen/contact_list_icon_size"
+ android:layout_height="@dimen/contact_list_icon_size"
+ android:importantForAccessibility="no"
+ app:iconSize="normal"
+ android:layout_gravity="center_vertical"/>
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ParticipantListItem"
+ android:layout_gravity="center_vertical"
+ android:padding="@dimen/blocked_participant_list_item_view_padding"/>
+ <TextView
+ android:id="@+id/tap_to_unblock"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textColor="@color/unblock_item_text_color"
+ android:text="@string/tap_to_unblock_message"
+ android:layout_gravity="center_vertical"
+ android:gravity="right"
+ android:padding="@dimen/blocked_participant_list_item_view_padding"/>
+</com.android.messaging.ui.BlockedParticipantListItemView>
diff --git a/res/layout/blocked_participants_activity.xml b/res/layout/blocked_participants_activity.xml
new file mode 100644
index 0000000..e00f20f
--- /dev/null
+++ b/res/layout/blocked_participants_activity.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.BlockedParticipantsFragment"
+ android:id="@+id/blocked_participants_fragment"
+ android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
+ android:layout_width="fill_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/blocked_participants_fragment.xml b/res/layout/blocked_participants_fragment.xml
new file mode 100644
index 0000000..7bb3c6a
--- /dev/null
+++ b/res/layout/blocked_participants_fragment.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="match_parent"
+ android:divider="@null"
+ android:dividerHeight="0px" /> \ No newline at end of file
diff --git a/res/layout/chips_alternates_dropdown_item.xml b/res/layout/chips_alternates_dropdown_item.xml
new file mode 100644
index 0000000..dad052c
--- /dev/null
+++ b/res/layout/chips_alternates_dropdown_item.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="72dp"
+ android:background="@drawable/chips_dropdown_background"
+ android:padding="12dp">
+
+ <com.android.messaging.ui.ContactIconView
+ android:id="@android:id/icon"
+ style="@style/ChipIconStyle"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_gravity="center_vertical"
+ android:importantForAccessibility="no" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textColor="@drawable/chips_dropdown_text_color"
+ android:textSize="16sp" />
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textColor="@drawable/chips_dropdown_text_color"
+ android:textSize="14sp"/>
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@android:id/icon1"
+ style="@style/ChipDeleteIconStyle"
+ android:layout_width="36dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:paddingTop="10dp"
+ android:paddingBottom="10dp"
+ android:contentDescription="@string/chips_delete_content_description" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/compose_message_view.xml b/res/layout/compose_message_view.xml
new file mode 100644
index 0000000..fd60e2a
--- /dev/null
+++ b/res/layout/compose_message_view.xml
@@ -0,0 +1,187 @@
+<?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.
+-->
+<com.android.messaging.ui.conversation.ComposeMessageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/message_compose_view_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <!-- Note it may seem off to have the layout_weight="1" on the compose bar and attachment view
+ when it's the media picker that expands, but this is correct because weight also works when
+ there's not enough space. Any views with positive weights will be shrunk to make space.
+ When the media picker goes full screen, it fills the parent, leaving no room for the compose
+ bar -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/top_margin"
+ android:layout_width="match_parent"
+ android:layout_height="16dp" />
+
+ <include layout="@layout/attachment_preview"
+ android:id="@+id/attachment_draft_view"
+ style="@style/ComposeMessageViewDraftViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginBottom="4dp"
+ android:layout_weight="1"
+ android:visibility="gone" />
+
+ <RelativeLayout
+ android:id="@+id/subject_view"
+ style="@style/SubjectViewStyle"
+ android:paddingTop="@dimen/compose_message_subject_top_padding"
+ android:paddingBottom="@dimen/compose_message_subject_bottom_padding"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <com.android.messaging.ui.PlainTextEditText
+ android:background="@drawable/subject_editor_bubble"
+ android:id="@+id/compose_subject_text"
+ style="@style/ConversationComposeSubjectText"
+ android:hint="@string/compose_message_view_subject_hint_text"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:imeOptions="flagNoExtractUi" />
+
+ <ImageButton
+ android:id="@+id/delete_subject_button"
+ style="@style/AttachmentPreviewCloseButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@null"
+ android:src="@drawable/ic_remove_light"
+ android:translationY="@dimen/compose_message_subject_cancel_top_offset"
+ android:contentDescription="@string/delete_subject_content_description" />
+
+ </RelativeLayout>
+
+ <!-- Holds all views that create the actual compose box -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <!-- Contains compose message bubble and character counter for SMS which should be left
+ aligned -->
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- Contains media button and compose message bubble whose centers should be
+ vertically aligned -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageButton
+ android:id="@+id/attach_media_button"
+ style="@style/ComposeMessageViewAttachMediaButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/ic_attachment_dark"
+ android:paddingTop="@dimen/compose_message_attachment_padding_topBottom"
+ android:paddingBottom="@dimen/compose_message_attachment_padding_topBottom"
+ android:contentDescription="@string/attachMediaButtonContentDescription" />
+
+ <com.android.messaging.ui.PlainTextEditText
+ android:id="@+id/compose_message_text"
+ style="@style/ConversationComposeSendText"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_gravity="center"
+ android:background="@drawable/msg_bubble_input"
+ android:hint="@string/compose_message_view_hint_text"
+ android:imeOptions="actionSend|flagNoEnterAction|flagNoExtractUi" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/char_counter"
+ style="@style/ComposeMessageViewTextCounterStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:visibility="invisible" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ComposeMessageViewFrameLayoutStyle"
+ android:layout_gravity="top" >
+ <com.android.messaging.ui.conversation.SimIconView
+ android:id="@+id/self_send_icon"
+ android:layout_width="@dimen/conversation_message_contact_icon_size"
+ android:layout_height="@dimen/conversation_message_contact_icon_size"
+ android:importantForAccessibility="no"
+ app:iconSize="normal"
+ app:reveal="true"
+ android:clickable="true" />
+ <ImageButton
+ android:id="@+id/send_message_button"
+ android:layout_width="@dimen/conversation_message_contact_icon_size"
+ android:layout_height="@dimen/conversation_message_contact_icon_size"
+ android:src="@drawable/ic_send_light"
+ android:background="@drawable/send_arrow_background"
+ android:contentDescription="@string/sendButtonContentDescription"
+ android:visibility="gone" />
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/mms_indicator"
+ style="@style/MmsIndicatorStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:visibility="invisible"
+ android:text="@string/mms_text" />
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/mediapicker_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0" />
+
+</com.android.messaging.ui.conversation.ComposeMessageView>
diff --git a/res/layout/contact_list_item_view.xml b/res/layout/contact_list_item_view.xml
new file mode 100644
index 0000000..3015ae3
--- /dev/null
+++ b/res/layout/contact_list_item_view.xml
@@ -0,0 +1,97 @@
+<?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.
+-->
+<com.android.messaging.ui.contact.ContactListItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ style="@style/ContactListItemViewStyle"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:background="?android:attr/selectableItemBackground">
+
+ <TextView
+ style="@style/ContactListAlphabetHeader"
+ android:id="@+id/alphabet_header"
+ android:layout_height="wrap_content"
+ android:layout_width="56dp"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone" />
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+
+ <com.android.messaging.ui.ContactIconView
+ android:id="@+id/contact_icon"
+ android:layout_width="@dimen/contact_list_icon_size"
+ android:layout_height="@dimen/contact_list_icon_size"
+ android:importantForAccessibility="no"
+ app:iconSize="normal"
+ android:clickable="true" />
+
+ <ImageView
+ android:id="@+id/contact_checkmark"
+ android:layout_width="@dimen/contact_list_icon_size"
+ android:layout_height="@dimen/contact_list_icon_size"
+ android:src="@drawable/ic_checkmark_circle_blue"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ style="@style/ContactListItemLinearLayoutStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" >
+ <TextView
+ android:id="@+id/contact_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingBottom="4dp"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ContactListItem" />
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/contact_details"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ContactListItemDetail" />
+
+ <TextView
+ android:id="@+id/contact_detail_type"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ContactListItemDetailType" />
+ </LinearLayout>
+ </LinearLayout>
+</com.android.messaging.ui.contact.ContactListItemView>
diff --git a/res/layout/contact_picker_fragment.xml b/res/layout/contact_picker_fragment.xml
new file mode 100644
index 0000000..c65f140
--- /dev/null
+++ b/res/layout/contact_picker_fragment.xml
@@ -0,0 +1,79 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?attr/actionBarSize"
+ android:background="@color/action_bar_background_color"
+ app:theme="@style/ThemeOverlay.AppCompat.ActionBar">
+
+ <com.android.messaging.ui.MaxHeightScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:maxHeight="@dimen/compose_message_chips_view_max_height">
+
+ <com.android.messaging.ui.contact.ContactRecipientAutoCompleteView
+ android:id="@+id/recipient_text_view"
+ style="@style/RecipientEditTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:minHeight="@dimen/compose_message_contacts_height"
+ android:popupBackground="@drawable/contact_popup_background"
+ android:dropDownWidth="match_parent"
+ android:hint="@string/recipient_hint"
+ app:avatarPosition="start"
+ app:chipHeight="@dimen/compose_message_chip_height"
+ app:chipPadding="@dimen/compose_message_chip_padding"
+ app:imageSpanAlignment="baseline"
+ app:unselectedChipTextColor="@color/chips_text_color"
+ app:unselectedChipBackgroundColor="@color/chips_background_color">
+
+ <!-- Put focus on the chips view by default so soft keyboard can be shown -->
+ <requestFocus />
+ </com.android.messaging.ui.contact.ContactRecipientAutoCompleteView>
+
+ </com.android.messaging.ui.MaxHeightScrollView>
+
+ </android.support.v7.widget.Toolbar>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <com.android.messaging.ui.CustomHeaderViewPager
+ android:id="@+id/contact_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <View
+ android:id="@+id/compose_contact_divider"
+ android:layout_width="match_parent"
+ android:layout_height="4dp"
+ android:layout_gravity="top"
+ android:background="@drawable/compose_chips_divider_gradient"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
new file mode 100644
index 0000000..8879704
--- /dev/null
+++ b/res/layout/conversation_activity.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<com.android.messaging.ui.ImeDetectFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/conversation_and_compose_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/conversation_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <FrameLayout
+ android:id="@+id/contact_picker_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</com.android.messaging.ui.ImeDetectFrameLayout> \ No newline at end of file
diff --git a/res/layout/conversation_fragment.xml b/res/layout/conversation_fragment.xml
new file mode 100644
index 0000000..028e66b
--- /dev/null
+++ b/res/layout/conversation_fragment.xml
@@ -0,0 +1,68 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:weightSum="1.0">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical">
+ <!-- This FrameLayout will take all available height (excluding media picker)
+ but setting height to 0dp causes it not to shrink properly in KK, so a height of match_parent
+ works equally well -->
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1" >
+ <!-- Message list -->
+ <android.support.v7.widget.RecyclerView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:listSelector="@android:color/transparent"
+ android:paddingTop="@dimen/action_bar_height"
+ android:clipToPadding="false" />
+
+ <View
+ android:id="@+id/conversation_compose_divider"
+ android:layout_width="match_parent"
+ android:layout_height="4dp"
+ android:layout_gravity="bottom"
+ android:background="@drawable/conversation_compose_divider_gradient"
+ android:alpha="0"
+ android:importantForAccessibility="no" />
+
+ <include layout="@layout/sim_selector_view"
+ android:id="@+id/sim_selector"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ </FrameLayout>
+
+ <!-- Attachments to send, compose message view, media picker. -->
+ <include layout="@layout/compose_message_view"
+ android:id="@+id/message_compose_view_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/conversation_list_activity.xml b/res/layout/conversation_list_activity.xml
new file mode 100644
index 0000000..48f3b15
--- /dev/null
+++ b/res/layout/conversation_list_activity.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.conversationlist.ConversationListFragment"
+ android:id="@+id/conversation_list_fragment"
+ android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/conversation_list_fragment.xml b/res/layout/conversation_list_fragment.xml
new file mode 100644
index 0000000..bf5d401
--- /dev/null
+++ b/res/layout/conversation_list_fragment.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/conversation_list_first_item_extra_padding"
+ android:clipToPadding="false"
+ android:listSelector="?android:attr/listSelector"
+ android:dividerHeight="0px" />
+
+ <include
+ layout="@layout/list_empty_view"
+ android:id="@+id/no_conversations_view"
+ android:visibility="gone"
+ android:layout_marginBottom="@dimen/conversation_list_empty_text_bottom_margin" />
+
+ <ImageView
+ style="@style/ConversationListFragmentStartNewButtonStyle"
+ android:id="@+id/start_new_conversation_button"
+ android:layout_width="@dimen/fab_size"
+ android:layout_height="@dimen/fab_size"
+ android:layout_gravity="bottom|end"
+ android:layout_marginBottom="@dimen/fab_bottom_margin"
+ android:paddingBottom="@dimen/fab_padding_bottom"
+ android:background="@drawable/fab_new_message_bg"
+ android:elevation="@dimen/fab_elevation"
+ android:scaleType="center"
+ android:src="@drawable/ic_add_white"
+ android:stateListAnimator="@animator/fab_anim"
+ android:contentDescription="@string/start_new_conversation"/>
+
+</FrameLayout>
diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml
new file mode 100644
index 0000000..da1ca4e
--- /dev/null
+++ b/res/layout/conversation_list_item_view.xml
@@ -0,0 +1,190 @@
+<?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.
+-->
+<com.android.messaging.ui.conversationlist.ConversationListItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <!-- The background displayed when the conversation is swiped sideways -->
+ <LinearLayout
+ android:id="@+id/crossSwipeBackground"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|start"
+ android:baselineAligned="false"
+ android:background="@drawable/swipe_shadow"
+ android:visibility="gone"
+ android:padding="@dimen/conversation_list_item_view_padding">
+ <ImageView
+ android:id="@+id/crossSwipeArchiveIconLeft"
+ android:layout_width="wrap_content"
+ 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
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+ <ImageView
+ android:id="@+id/crossSwipeArchiveIconRight"
+ android:layout_width="wrap_content"
+ 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>
+
+ <FrameLayout
+ android:id="@+id/swipeableContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no" >
+ <LinearLayout
+ android:id="@+id/swipeableContent"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:duplicateParentState="true"
+ android:padding="@dimen/conversation_list_item_view_padding">
+
+ <!-- Contact avatar on the left side of the view -->
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent">
+ <com.android.messaging.ui.ContactIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/conversation_list_contact_icon_size"
+ android:layout_height="@dimen/conversation_list_contact_icon_size"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
+ app:iconSize="large"
+ android:clickable="true"
+ android:background="@android:color/transparent" />
+ <ImageView
+ android:id="@+id/conversation_checkmark"
+ android:layout_width="@dimen/conversation_list_contact_icon_size"
+ android:layout_height="@dimen/conversation_list_contact_icon_size"
+ android:src="@drawable/ic_checkmark_circle_blue"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:background="@android:color/transparent" />
+ <ImageView
+ android:id="@+id/conversation_failed_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:src="@drawable/ic_failed_status_red"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
+ android:background="@android:color/transparent" />
+ </FrameLayout>
+
+ <!-- The conversation name, message preview, etc -->
+ <LinearLayout
+ style="@style/ConversationListItemViewPaddingStyle"
+ android:orientation="vertical"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="top"
+ android:background="@android:color/transparent">
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent">
+ <ImageView
+ android:id="@+id/conversation_notification_bell"
+ style="@style/ConversationListNotificationBellPaddingStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_notifications_off_small_light"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
+ android:layout_gravity="center_vertical"
+ android:background="@android:color/transparent" />
+ <TextView
+ android:id="@+id/conversation_name"
+ style="@style/ConversationListItemViewConversationNameStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:layout_gravity="center_vertical"
+ android:paddingBottom="2dp" />
+ </LinearLayout>
+ <TextView
+ android:id="@+id/conversation_subject"
+ style="@style/ConversationListItemViewTextStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:paddingBottom="3dp"
+ android:ellipsize="end"
+ android:visibility="gone" />
+ <TextView
+ android:id="@+id/conversation_snippet"
+ style="@style/ConversationListItemViewTextStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="3dp"
+ android:ellipsize="end" />
+ <TextView
+ android:id="@+id/conversation_timestamp"
+ style="@style/ConversationListItemViewTextStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <!-- The attachment preview on the right side of the view -->
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:background="@android:color/transparent">
+ <com.android.messaging.ui.AsyncImageView
+ android:id="@+id/conversation_image_preview"
+ style="@style/ConversationListItemViewPaddingStyle"
+ app:cornerRadius="@dimen/conversation_list_image_preview_corner_radius"
+ android:layout_width="@dimen/conversation_list_image_preview_size"
+ android:layout_height="@dimen/conversation_list_image_preview_size"
+ android:visibility="gone"
+ android:scaleType="centerCrop"
+ android:contentDescription="@string/message_image_content_description"
+ android:background="@android:color/transparent" />
+ <com.android.messaging.ui.AudioAttachmentView
+ android:id="@+id/audio_attachment_view"
+ android:layout_width="@dimen/conversation_list_image_preview_size"
+ android:layout_height="@dimen/conversation_list_image_preview_size"
+ app:layoutMode="subcompact"
+ android:visibility="gone"
+ android:background="@android:color/transparent" />
+ </FrameLayout>
+ </LinearLayout>
+ </FrameLayout>
+</com.android.messaging.ui.conversationlist.ConversationListItemView>
diff --git a/res/layout/conversation_message_view.xml b/res/layout/conversation_message_view.xml
new file mode 100644
index 0000000..daad600
--- /dev/null
+++ b/res/layout/conversation_message_view.xml
@@ -0,0 +1,171 @@
+<?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.
+-->
+<com.android.messaging.ui.conversation.ConversationMessageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ style="@style/ConversationMessageViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <com.android.messaging.ui.ContactIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/conversation_message_contact_icon_size"
+ android:layout_height="@dimen/conversation_message_contact_icon_size"
+ app:iconSize="normal"
+ android:clickable="true"
+ android:layout_gravity="center_vertical"
+ android:importantForAccessibility="no" />
+
+ <com.android.messaging.ui.conversation.ConversationMessageBubbleView
+ android:id="@+id/message_content"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+
+ <LinearLayout
+ android:id="@+id/message_attachments"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="gone" >
+
+ <!-- Video, audio and vcard attachments (if present) will be added here -->
+
+ <com.android.messaging.ui.MultiAttachmentLayout
+ android:id="@+id/multiple_attachments"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+ <com.android.messaging.ui.AsyncImageView
+ android:id="@+id/message_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/conversation_message_photo_min_size"
+ android:minHeight="@dimen/conversation_message_photo_min_size"
+ android:adjustViewBounds="true"
+ android:scaleType="fitCenter"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone"
+ app:placeholderDrawable="@drawable/attachment_image_placeholder_background"
+ app:cornerRadius="@dimen/attachment_rounded_corner_radius"
+ android:contentDescription="@string/message_image_content_description" />
+
+ </LinearLayout>
+
+ <com.android.messaging.ui.conversation.MessageBubbleBackground
+ android:id="@+id/message_text_and_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/message_title_layout"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:importantForAccessibility="noHideDescendants" >
+
+ <TextView
+ android:id="@+id/message_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ConversationMessageTitle" />
+
+ <TextView
+ android:id="@+id/mms_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ConversationMessageInfo" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/subject_container"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="noHideDescendants" >
+
+ <TextView
+ android:id="@+id/subject_label"
+ android:text="@string/conversation_message_view_subject_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ConversationMessage" />
+
+ <TextView
+ android:id="@+id/subject_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ConversationMessage" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/message_text"
+ style="@style/ConversationMessage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:importantForAccessibility="no" />
+
+ <com.android.messaging.ui.LineWrapLayout
+ android:id="@+id/message_metadata"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:importantForAccessibility="noHideDescendants" >
+
+ <TextView
+ android:id="@+id/message_sender_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="4dp"
+ style="@style/ConversationMessageStatus"
+ android:visibility="gone" />
+ <TextView
+ android:id="@+id/message_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ConversationMessageStatus"
+ android:visibility="gone" />
+ <TextView
+ android:id="@+id/sim_name"
+ style="@style/MessageSimIndicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:visibility="gone" />
+ <ImageView
+ android:id="@+id/smsDeliveredBadge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ style="@style/SmsDeliverdBadge"
+ android:src="@drawable/ic_sms_delivery_ok"
+ android:visibility="gone" />
+ </com.android.messaging.ui.LineWrapLayout>
+
+ </com.android.messaging.ui.conversation.MessageBubbleBackground>
+
+ </com.android.messaging.ui.conversation.ConversationMessageBubbleView>
+
+</com.android.messaging.ui.conversation.ConversationMessageView>
diff --git a/res/layout/copy_contact_dialog_view.xml b/res/layout/copy_contact_dialog_view.xml
new file mode 100644
index 0000000..15e07f7
--- /dev/null
+++ b/res/layout/copy_contact_dialog_view.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/CopyContactDialogTextStyle"
+ android:ellipsize="end"
+ android:paddingTop="@dimen/copy_contact_dialog_top_padding"/> \ No newline at end of file
diff --git a/res/layout/custom_header_view_pager.xml b/res/layout/custom_header_view_pager.xml
new file mode 100644
index 0000000..9552cd6
--- /dev/null
+++ b/res/layout/custom_header_view_pager.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.messaging.ui.ViewPagerTabs
+ android:id="@+id/tab_strip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAllCaps="true"
+ android:orientation="horizontal"
+ style="@style/PagerTabHeader" />
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="2dp"
+ android:layout_gravity="top"
+ android:background="@drawable/compose_chips_divider_gradient"/>
+
+ </FrameLayout>
+</merge> \ No newline at end of file
diff --git a/res/layout/debug_mmsconfig_activity.xml b/res/layout/debug_mmsconfig_activity.xml
new file mode 100644
index 0000000..4056317
--- /dev/null
+++ b/res/layout/debug_mmsconfig_activity.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.debug.DebugMmsConfigFragment"
+ android:id="@+id/debug_mmsconfig_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/debug_mmsconfig_fragment.xml b/res/layout/debug_mmsconfig_fragment.xml
new file mode 100644
index 0000000..be61710
--- /dev/null
+++ b/res/layout/debug_mmsconfig_fragment.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:divider="@null"
+ android:dividerHeight="0px" /> \ No newline at end of file
diff --git a/res/layout/debug_mmsconfig_item_view.xml b/res/layout/debug_mmsconfig_item_view.xml
new file mode 100644
index 0000000..a825ee5
--- /dev/null
+++ b/res/layout/debug_mmsconfig_item_view.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<com.android.messaging.ui.debug.DebugMmsConfigItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:background="@android:color/background_light"
+ android:gravity="center_vertical"
+ android:clickable="true"
+ style="@style/DebugMmsConfigItemStyle" >
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:gravity="center_vertical"/>
+ <Switch
+ android:id="@+id/switch_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+ <TextView
+ android:id="@+id/text_value"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:singleLine="true"
+ android:gravity="center_vertical"
+ android:visibility="gone"/>
+</com.android.messaging.ui.debug.DebugMmsConfigItemView>
diff --git a/res/layout/debug_sms_mms_from_dump_file_dialog.xml b/res/layout/debug_sms_mms_from_dump_file_dialog.xml
new file mode 100644
index 0000000..342f489
--- /dev/null
+++ b/res/layout/debug_sms_mms_from_dump_file_dialog.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="6dp"
+ android:background="@color/conversation_background"
+ >
+
+ <ListView android:id="@+id/dump_file_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"
+ android:longClickable="false"/>
+
+</FrameLayout>
diff --git a/res/layout/enter_phone_number_view.xml b/res/layout/enter_phone_number_view.xml
new file mode 100644
index 0000000..825ac51
--- /dev/null
+++ b/res/layout/enter_phone_number_view.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<EditText
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:hint="@string/enter_phone_number_hint"
+ android:inputType="number|phone"
+ />
diff --git a/res/layout/fastscroll_preview.xml b/res/layout/fastscroll_preview.xml
new file mode 100644
index 0000000..ab72744
--- /dev/null
+++ b/res/layout/fastscroll_preview.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingLeft="@dimen/fastscroll_preview_padding"
+ android:paddingRight="@dimen/fastscroll_preview_padding"
+ android:textColor="@color/fastscroll_preview_text_color"
+ android:textSize="@dimen/fastscroll_preview_text_size" />
diff --git a/res/layout/fastscroll_thumb.xml b/res/layout/fastscroll_thumb.xml
new file mode 100644
index 0000000..6f57056
--- /dev/null
+++ b/res/layout/fastscroll_thumb.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:scaleType="fitXY" /> \ No newline at end of file
diff --git a/res/layout/fastscroll_track.xml b/res/layout/fastscroll_track.xml
new file mode 100644
index 0000000..6a35e94
--- /dev/null
+++ b/res/layout/fastscroll_track.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:scaleType="fitXY"
+ android:src="@drawable/fastscroll_track" />
diff --git a/res/layout/frequent_contacts_list_view.xml b/res/layout/frequent_contacts_list_view.xml
new file mode 100644
index 0000000..9cb0c3a
--- /dev/null
+++ b/res/layout/frequent_contacts_list_view.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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView
+ android:id="@+id/frequent_contacts_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:clipToPadding="false"
+ android:divider="@null"
+ android:dividerHeight="0px"
+ android:fastScrollEnabled="true" />
+
+ <include
+ layout="@layout/list_empty_view"
+ android:id="@+id/empty_view"
+ android:visibility="gone" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/gallery_grid_item_view.xml b/res/layout/gallery_grid_item_view.xml
new file mode 100644
index 0000000..8b7ee58
--- /dev/null
+++ b/res/layout/gallery_grid_item_view.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<com.android.messaging.ui.mediapicker.GalleryGridItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/gallery_image_cell_size"
+ android:background="@color/gallery_image_default_background"
+ android:clickable="true">
+
+ <com.android.messaging.ui.AsyncImageView
+ android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/gallery_image_background_selector" />
+
+ <CheckBox
+ android:id="@+id/checkbox"
+ style="@style/GalleryGridItemViewCheckBoxStyle"
+ android:button="@drawable/gallery_checkbox_selector"
+ android:background="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:paddingTop="4dp"
+ android:visibility="gone"
+ android:contentDescription="@string/gallery_checkbox_content_description" />
+</com.android.messaging.ui.mediapicker.GalleryGridItemView>
diff --git a/res/layout/group_mms_setting_dialog.xml b/res/layout/group_mms_setting_dialog.xml
new file mode 100644
index 0000000..efc3b00
--- /dev/null
+++ b/res/layout/group_mms_setting_dialog.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<RadioGroup
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <RadioButton
+ android:id="@+id/disable_group_mms_button"
+ style="@style/GroupMmsSettingItem"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/disable_group_mms"/>
+
+ <RadioButton
+ android:id="@+id/enable_group_mms_button"
+ style="@style/GroupMmsSettingItem"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enable_group_mms"/>
+
+</RadioGroup>
diff --git a/res/layout/license_activity.xml b/res/layout/license_activity.xml
new file mode 100644
index 0000000..1475ac2
--- /dev/null
+++ b/res/layout/license_activity.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<WebView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+</WebView> \ No newline at end of file
diff --git a/res/layout/list_empty_view.xml b/res/layout/list_empty_view.xml
new file mode 100644
index 0000000..aa4b4fb
--- /dev/null
+++ b/res/layout/list_empty_view.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<com.android.messaging.ui.ListEmptyView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/empty_image_hint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/empty_text_hint"
+ android:gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginLeft="@dimen/list_empty_text_left_right_margin"
+ android:layout_marginRight="@dimen/list_empty_text_left_right_margin"
+ android:layout_marginTop="@dimen/list_empty_text_top_margin"
+ style="@style/ListEmptyText"/>
+</com.android.messaging.ui.ListEmptyView>
diff --git a/res/layout/mediapicker_audio_chooser.xml b/res/layout/mediapicker_audio_chooser.xml
new file mode 100644
index 0000000..795d2f8
--- /dev/null
+++ b/res/layout/mediapicker_audio_chooser.xml
@@ -0,0 +1,96 @@
+<?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.
+-->
+<com.android.messaging.ui.mediapicker.AudioRecordView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/white"
+ android:importantForAccessibility="no" >
+
+ <FrameLayout
+ android:id="@+id/mediapicker_enabled"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TextView
+ android:id="@+id/hint_text"
+ style="@style/AudioPickerHintText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center"
+ android:layout_marginTop="16dp"
+ android:text="@string/audio_picker_hint_text"
+ android:importantForAccessibility="no" />
+
+ <com.android.messaging.ui.mediapicker.PausableChronometer
+ android:id="@+id/timer_text"
+ style="@style/AudioPickerTimerText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center"
+ android:layout_marginTop="16dp"
+ android:visibility="gone" />
+
+ <com.android.messaging.ui.mediapicker.SoundLevels
+ android:id="@+id/sound_levels"
+ android:layout_width="260dp"
+ android:layout_height="260dp"
+ android:layout_gravity="center"
+ android:focusableInTouchMode="false"
+ app:minLevelRadius="55dp"
+ app:maxLevelRadius="130dp"
+ app:primaryColor="@color/audio_picker_level_primary_color" />
+
+ <!-- View to make the touachable area larger for accessibility's two-finger touch -->
+ <FrameLayout
+ android:id="@+id/record_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:padding="30dp"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:contentDescription="@string/audio_record_view_content_description" >
+
+ <ImageView
+ android:id="@+id/record_button_visual"
+ android:layout_width="70dp"
+ android:layout_height="70dp"
+ android:layout_margin="30dp"
+ android:layout_gravity="center"
+ android:scaleType="center"
+ android:src="@drawable/ic_mp_audio_mic"
+ android:background="@drawable/audio_record_control_button_background"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ </FrameLayout>
+
+ </FrameLayout>
+
+ <!-- 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" />
+
+</com.android.messaging.ui.mediapicker.AudioRecordView>
diff --git a/res/layout/mediapicker_camera_chooser.xml b/res/layout/mediapicker_camera_chooser.xml
new file mode 100644
index 0000000..27d26bd
--- /dev/null
+++ b/res/layout/mediapicker_camera_chooser.xml
@@ -0,0 +1,150 @@
+<?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.
+-->
+
+<com.android.messaging.ui.mediapicker.CameraMediaChooserView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black">
+
+ <FrameLayout
+ android:id="@+id/mediapicker_enabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <!-- Default to using the hardware rendered camera preview, we will fall back to
+ SoftwareCameraPreview in CameraMediaChooserView if needed -->
+ <com.android.messaging.ui.mediapicker.HardwareCameraPreview
+ android:id="@+id/camera_preview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ <com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay
+ android:id="@+id/focus_visual"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <View
+ android:id="@+id/camera_shutter_visual"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/white"
+ android:visibility="gone" />
+
+ <!-- Need a background on this view in order for the ripple effect to have a place to draw -->
+ <FrameLayout
+ android:id="@+id/camera_button_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:paddingTop="20dp"
+ android:layout_gravity="bottom">
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ style="@style/CameraChooserFrameStyle">
+
+ <ImageButton
+ android:id="@+id/camera_swap_mode_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:background="@drawable/transparent_button_background"
+ android:src="@drawable/ic_mp_video_small_light"
+ android:contentDescription="@string/camera_switch_to_video_mode"/>
+
+ <Chronometer
+ android:id="@+id/camera_video_counter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/white"
+ android:textSize="18sp"
+ android:padding="16dp"
+ android:visibility="gone" />
+
+ </FrameLayout>
+
+ <ImageButton
+ android:id="@+id/camera_capture_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:minWidth="96dp"
+ android:minHeight="96dp"
+ android:background="@drawable/transparent_button_background"
+ android:src="@drawable/ic_checkmark_large_light"
+ android:contentDescription="@string/camera_take_picture"/>
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|center_vertical"
+ android:paddingEnd="16dp">
+
+ <ImageButton
+ android:id="@+id/camera_fullScreen_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:background="@drawable/transparent_button_background"
+ android:src="@drawable/ic_mp_full_screen_light"
+ android:contentDescription="@string/camera_switch_full_screen" />
+
+ <ImageButton
+ android:id="@+id/camera_swapCamera_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:background="@drawable/transparent_button_background"
+ android:visibility="gone"
+ android:contentDescription="@string/camera_switch_camera_facing"/>
+
+ <ImageButton
+ android:id="@+id/camera_cancel_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:background="@drawable/transparent_button_background"
+ android:src="@drawable/ic_remove_small_light"
+ android:visibility="gone"
+ android:contentDescription="@string/camera_cancel_recording" />
+
+ </FrameLayout>
+
+ </FrameLayout>
+
+ </FrameLayout>
+
+ <!-- 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" />
+
+</com.android.messaging.ui.mediapicker.CameraMediaChooserView>
diff --git a/res/layout/mediapicker_fragment.xml b/res/layout/mediapicker_fragment.xml
new file mode 100644
index 0000000..2e414bd
--- /dev/null
+++ b/res/layout/mediapicker_fragment.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+
+<com.android.messaging.ui.mediapicker.MediaPickerPanel
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.android.messaging.ui.PagingAwareViewPager
+ android:id="@+id/mediapicker_view_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/mediapicker_default_chooser_height" />
+ <LinearLayout
+ android:id="@+id/mediapicker_tabstrip"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="horizontal"
+ android:gravity="center" />
+</com.android.messaging.ui.mediapicker.MediaPickerPanel>
diff --git a/res/layout/mediapicker_image_chooser.xml b/res/layout/mediapicker_image_chooser.xml
new file mode 100644
index 0000000..b4f3c01
--- /dev/null
+++ b/res/layout/mediapicker_image_chooser.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<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.GalleryGridView
+ android:id="@+id/gallery_grid_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="6dp"
+ android:columnWidth="@dimen/gallery_image_cell_size"
+ android:numColumns="auto_fit"
+ android:verticalSpacing="6dp"
+ android:horizontalSpacing="6dp"
+ android:stretchMode="columnWidth"
+ android:gravity="center"
+ android:clipToPadding="false"
+ 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/mediapicker_location_chooser.xml b/res/layout/mediapicker_location_chooser.xml
new file mode 100644
index 0000000..9135299
--- /dev/null
+++ b/res/layout/mediapicker_location_chooser.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/place_picker_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:name="com.google.android.location.places.ui.placepicker.PlacePickerFragment" />
diff --git a/res/layout/mediapicker_location_container.xml b/res/layout/mediapicker_location_container.xml
new file mode 100644
index 0000000..6f476d3
--- /dev/null
+++ b/res/layout/mediapicker_location_container.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/place_picker_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <!-- This view will be hidden by the PlacePickerFragment if the required permission is granted -->
+ <TextView
+ android:id="@+id/missing_permission_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/enable_permission_procedure"
+ android:contentDescription="@string/enable_permission_procedure_description" />
+ </FrameLayout>
diff --git a/res/layout/mediapicker_tab_button.xml b/res/layout/mediapicker_tab_button.xml
new file mode 100644
index 0000000..353c871
--- /dev/null
+++ b/res/layout/mediapicker_tab_button.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- android:contentDescription is set in code -->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:background="@drawable/mediapicker_tab_button_background"
+ android:contentDescription="@null">
+</ImageButton> \ No newline at end of file
diff --git a/res/layout/message_audio_attachment.xml b/res/layout/message_audio_attachment.xml
new file mode 100644
index 0000000..0854269
--- /dev/null
+++ b/res/layout/message_audio_attachment.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<com.android.messaging.ui.AudioAttachmentView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="70dp"
+ android:importantForAccessibility="no"
+ style="@style/AudioAttachmentViewStyle" />
diff --git a/res/layout/message_vcard_attachment.xml b/res/layout/message_vcard_attachment.xml
new file mode 100644
index 0000000..0da6e2d
--- /dev/null
+++ b/res/layout/message_vcard_attachment.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<com.android.messaging.ui.PersonItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/message_text_top_padding"
+ android:paddingBottom="@dimen/message_text_bottom_padding"
+ style="@style/MessageVcardAttachmentStyle" />
diff --git a/res/layout/message_video_attachment.xml b/res/layout/message_video_attachment.xml
new file mode 100644
index 0000000..8aa2061
--- /dev/null
+++ b/res/layout/message_video_attachment.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<com.android.messaging.ui.VideoThumbnailView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:minWidth="@dimen/video_message_min_size"
+ android:minHeight="@dimen/video_message_min_size"
+ app:mode="imageThumbnail" />
diff --git a/res/layout/mms_config_debug_fragment.xml b/res/layout/mms_config_debug_fragment.xml
new file mode 100644
index 0000000..27528f9
--- /dev/null
+++ b/res/layout/mms_config_debug_fragment.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_margin="16dp">
+ <TextView
+ android:id="@+id/sim_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/debug_sub_id_spinner_text" />
+ <Spinner
+ android:id="@+id/sim_selector"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#555555" />
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:divider="@null"
+ android:dividerHeight="0px" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/open_source_licenses.xml b/res/layout/open_source_licenses.xml
new file mode 100644
index 0000000..89995eb
--- /dev/null
+++ b/res/layout/open_source_licenses.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<WebView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/osl_web_view"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ />
diff --git a/res/layout/people_and_options_activity.xml b/res/layout/people_and_options_activity.xml
new file mode 100644
index 0000000..af879a0
--- /dev/null
+++ b/res/layout/people_and_options_activity.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.conversationsettings.PeopleAndOptionsFragment"
+ android:id="@+id/people_and_options_fragment"
+ android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/people_and_options_fragment.xml b/res/layout/people_and_options_fragment.xml
new file mode 100644
index 0000000..be61710
--- /dev/null
+++ b/res/layout/people_and_options_fragment.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:divider="@null"
+ android:dividerHeight="0px" /> \ No newline at end of file
diff --git a/res/layout/people_and_options_section_header.xml b/res/layout/people_and_options_section_header.xml
new file mode 100644
index 0000000..44b10c1
--- /dev/null
+++ b/res/layout/people_and_options_section_header.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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/people_and_options_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@color/people_and_options_list_divider"/>
+
+ <TextView
+ android:id="@+id/header_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:text="@string/participant_list_title"
+ style="@style/PeopleAndOptionsSectionHeader" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/people_list_item_view.xml b/res/layout/people_list_item_view.xml
new file mode 100644
index 0000000..e4c07e4
--- /dev/null
+++ b/res/layout/people_list_item_view.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<com.android.messaging.ui.PersonItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:background="?android:attr/selectableItemBackground"
+ style="@style/PeopleListItemViewStyle" />
diff --git a/res/layout/people_options_item_view.xml b/res/layout/people_options_item_view.xml
new file mode 100644
index 0000000..fbb9592
--- /dev/null
+++ b/res/layout/people_options_item_view.xml
@@ -0,0 +1,64 @@
+<?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.
+-->
+<com.android.messaging.ui.conversationsettings.PeopleOptionsItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ style="@style/PeopleAndOptionsItemStyle">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ParticipantListItem"
+ android:layout_gravity="center_vertical" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ParticipantListItemDetail"
+ android:layout_gravity="center_vertical" />
+
+ </LinearLayout>
+
+ <android.support.v7.widget.SwitchCompat
+ android:id="@+id/switch_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:clickable="false"
+ android:visibility="gone" />
+
+</com.android.messaging.ui.conversationsettings.PeopleOptionsItemView>
diff --git a/res/layout/permission_check_activity.xml b/res/layout/permission_check_activity.xml
new file mode 100644
index 0000000..57f665c
--- /dev/null
+++ b/res/layout/permission_check_activity.xml
@@ -0,0 +1,80 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/permission_check_activity_background"
+ android:orientation="vertical" >
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/required_permissions_promo"
+ android:paddingTop="96dp"
+ style="@style/PromoScreenTextStyle.CenterAligned" />
+ <TextView
+ android:id="@+id/enable_permission_procedure"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:text="@string/enable_permission_procedure"
+ android:contentDescription="@string/enable_permission_procedure_description"
+ style="@style/PromoScreenTextStyle.CenterAligned"
+ android:visibility="invisible"/>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:paddingTop="40dp"
+ android:src="@drawable/permissions"
+ android:scaleType="centerInside"
+ android:importantForAccessibility="no" />
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/white" />
+ <!-- Buttons -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="horizontal" >
+ <TextView
+ android:id="@+id/exit"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/exit"
+ style="@style/PromoScreenButtonStyle" />
+ <TextView
+ android:id="@+id/next"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/next_with_arrow"
+ android:contentDescription="@string/next"
+ style="@style/PromoScreenButtonStyle" />
+ <TextView
+ android:id="@+id/settings"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/settings_with_arrow"
+ android:contentDescription="@string/settings"
+ style="@style/PromoScreenButtonStyle"
+ android:visibility="gone"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/person_item_view.xml b/res/layout/person_item_view.xml
new file mode 100644
index 0000000..9645024
--- /dev/null
+++ b/res/layout/person_item_view.xml
@@ -0,0 +1,60 @@
+<?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.
+-->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.android.messaging.ui.ContactIconView
+ android:id="@+id/contact_icon"
+ android:layout_width="@dimen/contact_list_icon_size"
+ android:layout_height="@dimen/contact_list_icon_size"
+ android:importantForAccessibility="no"
+ app:iconSize="normal"
+ android:layout_gravity="center_vertical" />
+
+ <LinearLayout
+ android:id="@+id/details_container"
+ android:orientation="vertical"
+ style="@style/ContactListItemLinearLayoutStyle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical"
+ android:background="@android:color/transparent">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/ParticipantListItem" />
+
+ <TextView
+ android:id="@+id/details"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:ellipsize="end"
+ style="@style/ContactListItemDetail" />
+
+ </LinearLayout>
+</merge>
diff --git a/res/layout/place_card.xml b/res/layout/place_card.xml
new file mode 100644
index 0000000..93b9e8c
--- /dev/null
+++ b/res/layout/place_card.xml
@@ -0,0 +1,54 @@
+<?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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <com.android.messaging.ui.AsyncImageView
+ android:id="@+id/map_image"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true" />
+
+ <TextView
+ android:id="@+id/place_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textSize="16sp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/place_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:textSize="14sp" />
+
+ <TextView
+ android:id="@+id/place_notes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14sp" />
+
+</merge> \ No newline at end of file
diff --git a/res/layout/settings_fragment.xml b/res/layout/settings_fragment.xml
new file mode 100644
index 0000000..b62b245
--- /dev/null
+++ b/res/layout/settings_fragment.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/settings_item_view.xml b/res/layout/settings_item_view.xml
new file mode 100644
index 0000000..a434c11
--- /dev/null
+++ b/res/layout/settings_item_view.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/settings_list_item_height"
+ android:gravity="center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:background="?android:attr/selectableItemBackground" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/SettingsListItem" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="2"
+ android:ellipsize="end"
+ style="@style/SettingsListItemDetail" />
+
+</LinearLayout>
diff --git a/res/layout/share_intent_activity.xml b/res/layout/share_intent_activity.xml
new file mode 100644
index 0000000..d1e59ff
--- /dev/null
+++ b/res/layout/share_intent_activity.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.conversationlist.ShareIntentFragment"
+ android:id="@+id/share_intent_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/res/layout/share_intent_conversation_list_view.xml b/res/layout/share_intent_conversation_list_view.xml
new file mode 100644
index 0000000..3da3c6b
--- /dev/null
+++ b/res/layout/share_intent_conversation_list_view.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:orientation="vertical">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+ <android.support.v7.widget.RecyclerView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/conversation_list_first_item_extra_padding"
+ android:clipToPadding="false"
+ android:listSelector="?android:attr/listSelector"
+ android:dividerHeight="0px" />
+ <include
+ layout="@layout/list_empty_view"
+ android:id="@+id/no_conversations_view"
+ android:visibility="gone"/>
+ </FrameLayout>
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#d2d2d2"/>
+</LinearLayout>
diff --git a/res/layout/sim_selector_item_view.xml b/res/layout/sim_selector_item_view.xml
new file mode 100644
index 0000000..a20c4a9
--- /dev/null
+++ b/res/layout/sim_selector_item_view.xml
@@ -0,0 +1,65 @@
+<?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.
+-->
+<com.android.messaging.ui.conversation.SimSelectorItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="?android:attr/selectableItemBackground"
+ android:padding="12dp"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/details_container"
+ android:orientation="vertical"
+ style="@style/SimSelectorItemLinearLayoutStyle"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" >
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/SimSelectorItem" />
+
+ <TextView
+ android:id="@+id/details"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="end"
+ style="@style/SimSelectorItemDetail" />
+
+ </LinearLayout>
+
+ <com.android.messaging.ui.conversation.SimIconView
+ android:id="@+id/sim_icon"
+ android:layout_width="@dimen/sim_selector_icon_size"
+ android:layout_height="@dimen/sim_selector_icon_size"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center_vertical"
+ android:elevation="3dp"
+ app:iconSize="normal" />
+
+</com.android.messaging.ui.conversation.SimSelectorItemView>
diff --git a/res/layout/sim_selector_view.xml b/res/layout/sim_selector_view.xml
new file mode 100644
index 0000000..816a2cc
--- /dev/null
+++ b/res/layout/sim_selector_view.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<com.android.messaging.ui.conversation.SimSelectorView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/sim_selector_background_gradient">
+
+ <ListView
+ android:id="@+id/sim_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:divider="@null"
+ android:dividerHeight="0px"
+ android:visibility="gone" />
+
+</com.android.messaging.ui.conversation.SimSelectorView>
diff --git a/res/layout/sms_free_storage_action_item_view.xml b/res/layout/sms_free_storage_action_item_view.xml
new file mode 100644
index 0000000..fd71904
--- /dev/null
+++ b/res/layout/sms_free_storage_action_item_view.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/action"
+ android:padding="8dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/LowStorageActionItemStyle" />
diff --git a/res/layout/sms_mms_dump_file_list_item.xml b/res/layout/sms_mms_dump_file_list_item.xml
new file mode 100644
index 0000000..a939db2
--- /dev/null
+++ b/res/layout/sms_mms_dump_file_list_item.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/file"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip"
+ android:paddingTop="10dip"
+ android:paddingBottom="10dip"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:textSize="16sp"
+ android:textColor="@color/compose_send_text_color"
+ android:singleLine="false"
+ style="@style/ConversationComposeSendText" />
diff --git a/res/layout/sms_storage_low_warning_dialog.xml b/res/layout/sms_storage_low_warning_dialog.xml
new file mode 100644
index 0000000..965cab6
--- /dev/null
+++ b/res/layout/sms_storage_low_warning_dialog.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="6dp"
+ android:background="@android:color/background_light">
+
+ <ListView android:id="@+id/free_storage_action_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"
+ android:listSelector="?android:attr/selectableItemBackground"
+ android:longClickable="false"/>
+
+</FrameLayout>
diff --git a/res/layout/snack_bar.xml b/res/layout/snack_bar.xml
new file mode 100644
index 0000000..1cdabc3
--- /dev/null
+++ b/res/layout/snack_bar.xml
@@ -0,0 +1,64 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/snackbarContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <!--
+ First child is also a LinearLayout to preserve it's styles from getting
+ overridden by the LayoutParams set by the WindowManager.
+ Also, it is the view being animated within the root view.
+ -->
+
+ <LinearLayout
+ android:id="@+id/snack_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="#323232" >
+
+ <FrameLayout
+ android:id="@+id/snack_bar_message_wrapper"
+ style="@style/SnackBarMessageWrapper"
+ android:layout_marginTop="@dimen/snack_bar_top_bottom_margin"
+ android:layout_marginBottom="@dimen/snack_bar_top_bottom_margin"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+ <TextView
+ android:id="@+id/snack_bar_message"
+ style="@style/SnackBarText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/snack_bar_action"
+ style="@style/SnackBarText"
+ android:paddingLeft="@dimen/snack_bar_left_right_margin"
+ android:paddingRight="@dimen/snack_bar_left_right_margin"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textColor="#EEFF41"
+ android:textAllCaps="true" />
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/test_activity.xml b/res/layout/test_activity.xml
new file mode 100644
index 0000000..a34b17a
--- /dev/null
+++ b/res/layout/test_activity.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/test_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+ <FrameLayout
+ android:id="@+id/test_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</FrameLayout>
diff --git a/res/layout/vcard_detail_activity.xml b/res/layout/vcard_detail_activity.xml
new file mode 100644
index 0000000..aa4c3b9
--- /dev/null
+++ b/res/layout/vcard_detail_activity.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<fragment
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.VCardDetailFragment"
+ android:id="@+id/vcard_detail_fragment"
+ android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/res/layout/vcard_detail_fragment.xml b/res/layout/vcard_detail_fragment.xml
new file mode 100644
index 0000000..8689df0
--- /dev/null
+++ b/res/layout/vcard_detail_fragment.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_layout_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ExpandableListView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:drawSelectorOnTop="false" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/video_thumbnail_view.xml b/res/layout/video_thumbnail_view.xml
new file mode 100644
index 0000000..7c009cb
--- /dev/null
+++ b/res/layout/video_thumbnail_view.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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.messaging.ui.AsyncImageView
+ android:id="@+id/video_thumbnail_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:cornerRadius="@dimen/attachment_rounded_corner_radius" />
+
+ <ImageButton
+ android:id="@+id/video_thumbnail_play_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/transparent_button_background"
+ android:src="@drawable/ic_video_play_light"
+ android:contentDescription="@string/video_thumbnail_view_play_button_content_description" />
+
+</merge> \ No newline at end of file
diff --git a/res/layout/viewgroup_vertical_explode_animation_popup.xml b/res/layout/viewgroup_vertical_explode_animation_popup.xml
new file mode 100644
index 0000000..8a8aa60
--- /dev/null
+++ b/res/layout/viewgroup_vertical_explode_animation_popup.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<view
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ class="com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation$VerticalExplodePopupRootView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#55000000">
+
+ <ImageView
+ android:id="@+id/explode_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no" />
+
+</view>
diff --git a/res/layout/widget_conversation.xml b/res/layout/widget_conversation.xml
new file mode 100644
index 0000000..a9cda98
--- /dev/null
+++ b/res/layout/widget_conversation.xml
@@ -0,0 +1,110 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/widget_margin"
+ android:background="@android:color/transparent" >
+ <LinearLayout
+ android:id="@+id/widget_header"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="horizontal"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
+ android:layout_alignParentTop="true">
+ <TextView
+ android:id="@+id/widget_label"
+ style="@style/WidgetConversationTitle"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight = "1"
+ android:layout_gravity="center_vertical"
+ android:freezesText="true" />
+ <ImageView
+ android:id="@+id/launcher_icon"
+ android:src="@drawable/ic_launcher"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight = "1"
+ android:scaleType="fitStart"
+ android:paddingTop="12dip"
+ android:paddingBottom="12dip"
+ android:visibility="gone"
+ android:importantForAccessibility="no" />
+ <ImageButton
+ android:id="@+id/widget_goto_conversation_list"
+ style="@style/WidgetHeaderImage"
+ android:layout_width="@dimen/widget_header_new_conv_button_width"
+ android:layout_height="match_parent"
+ android:src="@drawable/ic_widget_list"
+ android:background="?android:attr/selectableItemBackground"
+ android:contentDescription="@string/widget_conversation_list_content_description"
+ android:layout_gravity="center_vertical" />
+ </LinearLayout>
+ <ListView
+ android:id="@+id/message_list"
+ android:layout_below="@+id/top_line"
+ android:layout_above="@+id/bottom_line"
+ android:layout_marginTop="-2dp"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="true"
+ android:transcriptMode="alwaysScroll"
+ android:stackFromBottom="true"
+ android:overScrollMode="never"
+ android:divider="@null" />
+ <TextView
+ android:id="@+id/widget_configuration"
+ style="@style/WidgetConversationTitle"
+ android:layout_below="@+id/top_line"
+ android:layout_above="@+id/bottom_line"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:visibility="gone"
+ android:singleLine="false"
+ android:paddingRight="20dp"
+ android:paddingLeft="20dp"
+ android:paddingBottom="20dp"
+ android:text="@string/tap_to_configure" />
+ <ImageView
+ android:id="@id/top_line"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/widget_hr"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
+ android:layout_marginTop="-2dp"
+ android:layout_below="@id/widget_header"
+ />
+ <ImageView
+ android:id="@id/bottom_line"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/widget_hr"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
+ android:layout_marginTop="-24dp"
+ android:layout_alignParentBottom="true"
+ />
+</RelativeLayout>
diff --git a/res/layout/widget_conversation_list.xml b/res/layout/widget_conversation_list.xml
new file mode 100644
index 0000000..822fe55
--- /dev/null
+++ b/res/layout/widget_conversation_list.xml
@@ -0,0 +1,65 @@
+<?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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/widget_margin"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/widget_header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/widget_header_height"
+ android:orientation="horizontal"
+ android:paddingLeft="8dip"
+ android:background="@drawable/widget_top_background"
+ android:gravity="center_vertical">
+ <TextView
+ android:id="@+id/widget_label"
+ style="@style/WidgetTitle"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight = "1"
+ android:contentDescription="@string/widget_title_content_description"
+ android:freezesText="true" />
+ <ImageButton
+ android:id="@+id/widget_compose"
+ style="@style/WidgetHeaderImage"
+ android:layout_width="@dimen/widget_header_new_conv_button_width"
+ android:layout_height="match_parent"
+ android:src="@drawable/ic_add_white"
+ android:background="?android:attr/selectableItemBackground"
+ android:contentDescription="@string/widget_new_conversation_content_description"
+ android:paddingRight="8dip"
+ android:paddingBottom="4dip" />
+ </LinearLayout>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#aaaaaa"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ <ListView
+ android:id="@+id/conversation_list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:cacheColorHint="#00000000"
+ android:divider="@android:drawable/divider_horizontal_bright"
+ android:background="@drawable/widget_bottom_background" />
+</LinearLayout>
diff --git a/res/layout/widget_conversation_list_item.xml b/res/layout/widget_conversation_list_item.xml
new file mode 100644
index 0000000..e0115a2
--- /dev/null
+++ b/res/layout/widget_conversation_list_item.xml
@@ -0,0 +1,144 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_conversation_list_item"
+ android:layout_width="match_parent"
+ style="@style/WidgetConversationListItemStyle"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip" >
+
+ <FrameLayout
+ android:id="@+id/avatarFrame"
+ android:layout_centerVertical="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/avatarView"
+ style="@style/WidgetConversationListItemAvatar"
+ android:layout_width="@dimen/contact_icon_view_normal_size"
+ android:layout_height="@dimen/contact_icon_view_normal_size"
+ android:layout_centerVertical="true"
+ android:scaleType="fitXY"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/conversation_failed_status_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/WidgetConversationItemFailed"
+ android:src="@drawable/ic_failed_status_red"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_centerVertical="true"
+ android:orientation="vertical"
+ style="@style/WidgetConversationListItemBody"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dip"
+ android:layout_alignParentTop="true"
+ android:gravity="center_vertical"
+ android:background="@android:color/transparent">
+ <ImageView
+ android:id="@+id/conversation_notification_bell"
+ style="@style/ConversationListNotificationBellPaddingStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_notifications_off_small_light"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
+ android:layout_gravity="center_vertical"
+ android:background="@android:color/transparent" />
+
+ <TextView android:id="@+id/from"
+ style="@style/WidgetConversationListItemFrom"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMediumInverse"
+ android:singleLine="true"
+ android:textSize="18sp"
+ android:textColor="@color/widget_text_color"
+ android:ellipsize="marquee" />
+
+ <TextView android:id="@+id/date"
+ style="@style/WidgetConversationListItemDate"
+ android:paddingTop="4dp"
+ android:textSize="12sp"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:singleLine="true"
+ android:textColor="@color/widget_text_color" />
+ </LinearLayout>
+ </RelativeLayout>
+
+ <TextView android:id="@+id/snippet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:maxLines="2"
+ android:textSize="13sp"
+ android:layout_marginBottom="5dip"
+ android:layout_alignParentBottom="true"
+ android:textColor="@color/widget_text_color"
+ android:ellipsize="end" />
+
+ <LinearLayout android:id="@+id/errorBlock"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <TextView android:id="@+id/errorSnippet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:maxLines="1"
+ android:textSize="13sp"
+ android:textColor="@color/widget_text_color"
+ android:ellipsize="end" />
+
+ <TextView android:id="@+id/errorText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:maxLines="2"
+ android:textSize="13sp"
+ android:layout_marginBottom="5dip"
+ android:textColor="@color/conversation_list_error"
+ android:ellipsize="end" />
+
+ </LinearLayout>
+ </LinearLayout>
+</RelativeLayout>
diff --git a/res/layout/widget_loading.xml b/res/layout/widget_loading.xml
new file mode 100644
index 0000000..8282b5d
--- /dev/null
+++ b/res/layout/widget_loading.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_loading"
+ android:layout_width="match_parent"
+ android:layout_height="64sp" >
+ <TextView
+ android:id="@+id/loading_text"
+ style="@style/WidgetLoading"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:gravity="center"
+ android:textColor="@android:color/black"
+ android:textSize="12sp" />
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/widget_message_item_incoming.xml b/res/layout/widget_message_item_incoming.xml
new file mode 100644
index 0000000..3a1bc00
--- /dev/null
+++ b/res/layout/widget_message_item_incoming.xml
@@ -0,0 +1,119 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_message_item_incoming"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip" >
+
+ <FrameLayout
+ android:id="@+id/avatarFrame"
+ android:layout_gravity="top"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/avatarShadow"
+ style="@style/WidgetConversationItemIncomingAvatarShadow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:src="@drawable/ic_widget_avatar_shadow"
+ android:layout_marginTop="-4dp"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/avatarView"
+ style="@style/WidgetConversationItemAvatarIncoming"
+ android:layout_width="@dimen/contact_icon_view_normal_size"
+ android:layout_height="@dimen/contact_icon_view_normal_size"
+ android:layout_centerVertical="true"
+ android:scaleType="fitXY"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/conversation_failed_status_icon"
+ style="@style/WidgetConversationItemFailed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_failed_status_red"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_gravity="top"
+ android:orientation="vertical"
+ style="@style/WidgetConversationItemBodyIncoming"
+ android:background="@drawable/widget_msg_bubble_incoming"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <TextView android:id="@+id/message"
+ style="@style/WidgetConversationItemWidget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:textSize="16sp"
+ android:paddingTop="6dp"
+ android:paddingLeft="6dp"
+ android:paddingRight="6dp"
+ android:layout_marginBottom="3dip"
+ android:layout_alignParentBottom="true"
+ android:textColor="@color/widget_incoming_text_color"
+ android:ellipsize="end" />
+
+ <TextView android:id="@+id/date"
+ style="@style/WidgetConversationItemDate"
+ android:paddingLeft="6dp"
+ android:paddingRight="6dp"
+ android:layout_marginBottom="9dip"
+ android:textSize="12sp"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:singleLine="true"
+ android:textColor="@color/timestamp_text_incoming" />
+
+ <FrameLayout
+ android:id="@+id/attachmentFrame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/attachment"
+ style="@style/WidgetConversationItemAttachment"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no"
+ android:adjustViewBounds="true"
+ android:contentDescription="@null"/>
+ <ImageView
+ android:id="@+id/playButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ android:src="@drawable/ic_preview_play"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+ </FrameLayout>
+ </LinearLayout>
+</RelativeLayout>
diff --git a/res/layout/widget_message_item_outgoing.xml b/res/layout/widget_message_item_outgoing.xml
new file mode 100644
index 0000000..472b5a2
--- /dev/null
+++ b/res/layout/widget_message_item_outgoing.xml
@@ -0,0 +1,120 @@
+<?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.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_message_item_outgoing"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
+ android:paddingLeft="8dip"
+ android:paddingRight="8dip" >
+
+ <FrameLayout
+ android:id="@+id/avatarFrame"
+ android:layout_gravity="top"
+ style="@style/WidgetConversationItemAvatarOutgoing"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/avatarShadow"
+ style="@style/WidgetConversationItemOutgoingAvatarShadow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:src="@drawable/ic_widget_avatar_shadow"
+ android:layout_marginTop="-4dp"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/avatarView"
+ android:layout_width="@dimen/contact_icon_view_normal_size"
+ android:layout_height="@dimen/contact_icon_view_normal_size"
+ android:layout_centerVertical="true"
+ android:scaleType="fitXY"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null" />
+ <ImageView
+ android:id="@+id/conversation_failed_status_icon"
+ style="@style/WidgetConversationItemFailed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_failed_status_red"
+ android:visibility="gone"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_gravity="top"
+ android:orientation="vertical"
+ style="@style/WidgetConversationItemBodyOutgoing"
+ android:background="@drawable/widget_msg_bubble_outgoing"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <TextView android:id="@+id/message"
+ style="@style/WidgetConversationItemWidget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:textSize="16sp"
+ android:paddingTop="6dp"
+ android:paddingLeft="6dp"
+ android:paddingRight="6dp"
+ android:layout_marginBottom="3dip"
+ android:layout_alignParentBottom="true"
+ android:textColor="@color/widget_outgoing_text_color"
+ android:ellipsize="end" />
+
+ <TextView android:id="@+id/date"
+ style="@style/WidgetConversationItemDate"
+ android:paddingLeft="6dp"
+ android:paddingRight="6dp"
+ android:layout_marginBottom="9dip"
+ android:textSize="12sp"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmallInverse"
+ android:singleLine="true"
+ android:textColor="@color/timestamp_text_outgoing" />
+
+ <FrameLayout
+ android:id="@+id/attachmentFrame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/attachment"
+ style="@style/WidgetConversationItemAttachment"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no"
+ android:adjustViewBounds="true"
+ android:contentDescription="@null"/>
+ <ImageView
+ android:id="@+id/playButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ android:src="@drawable/ic_preview_play"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"/>
+ </FrameLayout>
+
+ </LinearLayout>
+</RelativeLayout>
diff --git a/res/layout/widget_missing_permission.xml b/res/layout/widget_missing_permission.xml
new file mode 100644
index 0000000..712ad02
--- /dev/null
+++ b/res/layout/widget_missing_permission.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:text="@string/required_permissions_promo"
+ android:gravity="center"
+ style="@android:style/TextAppearance.Medium"
+ android:background="@color/action_bar_background_color"
+ android:freezesText="true" />
diff --git a/res/menu/archived_conversation_list_menu.xml b/res/menu/archived_conversation_list_menu.xml
new file mode 100644
index 0000000..a3a2a19
--- /dev/null
+++ b/res/menu/archived_conversation_list_menu.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" >
+ <item
+ android:id="@+id/action_debug_options"
+ android:orderInCategory="9900"
+ android:title="@string/action_debug_options"
+ android:showAsAction="never"/>
+
+</menu>
diff --git a/res/menu/attachment_chooser_menu.xml b/res/menu/attachment_chooser_menu.xml
new file mode 100644
index 0000000..99345c9
--- /dev/null
+++ b/res/menu/attachment_chooser_menu.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<menu
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" >
+ <item
+ android:id="@+id/action_confirm_selection"
+ android:icon="@drawable/ic_checkmark_small_light"
+ android:orderInCategory="100"
+ android:title="@string/action_confirm_selection"
+ appcompat:showAsAction="always"/>
+</menu>
diff --git a/res/menu/compose_menu.xml b/res/menu/compose_menu.xml
new file mode 100644
index 0000000..a384f57
--- /dev/null
+++ b/res/menu/compose_menu.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ tools:context="com.android.messaging.ConversationActivity" >
+
+ <!-- This must always be the first item so it appears right next to the chip view -->
+ <item
+ android:id="@+id/action_delete_text"
+ android:icon="@drawable/ic_cancel_small_light"
+ android:orderInCategory="0"
+ android:title="@string/chips_text_delete_button_content_description"
+ appcompat:showAsAction="always"/>
+
+ <item
+ android:id="@+id/action_ime_dialpad_toggle"
+ android:icon="@drawable/ic_numeric_dialpad"
+ android:orderInCategory="100"
+ android:title="@string/numeric_text_keyboard_toggle_button_content_description"
+ appcompat:showAsAction="always"/>
+
+ <item
+ android:id="@+id/action_add_more_participants"
+ android:icon="@drawable/ic_people_add_light"
+ android:orderInCategory="110"
+ android:title="@string/add_more_participants_button_content_description"
+ appcompat:showAsAction="always"/>
+
+ <item
+ android:id="@+id/action_confirm_participants"
+ android:icon="@drawable/ic_checkmark_small_light"
+ android:orderInCategory="120"
+ android:title="@string/confrim_participants_button_content_description"
+ appcompat:showAsAction="always"/>
+
+</menu>
diff --git a/res/menu/conversation_fragment_select_menu.xml b/res/menu/conversation_fragment_select_menu.xml
new file mode 100644
index 0000000..7ce0829
--- /dev/null
+++ b/res/menu/conversation_fragment_select_menu.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <item
+ android:id="@+id/share_message_menu"
+ android:title="@string/action_share"
+ android:icon="@drawable/ic_share_dark"
+ appcompat:showAsAction="collapseActionView|always" />
+ <item
+ android:id="@+id/forward_message_menu"
+ android:title="@string/message_context_menu_forward_message"
+ android:icon="@drawable/ic_forward_dark"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/save_attachment"
+ android:title="@string/save"
+ android:icon="@drawable/ic_save_dark"
+ appcompat:showAsAction="collapseActionView|always" />
+ <item
+ android:id="@+id/copy_text"
+ android:title="@string/message_context_menu_copy_text"
+ android:icon="@drawable/ic_content_copy_dark"
+ appcompat:showAsAction="collapseActionView|always"/>
+
+ <item
+ android:id="@+id/details_menu"
+ android:title="@string/message_context_menu_view_details"
+ android:icon="@drawable/ic_info_dark"
+ appcompat:showAsAction="collapseActionView|always"/>
+
+ <item
+ android:id="@+id/action_send"
+ android:icon="@drawable/ic_send_dark"
+ android:title="@string/action_send"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_download"
+ android:icon="@drawable/ic_file_download_dark"
+ android:title="@string/action_download"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_delete_message"
+ android:icon="@drawable/ic_delete_small_dark"
+ android:title="@string/action_delete_message"
+ appcompat:showAsAction="collapseActionView|always"/>
+</menu>
diff --git a/res/menu/conversation_list_fragment_menu.xml b/res/menu/conversation_list_fragment_menu.xml
new file mode 100644
index 0000000..a83a2a5
--- /dev/null
+++ b/res/menu/conversation_list_fragment_menu.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <item android:id="@+id/action_start_new_conversation"
+ android:title="@string/start_new_conversation"
+ android:icon="@drawable/ic_add_white"
+ android:visible="false"
+ appcompat:showAsAction="ifRoom" />
+
+ <item
+ android:id="@+id/action_show_archived"
+ android:orderInCategory="200"
+ android:title="@string/action_menu_show_archived"
+ android:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_show_blocked_contacts"
+ android:orderInCategory="300"
+ android:title="@string/blocked_contacts_title"
+ appcompat:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_settings"
+ android:orderInCategory="400"
+ android:title="@string/action_settings"
+ appcompat:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_debug_options"
+ android:orderInCategory="9900"
+ android:title="@string/action_debug_options"
+ appcompat:showAsAction="never"/>
+
+</menu>
diff --git a/res/menu/conversation_list_fragment_select_menu.xml b/res/menu/conversation_list_fragment_select_menu.xml
new file mode 100644
index 0000000..8704691
--- /dev/null
+++ b/res/menu/conversation_list_fragment_select_menu.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <item
+ android:id="@+id/action_archive"
+ android:icon="@drawable/ic_archive_small_dark"
+ android:orderInCategory="50"
+ android:title="@string/action_archive"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_unarchive"
+ android:icon="@drawable/ic_archive_undo_small_dark"
+ android:orderInCategory="50"
+ android:title="@string/action_unarchive"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_delete"
+ android:icon="@drawable/ic_delete_small_dark"
+ android:orderInCategory="60"
+ android:title="@string/action_delete"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_notification_off"
+ android:icon="@drawable/ic_notifications_off_dark"
+ android:orderInCategory="70"
+ android:title="@string/action_notification_off"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_notification_on"
+ android:icon="@drawable/ic_notifications_on_dark"
+ android:orderInCategory="80"
+ android:title="@string/action_notification_on"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_add_contact"
+ android:icon="@drawable/ic_person_add_dark"
+ android:orderInCategory="90"
+ android:title="@string/action_add_contact"
+ appcompat:showAsAction="collapseActionView|always"/>
+ <item
+ android:id="@+id/action_block"
+ android:icon="@drawable/ic_dnd_on_dark"
+ android:orderInCategory="100"
+ android:title="@string/action_block"
+ appcompat:showAsAction="collapseActionView|always"/>
+</menu>
diff --git a/res/menu/conversation_menu.xml b/res/menu/conversation_menu.xml
new file mode 100644
index 0000000..57e561b
--- /dev/null
+++ b/res/menu/conversation_menu.xml
@@ -0,0 +1,60 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ tools:context="com.android.messaging.ConversationActivity" >
+
+ <item
+ android:id="@+id/action_call"
+ android:icon="@drawable/ic_phone_small_light"
+ android:orderInCategory="80"
+ android:title="@string/action_call"
+ appcompat:showAsAction="ifRoom"/>
+
+ <item
+ android:id="@+id/action_add_contact"
+ android:icon="@drawable/ic_person_add_light"
+ android:orderInCategory="90"
+ android:title="@string/action_add_contact"
+ android:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_people_and_options"
+ android:orderInCategory="100"
+ android:title="@string/action_people_and_options"
+ appcompat:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_archive"
+ android:orderInCategory="120"
+ android:title="@string/action_archive"
+ android:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_unarchive"
+ android:orderInCategory="130"
+ android:title="@string/action_unarchive"
+ android:showAsAction="never"/>
+
+ <item
+ android:id="@+id/action_delete"
+ android:orderInCategory="140"
+ android:title="@string/action_delete"
+ android:showAsAction="never"/>
+
+</menu>
diff --git a/res/menu/gallery_picker_menu.xml b/res/menu/gallery_picker_menu.xml
new file mode 100644
index 0000000..428f1e7
--- /dev/null
+++ b/res/menu/gallery_picker_menu.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <item
+ android:id="@+id/action_multiselect"
+ android:icon="@drawable/ic_checkbox_outline_light"
+ android:orderInCategory="100"
+ android:title="@string/action_multiselect"
+ appcompat:showAsAction="always"/>
+
+ <item
+ android:id="@+id/action_confirm_multiselect"
+ android:icon="@drawable/ic_checkmark_small_light"
+ android:orderInCategory="101"
+ android:title="@string/action_confirm_multiselect"
+ appcompat:showAsAction="always"/>
+</menu>
diff --git a/res/menu/photo_view_menu.xml b/res/menu/photo_view_menu.xml
new file mode 100644
index 0000000..10a4367
--- /dev/null
+++ b/res/menu/photo_view_menu.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <item
+ android:id="@+id/action_share"
+ android:title="@string/action_share"
+ android:actionProviderClass="android.widget.ShareActionProvider"
+ android:showAsAction="ifRoom"/>
+ <item
+ android:id="@+id/action_save"
+ android:title="@string/save"
+ android:icon="@drawable/ic_save_light"
+ android:showAsAction="ifRoom"/>
+
+</menu>
diff --git a/res/menu/settings_menu.xml b/res/menu/settings_menu.xml
new file mode 100644
index 0000000..3002be8
--- /dev/null
+++ b/res/menu/settings_menu.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" >
+
+ <item
+ android:id="@+id/action_license"
+ android:orderInCategory="100"
+ android:title="@string/menu_license"
+ android:showAsAction="never"/>
+
+</menu>
diff --git a/res/menu/vcard_detail_fragment_menu.xml b/res/menu/vcard_detail_fragment_menu.xml
new file mode 100644
index 0000000..ea923cb
--- /dev/null
+++ b/res/menu/vcard_detail_fragment_menu.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:appcompat="http://schemas.android.com/apk/res-auto" >
+
+ <item
+ android:id="@+id/action_add_contact"
+ android:icon="@drawable/ic_person_add_light"
+ android:orderInCategory="100"
+ android:title="@string/action_add_contact"
+ appcompat:showAsAction="always"/>
+</menu>
diff --git a/res/raw/audio_end.wav b/res/raw/audio_end.wav
new file mode 100644
index 0000000..01d08f5
--- /dev/null
+++ b/res/raw/audio_end.wav
Binary files differ
diff --git a/res/raw/audio_initiate.wav b/res/raw/audio_initiate.wav
new file mode 100644
index 0000000..e335d0d
--- /dev/null
+++ b/res/raw/audio_initiate.wav
Binary files differ
diff --git a/res/raw/db_op_debug.wav b/res/raw/db_op_debug.wav
new file mode 100644
index 0000000..b807636
--- /dev/null
+++ b/res/raw/db_op_debug.wav
Binary files differ
diff --git a/res/raw/message_failure.wav b/res/raw/message_failure.wav
new file mode 100644
index 0000000..270a0b7
--- /dev/null
+++ b/res/raw/message_failure.wav
Binary files differ
diff --git a/res/raw/message_inc_thread.wav b/res/raw/message_inc_thread.wav
new file mode 100644
index 0000000..7dce364
--- /dev/null
+++ b/res/raw/message_inc_thread.wav
Binary files differ
diff --git a/res/raw/message_sent.wav b/res/raw/message_sent.wav
new file mode 100644
index 0000000..1d45718
--- /dev/null
+++ b/res/raw/message_sent.wav
Binary files differ
diff --git a/res/raw/server_request_debug.wav b/res/raw/server_request_debug.wav
new file mode 100644
index 0000000..864a7dd
--- /dev/null
+++ b/res/raw/server_request_debug.wav
Binary files differ
diff --git a/res/transition/explode.xml b/res/transition/explode.xml
new file mode 100644
index 0000000..a3a94ba
--- /dev/null
+++ b/res/transition/explode.xml
@@ -0,0 +1,17 @@
+<?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.
+-->
+<explode/> \ No newline at end of file
diff --git a/res/transition/fade.xml b/res/transition/fade.xml
new file mode 100644
index 0000000..e3617dc
--- /dev/null
+++ b/res/transition/fade.xml
@@ -0,0 +1,17 @@
+<?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.
+-->
+<fade/> \ No newline at end of file
diff --git a/res/values-af/arrays.xml b/res/values-af/arrays.xml
new file mode 100644
index 0000000..d780ddc
--- /dev/null
+++ b/res/values-af/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"geen onderwerp nie"</item>
+ <item msgid="272485471009191934">"geen onderwerp nie"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ja"</item>
+ <item msgid="6049132459802288033">"Nee"</item>
+ <item msgid="3084376867445867895">"Goed"</item>
+ <item msgid="3155097332660174689">"Hê-hê"</item>
+ <item msgid="2611328818571146775">"Dankie"</item>
+ <item msgid="4881335087096496747">"Ek stem saam"</item>
+ <item msgid="2422296858597420738">"Lekker"</item>
+ <item msgid="4805581752819452687">"Op pad"</item>
+ <item msgid="4746700499431366214">"OK, laat ek jou later antwoord"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..ebe9d4d
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Boodskappe"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Boodskappe"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Kies gesprek"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Instellings"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Stuur boodskap"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Voeg \'n aanhegsel by"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Hulp"</string>
+ <string name="welcome" msgid="2857560951820802321">"Welkom"</string>
+ <string name="skip" msgid="7238879696319945853">"Slaan oor"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Volgende &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Volgende"</string>
+ <string name="exit" msgid="1905187380359981199">"Gaan uit"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Instellings &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Instellings"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Boodskappe het toestemming nodig vir SMS, foon en kontakte."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Jy kan toestemmings verander in Instellings &gt; Programme &gt; Boodskappe &gt; Toestemmings."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Jy kan toestemmings verander in Instellings, Programme, Boodskappe, Toestemmings."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Gereeldes"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Alle kontakte"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Stuur na <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Neem foto\'s of video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Kies prente van hierdie toestel af"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Neem oudio op"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Kies foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Die media is gekies."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Die media is nie gekies nie."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> gekies"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"prent <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" msgid="8142175029053370662">"prent"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Neem oudio op"</string>
+ <string name="action_share" msgid="2143483844803153871">"Deel"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Nou net"</string>
+ <string name="posted_now" msgid="867560789350406701">"Nou"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> uur</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> uur</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dae</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dag</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> weke</item>
+ <item quantity="one">\'n week</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> maande</item>
+ <item quantity="one">\'n maand</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> jaar</item>
+ <item quantity="one">\'n jaar</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Klas 0-boodskap"</string>
+ <string name="save" msgid="5081141452059463572">"Stoor"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Toestel het min spasie. Boodskappe sal ouer boodskappe outomaties uitvee om spasie beskikbaar te stel."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Bergingspasie word min"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Boodskappe sal dalk nie boodskappe stuur of ontvang voordat meer spasie op jou toestel beskikbaar is nie."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Min SMS-berging. Jy sal dalk boodskappe moet uitvee."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Bevestig jou foonnommer"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Hierdie eenmalige stap sal sorg dat Boodskappe jou groepboodskappe toepaslik aflewer."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Foonnommer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Vee alle boodskappe met media uit"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Vee boodskappe ouer as <xliff:g id="DURATION">%s</xliff:g> uit"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Vee boodskappe ouer as <xliff:g id="DURATION">%s</xliff:g> outomaties uit"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignoreer"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Vee alle boodskappe met media uit?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Vee boodskappe ouer as <xliff:g id="DURATION">%s</xliff:g> uit?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Vee boodskappe ouer as <xliff:g id="DURATION">%s</xliff:g> uit en skakel outomatiese uitvee aan?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> het gesê"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Jy het gesê"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Boodskap van <xliff:g id="SENDER">%s</xliff:g> af"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Jy het \'n boodskap gestuur"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Stuur tans …"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nie gestuur nie. Raak om weer te probeer."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nie gestuur nie. Probeer tans weer …"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Herstuur of vee uit"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Maak asseblief \'n stemoproep na nooddienste. Jou SMS kon nie in hierdie stadium afgelewer word nie."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Het misluk"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nuwe MMS-boodskap om af te laai"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nuwe MMS-boodskap"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Kon nie aflaai nie"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Raak om weer te probeer"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Raak om af te laai"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Laai af of vee uit"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Laai tans af …"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Boodskap het verval of is nie beskikbaar nie"</string>
+ <string name="mms_info" msgid="3402311750134118165">"grootte: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, verval: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Kan nie stuur nie. Ontvanger nie geldig nie."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Diens nie in netwerk geaktiveer nie"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Kon weens netwerkprobleem nie stuur nie"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Boodskap het verval of is nie beskikbaar nie"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Geen onderwerp nie)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Onbekende afsender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Afgelewer"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Kon nie boodskap <xliff:g id="SUBJECT">%1$s</xliff:g> van <xliff:g id="FROM">%2$s</xliff:g> aflaai nie."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Kon nie databasishandeling voltooi nie weens lae geheue"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Boodskap nie gestuur nie"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Sommige boodskappe is nie in Boodskappe gestuur nie"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> boodskappe in <xliff:g id="CONVERSATIONS">%d</xliff:g> gesprekke</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> boodskappe in een gesprek</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Boodskap het nie afgelaai nie"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Sommige boodskappe is nie in Boodskappe afgelaai nie"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> boodskappe in <xliff:g id="CONVERSATIONS">%d</xliff:g> gesprekke</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> boodskappe in een gesprek</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Boodskap aan <xliff:g id="NUMBER">%1$s</xliff:g> nie gestuur nie"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Maak asseblief \'n stemoproep na nooddienste. Jou SMS na <xliff:g id="NUMBER">%1$s</xliff:g> kon nie in hierdie stadium afgelewer word nie."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nuwe boodskappe</item>
+ <item quantity="one">Nuwe boodskap</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Begin"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera nie beskikbaar nie"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera nie beskikbaar nie"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video-opname nie beskikbaar nie"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Kan nie media stoor nie"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Kan nie foto neem nie"</string>
+ <string name="back" msgid="1477626055115561645">"Terug"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Geargiveer"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Vee uit"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Argiveer"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Deargiveer"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Skakel kennisgewings af"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Skakel kennisgewings aan"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Voeg kontak by"</string>
+ <string name="action_download" msgid="7786338136368564146">"Laai af"</string>
+ <string name="action_send" msgid="377635240181672039">"Stuur"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Vee uit"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Vee hierdie boodskap uit?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Hierdie handeling kan nie ontdoen word nie."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Vee uit"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Vee hierdie gesprekke uit?</item>
+ <item quantity="one">Vee hierdie gesprek uit?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Vee uit"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Kanselleer"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Aan"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Kies verskeie prente"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Bevestig keuse"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Kan nie oudio opneem nie. Probeer weer."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Kan nie oudio speel nie. Probeer weer."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Kon nie oudio stoor nie. Probeer weer."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Raak en hou"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Prent"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Oudiosnit"</string>
+ <string name="notification_video" msgid="4331423498662606204">"video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontakkaartjie"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Laai af"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Antwoord via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Antwoord via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Antwoord"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> deelnemers</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> deelnemer</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ek"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontak geblokkeer en geargiveer"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontak gedeblokkeer en gedeargiveer"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> geargiveer"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> gedeargiveer"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Kennisgewings afgeskakel"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Kennisgewings aangeskakel"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Alles is gereed. Raak om weer te stuur."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Boodskappe is suksesvol as die verstek-SMS-program gestel."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Gooi aanhegsels weg</item>
+ <item quantity="one">Gooi aanhegsel weg</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Oudio-aanhegsel"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Speel oudio-aanhegsel"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Wag"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Boodskap vanaf <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mislukte boodskap van <xliff:g id="SENDER">%s</xliff:g> af: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Boodskap van <xliff:g id="SENDER">%s</xliff:g> af: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Ongestuurde boodskap aan <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Stuur tans boodskap aan <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Kon nie boodskap aan <xliff:g id="CONTACT">%s</xliff:g> stuur nie: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Boodskap aan <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mislukte boodskap van <xliff:g id="SENDER">%s</xliff:g> af: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Boodskap van <xliff:g id="SENDER">%s</xliff:g> af: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Ongestuurde boodskap aan <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Stuur tans boodskap aan <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Kon nie boodskap aan <xliff:g id="GROUP">%s</xliff:g> stuur nie: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Boodskap aan <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tyd: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Kon nie boodskap stuur nie. Raak om weer te probeer."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Gesprek met <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Vee onderwerp uit"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Neem video op"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Vang \'n stilprent vas"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Neem foto"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Begin video opneem"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Skakel na volskerm-kamera oor"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Wissel tussen voorste en agterste kamera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Staak opname en heg video aan"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Stop video-opname"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto\'s uit Boodskappe"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foto\'s is na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"-album gestoor</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto is na \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"-album gestoor</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video\'s is na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"-album gestoor</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video is na \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"-album gestoor</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> aanhegsels is na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"-album gestoor</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> aanhegsel is na \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"-album gestoor</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> aanhegsels is na \"Aflaaie\" gestoor</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> aanhegsel is na \"Aflaaie\" gestoor</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> aanhegsels is gestoor</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> aanhegsel is gestoor</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Kon <xliff:g id="QUANTITY_1">%d</xliff:g> aanhegsels nie stoor nie</item>
+ <item quantity="one">Kon <xliff:g id="QUANTITY_0">%d</xliff:g> aanhegsel nie stoor nie</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS-aanhegsel gestoor"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Instellings"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Geargiveer"</string>
+ <string name="action_close" msgid="1840519376200478419">"Maak toe"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Gevorderd"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Ontfout"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Kennisgewings"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Klank"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Stil"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibreer"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Geblokkeer"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-afleweringverslae"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Versoek \'n afleweringsverslag vir elke SMS wat jy stuur"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Haal outomaties op"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Gaan haal MMS-boodskappe outomaties"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Outoherwinning wanneer swerf"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Laai MMS outomaties af terwyl geswerf word"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Groepboodskappe"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Gebruik MMS om \'n enkele boodskap te stuur wanneer daar veelvuldige ontvangers is"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Verstek-SMS-program"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Verstek-SMS-program"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Jou foonnommer"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Onbekend"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Klanke vir uitgaande boodskappe"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Gooi SMS weg"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Stort rou data van ontvangde SMS in eksterne berginglêer"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Gooi MMS weg"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Stort rou data van ontvangde MMS in eksterne berginglêer"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Draadlose waarskuwings"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Boodskapopsies"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopieer teks"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Bekyk besonderhede"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Vee uit"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Stuur aan"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Boodskapbesonderhede"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipe: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Teksboodskap"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimediaboodskap"</string>
+ <string name="from_label" msgid="1947831848146564875">"Van: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Aan: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Gestuur: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Ontvang: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Onderwerp: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Grootte: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioriteit: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Hoog"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normaal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Laag"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Versteekte afsenderadres"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Kan nie boodskap stuur terwyl aanhegsels gelaai word nie."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Kan nie aanhegsel laai nie. Probeer weer."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Netwerk is nie gereed nie. Probeer weer."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Vee teks uit"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Wissel tussen invoer van teks en syfers"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Voeg meer deelnemers by"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Bevestig deelnemers"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Begin nuwe gesprek"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Kies hierdie item"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Speel video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Mense en opsies"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Ontfout"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Mense en opsies"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Algemeen"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Mense in hierdie gesprek"</string>
+ <string name="action_call" msgid="6596167921517350362">"Maak \'n oproep"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Stuur boodskap"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Stuur boodskap&lt;br/&gt;&lt;small&gt;van <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; af"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Stuur foto\'s</item>
+ <item quantity="one">Stuur foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Stuur oudio\'s</item>
+ <item quantity="one">Stuur oudio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Stuur video\'s</item>
+ <item quantity="one">Stuur video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Stuur kontakkaarte</item>
+ <item quantity="one">Stuur kontakkaart</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Stuur aanhegsels</item>
+ <item quantity="one">Stuur aanhegsel</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> aanhegsels is gereed om te stuur</item>
+ <item quantity="one">Een aanhegsel is gereed om te stuur</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Stuur terugvoer"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Bekyk in Google Play Winkel"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Weergawe-inligting"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Weergawe %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Oopbronlisensies"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Kennisgewings"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Aanhegsellimiet is bereik"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Kon nie aanhegsel laai nie."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Voeg by kontakte?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Voeg kontak by"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Onderwerp"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Onderwerp: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Laai tans kontakkaart"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kon nie kontakkaart laai nie"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Bekyk kontakkaart"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakte</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontak</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontakkaarte"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Verjaardag"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notas"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Aanstuurboodskap"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Antwoord"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS gedeaktiveer"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Stel Boodskappe as die verstek-SMS-program om te stuur"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Stel Boodskappe as die verstek-SMS-program"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Verander"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Stel Boodskappe as die verstek-SMS-program om boodskappe te ontvang"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Geen voorkeur-SIM vir die stuur van SMS-boodskappe gekies nie"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Hierdie program word nie deur die toesteleienaar toegelaat nie."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Goed"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Te veel deelnemers aan \'n gesprek"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Ongeldige kontakte</item>
+ <item quantity="one">Ongeldige kontak</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kon nie kameraprent laai nie"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Jy: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Konsep"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Sodra jy \'n nuwe gesprek begin, sal jy dit hier gelys sien"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Geargiveerde gesprekke verskyn hier"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Laai tans gesprekke …"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Prent"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Oudiosnit"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontakkaartjie"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Ontdoen"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Herprobeer"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Voer \'n kontaknaam of foonnommer in om \'n nuwe boodskap te begin"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokkeer"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokkeer <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Deblokkeer <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blokkeer <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Jy sal steeds boodskappe van hierdie nommer af ontvang, maar sal nie meer in kennis gestel word nie. Hierdie gesprek sal geargiveer word."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Geblokkeerde kontakte"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DEBLOKKEER"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Geblokkeerde kontakte"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Kies prent uit dokumentbiblioteek"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Stuur tans boodskap"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Boodskap gestuur"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Sellulêre data is afgeskakel. Gaan jou instellings na."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Kan nie boodskappe in vliegtuigmodus stuur nie"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Kon nie boodskap stuur nie"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Boodskap afgelaai"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Sellulêre data is afgeskakel. Gaan jou instellings na."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Kan nie boodskappe in vliegtuigmodus aflaai nie"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Boodskap kon nie afgelaai word nie"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nul"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Een"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Twee"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Drie"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Vier"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Vyf"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Ses"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sewe"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Agt"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nege"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Kan nie boodskap met <xliff:g id="CARRIERNAME">%1$s</xliff:g> stuur nie, fout <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Kan nie boodskap met onbekende diensverskaffer stuur nie, fout <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Boodskap nie gestuur nie: diens nie in netwerk geaktiveer nie"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Boodskap nie gestuur nie: ongeldige bestemmingsadres"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Boodskap nie gestuur nie: ongeldige boodskap"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Boodskap nie gestuur nie: inhoud word nie gesteun nie"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Boodskap nie gestuur nie: boodskap word nie gesteun nie"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Boodskap nie gestuur nie; te groot"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nuwe boodskap"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Bekyk"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Prent"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Kon nie \'n toepaslike program vind nie"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Verwyder ontvanger"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nuwe boodskap"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Kanselleer"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Wysig toegangspunt"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nie gestel nie"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Naam"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-volmag"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-poort"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-tipe"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Vee APN uit"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nuwe APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Stoor"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Gooi weg"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Die naamveld kan nie leeg wees nie."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Die APN kan nie leeg wees nie."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-veld moet 3 syfers wees."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Die MNC-veld moet uit 2 of 3 syfers bestaan."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Stel tans verstekinstellings vir APN terug."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Stel terug na verstek"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Terugstel van verstek-APN-instellings voltooi."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Titelloos"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"APN\'e"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN\'e"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nuwe APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"APN-instellings is nie vir hierdie gebruiker beskikbaar nie"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopieer na knipbord?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopieer"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"aan <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Algemeen"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Gevorderd"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Algemene instellings"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Gevorderde instellings"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\"-SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Stuur individuele SMS-boodskappe aan alle ontvangers. Net jy sal enige antwoorde kry"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Stuur \'n enkele MMS aan alle ontvangers"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Onbekende nommer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nuwe boodskap"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nuwe boodskap."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-selekteerder"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> gekies, SIM-selekteerder"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Wysig onderwerp"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Kies SIM of wysig onderwerp"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Raak en hou om oudio op te neem"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Begin nuwe gesprek"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Boodskappe"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Boodskappe-lys"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Boodskappe"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nuwe boodskap"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Gespreklys"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Laai tans gesprekke"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Laai tans boodskappe"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Bekyk nog gesprekke"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Bekyk nog boodskappe"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Gesprek uitgevee"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Gesprek is uitgevee. Raak om \'n ander Boodskappe-gesprek te wys"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Geblokkeer"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Gedeblokkeer"</string>
+ <string name="db_full" msgid="8459265782521418031">"Daar is min bergingspasie. Sommige data kan verloor word."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Kies aanhegsels"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Bevestig keuse"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> gekies"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Verwyder asseblief een of meer aanhegsels en probeer weer."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Jy kan probeer om jou boodskap te stuur, maar dit sal dalk nie afgelewer word tensy jy een of meer aanhegsels verwyder nie."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Jy kan net een video per boodskap stuur. Verwyder bykomende video\'s en probeer weer."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Boodskappe kon nie aanhegsel laai nie."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Stuur in elk geval"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Kon nie gesprek begin nie"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> gekies"</string>
+</resources>
diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml
new file mode 100644
index 0000000..225008e
--- /dev/null
+++ b/res/values-am/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ርዕሰ-ጉዳይ የሌለው"</item>
+ <item msgid="272485471009191934">"ምንም ርእስ የለውም"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"አዎ"</item>
+ <item msgid="6049132459802288033">"አይ"</item>
+ <item msgid="3084376867445867895">"እሺ"</item>
+ <item msgid="3155097332660174689">"ሃሃ"</item>
+ <item msgid="2611328818571146775">"እናመሰግናለን"</item>
+ <item msgid="4881335087096496747">"እስማማለሁ"</item>
+ <item msgid="2422296858597420738">"በጣም ጥሩ"</item>
+ <item msgid="4805581752819452687">"እየመጣሁ ነኝ"</item>
+ <item msgid="4746700499431366214">"እሺ፣ በኋላ ላይ ላግኝዎት"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..9707157
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"መልዕክት መላኪያ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"መልዕክት መላኪያ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"ውይይት ምረጥ"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ቅንብሮች"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"መልዕክት ላከ"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ዓባሪ አክል"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"እገዛ"</string>
+ <string name="welcome" msgid="2857560951820802321">"እንኳን በደህና መጡ"</string>
+ <string name="skip" msgid="7238879696319945853">"ዝለል"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"ቀጣይ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"ቀጣይ"</string>
+ <string name="exit" msgid="1905187380359981199">"ውጣ"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ቅንብሮች &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ቅንብሮች"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"አላላክ የኤስኤምኤስ፣ ስልክ እና እውቂያዎች ፍቃድ ያስፈልገዋል።"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ፍቃዶችን በቅንብሮች &gt; መተግበሪያዎች &gt; አላላክ &gt; ፍቃዶች ውስጥ መለወጥ ይችላሉ።"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ፍቃዶችን በቅንብሮች፣ መተግበሪያዎች፣ አላላክ፣ ፍቃዶች ውስጥ መለወጥ ይችላሉ።"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"ተደጋጋሚዎች"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ሁሉም ዕውቂያዎች"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"ወደ <xliff:g id="DESTINATION">%s</xliff:g> ላክ"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ስዕሎችን አንሳ ወይም ቪዲዮ ቅረጽ"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ከዚህ መሣሪያ ላይ ምስሎችን ይምረጡ"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ተሰሚ ቅረጽ"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ፎቶ ይምረጡ"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"ሚዲያው ተመርጧል።"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"ሚዲያው አልተመረጠም።"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ተመርጠዋል"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ምስል <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" msgid="8142175029053370662">"ምስል"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ተሰሚ ቅረጽ"</string>
+ <string name="action_share" msgid="2143483844803153871">"አጋራ"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ገና አሁን"</string>
+ <string name="posted_now" msgid="867560789350406701">"አሁን"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ደቂቃዎች</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ደቂቃዎች</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ሰዓቶች</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ሰዓቶች</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ቀኖች</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ቀኖች</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ሳምንቶች</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ሳምንቶች</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ወሮች</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ወሮች</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ዓመቶች</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ዓመቶች</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"የመደብ 0 መልዕክት"</string>
+ <string name="save" msgid="5081141452059463572">"አስቀምጥ"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"መሣሪያው አነስተኛ ቦታ ነው ያለው። አላላክ ቦታን ለማስለቀቅ የቆዩ መልዕክቶችን በራስ ሰር ይሰርዛል።"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"የማከማቻ ቦታ እያለቀ ነው"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"መሣሪያዎ ላይ ተጨማሪ ቦታ እስኪገኝ ድረስ አላላክ መልዕክቶችን ላይልክ ወይም ላይቀበል ይችላል።"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"ዝቅተኛ የኤስኤምኤስ ማከማቻ። መልዕክቶችን መሰረዝ ሊኖርብዎ ይችላል።"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"ስልክ ቁጥርዎን ያረጋግጡ"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ይሄ የአንድ ጊዜ እርምጃ አላላክ የቡድን መልዕክቶችዎን በአግባቡ እንደሚልክ ያረጋግጣል።"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ስልክ ቁጥር"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"ማህደረመረጃ ያላቸው ሁሉንም መልዕክቶች ሰርዝ"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"ከ<xliff:g id="DURATION">%s</xliff:g> በላይ የቆዩ መልዕክቶችን ሰርዝ"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"ከ<xliff:g id="DURATION">%s</xliff:g> በላይ የቆዩ መልዕክቶችን በራስ-ሰር ሰርዝ"</string>
+ <string name="ignore" msgid="7063392681130898793">"ችላ በል"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"ማህደረመረጃ ያላቸው ሁሉም መልዕክቶች ይሰረዙ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"ከ<xliff:g id="DURATION">%s</xliff:g> በላይ የቆዩ መልዕክቶች ይሰረዙ?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"ከ<xliff:g id="DURATION">%s</xliff:g> በላይ የቆዩ መልዕክቶች ይሰረዙ እና ራስ-ሰር ሰራዥ ይብራ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> እንዲህ ብለዋል፦"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"እርስዎ እንዲህ ብለዋል፦"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"መልዕክት ከ<xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"እርስዎ መልዕክት ልከዋል"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"በመላክ ላይ…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"አልተላከም። እንደገና ለመሞከር ይንኩ።"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"አልተላከም። እንደገና በመሞከር ላይ…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ዳግም ላክ ወይም ሰርዝ"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"ወደ ድንገተኛ አገልግሎቶች እባክዎ የድምፅ ጥሪ ያድርጉ። የእርስዎ የጽሑፍ መልዕክት አሁን ላይ ሊደርስ አይችልም።"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"አልተሳካም"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"አዲስ የሚወርድ የኤምኤምኤስ መልዕክት"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"አዲስ የኤምኤምኤስ መልዕክት"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ማውረድ አልተቻለም"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"እንደገና ለመሞከር ይንኩ"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ለማውረድ ይንኩ"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"አውርድ ወይም ሰርዝ"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"በማውረድ ላይ…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"መልዕክቱ ጊዜው አልፎበታል ወይም አይገኝም።"</string>
+ <string name="mms_info" msgid="3402311750134118165">"መጠን፦ <xliff:g id="MESSAGESIZE">%1$s</xliff:g>፣ ጊዜው የሚያልፍበት፦ <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"መላክ አይቻልም። ተቀባይ ልክ አይደለም።"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"አገልግሎት በአውታረ መረብ ላይ አልገበረም"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"በአውታረ መረብ ችግር ምክንያት መላክ አልተቻለም"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"መልዕክቱ ጊዜው አልፎበታል ወይም አይገኝም።"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(ርዕስ ጉዳይ የለውም)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"ያልታወቀ ላኪ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ደርሷል"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"መልዕክት <xliff:g id="SUBJECT">%1$s</xliff:g> ከ<xliff:g id="FROM">%2$s</xliff:g> ማውረድ አልተቻለም።"</string>
+ <string name="low_memory" msgid="5300743415198486619">"በአነስተኛ ማህደረ ትውስታ ምክንያት የውሂብ ጎታ ክወና ማጠናቀቅ አልተቻለም"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"መልዕክት አልተላከም"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"አንዳንድ መልዕክቶች በአላላክ ውስጥ አልተላኩም"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> መልዕክቶች በ<xliff:g id="CONVERSATIONS">%d</xliff:g> ውይይቶች ውስጥ</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> መልዕክቶች በ<xliff:g id="CONVERSATIONS">%d</xliff:g> ውይይቶች ውስጥ</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"መልዕክት አልወረደም"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"አንዳንድ መልዕክቶች በአላላክ ውስጥ አልወረዱም"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> መልዕክቶች በ<xliff:g id="CONVERSATIONS">%d</xliff:g> ውይይቶች ውስጥ</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> መልዕክቶች በ<xliff:g id="CONVERSATIONS">%d</xliff:g> ውይይቶች ውስጥ</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"ወደ <xliff:g id="NUMBER">%1$s</xliff:g> የሚሄድ መልዕክት አልተላከም"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"ወደ ድንገተኛ አገልግሎቶች እባክዎ የድምፅ ጥሪ ያድርጉ። የእርስዎ ወደ <xliff:g id="NUMBER">%1$s</xliff:g> የጽሑፍ መልዕክት አሁን ላይ ሊደርስ አይችልም።"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> አዲስ መልዕክቶች</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> አዲስ መልዕክቶች</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ጀምር"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ካሜራ ሊገኝ አይችልም"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ካሜራ ሊገኝ አይችልም"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"የቪዲዮ ቀረጻ ሊገኝ አይችልም"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"ማህደረ መረጃን ማስቀመጥ አይቻልም"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ስዕል ማንሳት አልተቻለም"</string>
+ <string name="back" msgid="1477626055115561645">"ተመለስ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"በማህደር ተቀምጧል"</string>
+ <string name="action_delete" msgid="4076795795307486019">"ሰርዝ"</string>
+ <string name="action_archive" msgid="5437034800324083170">"በማህደር አስቀምጥ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ከማህደር አስወጣ"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"ማሳወቂያዎችን አጥፋ"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"ማሳወቂያዎችን አብራ"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"እውቂያ አክል"</string>
+ <string name="action_download" msgid="7786338136368564146">"አውርድ"</string>
+ <string name="action_send" msgid="377635240181672039">"ላክ"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"ሰርዝ"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ይህን መልዕክት ይሰረዝ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ይህ እርምጃ ሊቀለበስ አይችልም።"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"ሰርዝ"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">እነዚህ ውይይቶች ይሰርዙ?</item>
+ <item quantity="other">እነዚህ ውይይቶች ይሰርዙ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"ሰርዝ"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"ይቅር"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"ለ"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"በርካታ ምስሎችን ምረጥ"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"ምርጫ አረጋግጥ"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ድምፅን መቅዳት አልተቻለም። እንደገና ይሞክሩ።"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ድምፅን ማጫወት አልተቻለም። እንደገና ይሞክሩ።"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ድምፅን ማስቀመጥ አልተቻለም። እንደገና ይሞክሩ።"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ነካ ያድርጉ እና ይያዙ"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"፣ "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">"፦ "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ስዕል"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"የተሰሚ ቅንጥብ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ቪዲዮ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"የእውቂያ ካርድ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"አውርድ"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"በኤስኤምኤስ በኩል መልስ ስጥ"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"በኤምኤምኤስ በኩል ምላሽ ስጥ"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ምላሽ ስጥ"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ተሳታፊዎች</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ተሳታፊዎች</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"እኔ"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"እውቂያ ታግዶ በማህደር ውስጥ ተቀምጧል"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"እውቂያ እገዳው ተነስቷል እና ከቤተማከማቻ ወጥቷል"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> በማህደር ተቀምጧል"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ከማህደር ወጥተዋል"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"ማሳወቂያዎች ጠፍተዋል"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"ማሳወቂያዎች በርተዋል"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"ሁሉም ተቀናብሯል። እንደገና ላክ የሚለውን ይንኩ።"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"የአላላክ በተሳካ ሁኔታ እንደ ነባሪ የኤስኤምኤስ መተግበሪያ ተዘጋጅቷል።"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">ዓባሪዎችን አስወግድ</item>
+ <item quantity="other">ዓባሪዎችን አስወግድ</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"የኦዲዮ ዓባሪ"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"የኦዲዮ ዓባሪ ያጫውቱ"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"ለአፍታ አቁም"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"መልእክት ከ <xliff:g id="SENDER">%s</xliff:g>፦ <xliff:g id="MESSAGE">%s</xliff:g>።"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"ያልተሳካ መልዕክት ከ<xliff:g id="SENDER">%s</xliff:g>፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"ከ<xliff:g id="SENDER">%s</xliff:g> የመጣ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"ወደ <xliff:g id="CONTACT">%s</xliff:g> የሚሄድ ያልተላከ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"መልዕክት ወደ <xliff:g id="CONTACT">%s</xliff:g> በመላክ ላይ፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"ያልተሳካ ወደ <xliff:g id="CONTACT">%s</xliff:g> የሚሄድ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"ወደ <xliff:g id="CONTACT">%s</xliff:g> የሚሄድ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"ያልተሳካ መልዕክት ከ<xliff:g id="SENDER">%s</xliff:g>፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>። <xliff:g id="GROUPINFO">%s</xliff:g>።"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"መልዕክት ከ<xliff:g id="SENDER">%s</xliff:g>፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>። <xliff:g id="GROUPINFO">%s</xliff:g>።"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"ወደ <xliff:g id="GROUP">%s</xliff:g> የሚሄድ ያልተላከ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"መልዕክት ወደ <xliff:g id="GROUP">%s</xliff:g> በመላክ ላይ፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"ያልተሳካ ወደ <xliff:g id="GROUP">%s</xliff:g> የሚሄድ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"ወደ <xliff:g id="GROUP">%s</xliff:g> የሚሄድ መልዕክት፦ <xliff:g id="MESSAGE">%s</xliff:g>። ሰዓት፦ <xliff:g id="TIME">%s</xliff:g>።"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ያልተሳካ መልዕክት። ዳግም ለመሞከር ይንኩ።"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"ከ<xliff:g id="PARTICIPANTS">%s</xliff:g> ጋር ውይይት"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"ርዕሰ ጉዳይ ሰርዝ"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ቪዲዮ ቅረጽ"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"የቆመ ምስል አንሳ"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ፎቶ አንሳ"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ቪዲዮ መቅረጽ ጀምር"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"ወደ የሙሉ ማያ ገጽ ካሜራ ቀይር"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"በፊት እና ኋላ ካሜራ መካከል ቀያይር"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"መቅረጽ አቁም እና ቪዲዮ አያይዝ"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ቪዲዮ መቅዳትን አቁም"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"ፎቶዎችን በመልዕክት መላክ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ፎቶዎች ወደ የ«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» አልበም ተቀምጠዋል</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ፎቶዎች ወደ የ«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» አልበም ተቀምጠዋል</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ቪዲዮዎች ወደ የ«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» አልበም ተቀምጠዋል</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ቪዲዮዎች ወደ የ«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» አልበም ተቀምጠዋል</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ዓባሪዎች ወደ «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» አልበም ተቀምጠዋል</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ዓባሪዎች ወደ «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» አልበም ተቀምጠዋል</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ዓባሪዎች ወደ «ውርዶች» ተቀምጠዋል</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ዓባሪዎች ወደ «ውርዶች» ተቀምጠዋል</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ዓባሪዎች ተቀምጠዋል</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ዓባሪዎች ተቀምጠዋል</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ዓባሪዎችን ማስቀመጥ አልተቻለም</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ዓባሪዎችን ማስቀመጥ አልተቻለም</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"የኤምኤምኤስ ዓባሪ ተቀምጧል"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ቅንብሮች"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"በማህደር ተቀምጧል"</string>
+ <string name="action_close" msgid="1840519376200478419">"ዝጋ"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"ኤምኤምኤስ"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"የላቀ"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"አርም"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"ማሳወቂያዎች"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ድምፅ"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"ፀጥታ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"ንዘር"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ታግዷል"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"የኤስኤምኤስ መላክ ሪፖርቶች"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ለሚልኩት እያንዳንዱ ኤስኤምኤስ የመላኪያ ሪፖርት ጠይቅ"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"በራስ- ሰርስረህ አውጣ"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"የኤምኤምኤስ መልዕክቶችን በራስ-ሰር ሰርስረህ አውጣ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"በእንቅስቃሴ ላይ በራስ-ሰር ሰርስረህ አውጣ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"የስልክ አገልግሎት ሰጪ በሚቀየርበት ጊዜ ኤምኤምኤስን በራስ-ሰር ሰርስረህ አውጣ"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"የቡድን መልዕክት መላላኪያ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ብዙ ተቀባዮች ሲኖሩ አንዲት መልዕክት ለመላክ ኤምኤምኤስ ይጠቀሙ"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ነባሪ የኤስኤምኤስ መተግበሪያ"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ነባሪ የኤስኤምኤስ መተግበሪያ"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"ስልክ ቁጥርዎ"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"የማይታወቅ"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"የወጪ መልዕክት ድምጾች"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"ኤስኤምኤስ አራግፍ"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"የመጡ የኤስኤምኤስ ጥሬ ውሂብ ወደ ውጫዊ የማከማቻ ፋይል ላይ አራግፍ"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"ኤምኤምኤስ አራግፍ"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"የመጡ የኤምኤምኤስ ጥሬ ውሂብ ወደ ውጫዊ የማከማቻ ፋይል ላይ አራግፍ"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ገመድ አልባ ማንቂያዎች"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"የመልዕክት አማራጮች"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ጽሁፍ ገልብጥ"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"ዝርዝሮችን ይመልከቱ"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"ሰርዝ"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ያስተላልፉ"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"የመልዕክት ዝርዝሮች"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ዓይነት፦ "</string>
+ <string name="text_message" msgid="7415419755252205721">"የፅሁፍ መልዕክት"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"ማህደረ ብዙ መረጃመልዕክት"</string>
+ <string name="from_label" msgid="1947831848146564875">"ከ፦ "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"ለ፦ "</string>
+ <string name="sent_label" msgid="5186286057597137301">"የተላከው፦ "</string>
+ <string name="received_label" msgid="4442494712757995203">"የደረሰው፦ "</string>
+ <string name="subject_label" msgid="1887378451808609152">"ርዕሰ ጉዳይ፦ "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"መጠን፦ "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ቅድሚያ የሚሰጠው፦ "</string>
+ <string name="sim_label" msgid="2706003016582772108">"ሲም፦ "</string>
+ <string name="priority_high" msgid="728836357310908368">"ከፍተኛ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"መደበኛ"</string>
+ <string name="priority_low" msgid="7398724779026801851">"ዝቅ ያለ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"ሲም <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"የተደበቀ የላኪ አድራሻ"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"አባሪዎችን በመጫን ላይ እያለ መልዕክት መላክ አይችልም።"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ዓባሪን መጫን አልተቻለም። እንደገና ይሞክሩ።"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"አውታረ መረቡ ዝግጁ አይደለም። እንደገና ይሞክሩ።"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ጽሑፍ ሰርዝ"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ጽሑፍ እና ቁጥሮችን በማስገባት መካከል ይቀያይሩ"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"ተጨማሪ ተሳታፊዎችን ያክሉ"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"ተሳታፊዎችን ያረጋግጡ"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"አዲስ ውይይት ጀምር"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ይህን ንጥል ምረጥ"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ቪዲዮ አጫውት"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ሰዎች እና አማራጮች"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"አርም"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ሰዎች እና አማራጮች"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"አጠቃላይ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"በዚህ ውይይት ውስጥ ያሉ ሰዎች"</string>
+ <string name="action_call" msgid="6596167921517350362">"ደውል"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"መልዕክት ላክ"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"መልዕክት ይላኩ&lt;br/&gt;&lt;small&gt;from <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ፎቶዎችን ይላኩ</item>
+ <item quantity="other">ፎቶዎችን ይላኩ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ኦዲዮዎች ይላኩ</item>
+ <item quantity="other">ኦዲዮዎች ይላኩ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">ቪዲዮዎችን ይላኩ</item>
+ <item quantity="other">ቪዲዮዎችን ይላኩ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">የእውቂያ ካርዶችን ይላኩ</item>
+ <item quantity="other">የእውቂያ ካርዶችን ይላኩ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">ዓባሪዎችን ይላኩ</item>
+ <item quantity="other">ዓባሪዎችን ይላኩ</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ዓባሪዎችን ለመላክ የተዘጋጁ ዓባሪዎች</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ዓባሪዎችን ለመላክ የተዘጋጁ ዓባሪዎች</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ግብረመልስ ላክ"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"በGoogle Play መደብር ውስጥ ይመልከቱ"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"የስሪት መረጃ"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"ስሪት %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"የክፍት ምንጭ ፍቃዶች"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"ማሳወቂያዎች"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"የዓባሪ ገደብ ተደርሷል"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ዓባሪን መጫን አልተሳካም።"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"ወደ እውቂያ ይታከል?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"እውቅያ ያክሉ"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"ርዕሰ ጉዳይ"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"ርዕሰ ጉዳይ፦ "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"የእውቂያ ካርድ በመጫን ላይ"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"የእውቂያ ካርድን መጫን አልተቻለም"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"የእውቂያ ካርድ ይመልከቱ"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> እውቂያዎች</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> እውቂያዎች</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"የአውቂያ ካርዶች"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"የልደት ቀን"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ማስታወሻዎች"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"መልዕክት አስተላልፍ"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ምላሽ ስጥ"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"ኤስኤምኤስ ተሰናክሏል"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"ለመላክ፣ የአላላክን እንደ ነባሪ የኤስኤምኤስ መተግበሪያ ያዘጋጁት"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"አላላክን እንደ ነባሪ የኤስኤምኤስ መተግበሪያ ያዘጋጁት"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"ለውጥ"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"መልዕክቶችን ለመቀበል የአላላክን እንደ ነባሪ የኤስኤምኤስ መተግበሪያ አድርገው ያዘጋጁት"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"ኤስ ኤም ኤስ መልዕክቶችን ለመላክ ምንም ተመራጭ ሲም አልተመረጠም"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ይህ መተግበሪያ በመሣሪያው ባለቤት አይፈቀድም።"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"እሺ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"በአንድ ውይይት ውስጥ በጣም ብዙ ተሳታፊዎች"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">ልክ ያልሆኑ እውቂያዎች</item>
+ <item quantity="other">ልክ ያልሆኑ እውቂያዎች</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"የካሜራ ምስልን መጫን አልተቻለም"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"እርስዎ፦ "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>፦ "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ረቂቅ"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"አንዴ አዲስ ውይይት ከጀመሩ በኋላ እዚህ ተዘርዝሮ ያዩታል"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"በቤተመዛግብት እንዲቀመጡ የተደረጉ ውይይቶች እዚህ ላይ ብቅ ያላሉ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"ውይይቶች በመስቀል ላይ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ስዕል"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"የተሰሚ ቅንጥብ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ቪዲዮ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"የእውቂያ ካርድ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"ኤምኤምኤስ"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"ቀልብስ"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"እንደገና ይሞክሩ"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"አዲስ መልዕክት ለመጀመር የእውቂያ ስም ወይም ስልክ ቁጥር ያስገቡ"</string>
+ <string name="action_block" msgid="9032076625645190136">"አግድ"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>ን አግድ"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"የ<xliff:g id="DESTINATION">%s</xliff:g> እገዳን አንሳ"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> ይታገድ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ከዚህ ቁጥር መልዕክቶችን መቀበልዎን ይቀጥላሉ ነገርግን ከእንግዲህ እንዲያውቁት አይደረጉም። ይህ ውይይት በቤተመዛግብት ይቀመጣል።"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"የታገዱ እውቂያዎች"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"እገዳ አንሳ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"የታገዱ እውቂያዎች"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ከሰነዶች ቤተመዛግብት ምስል ይምረጡ"</string>
+ <string name="sending_message" msgid="6363584950085384929">"መልዕክት በመላክ ላይ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"መልዕክት ተልኳል"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"የተንቀሳቃሽ ስልክ ውሂብ ጠፍቷል። ቅንብሮችዎን ያረጋግጡ።"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"በአይሮፕላን ሁኔታ ላይ እያለ መልዕክቶችን መላክ አይቻልም"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"መልዕክቱ ሊላክ አልቻለም"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"መልዕክት ወርዷል"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"የተንቀሳቃሽ ስልክ ውሂብ ጠፍቷል። ቅንብሮችዎን ያረጋግጡ።"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"መልዕክቶችን በአውሮፕላን ሁነታ ላይ ማውረድ አይቻልም"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"መልዕክት ሊወርድ አልቻለም"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"ዜሮ"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"አንድ"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"ሁለት"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"ሶስት"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"አራት"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"አምስት"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ስድስት"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ሰባት"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ስምንት"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ዘጠኝ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"መልዕክት በ<xliff:g id="CARRIERNAME">%1$s</xliff:g> መላክ አልተቻለም፣ ስህተት <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"መልዕክት ባልታወቀ አገልግሎት አቅራቢ መላክ አልተቻለም፣ ስህተት <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"የተላለፈ፦ <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"መልዕክት አልተላከም፦ አገልግሎቱ በአውታረ መረብ ላይ አልገበረም።"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"መልዕክት አልተላከም፦ ልክ ያልሆነ የመድረሻ አድራሻ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"መልዕክት አልተላከም፦ ልክ ያልሆነ መልዕክት"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"መልዕክት አልተላከም፦ የማይደገፍ ይዘት"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"መልዕክት አልተላከም፦ የማይደገፍ መልዕክት"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"መልዕክት አልተላከም፦ በጣም ትልቅ ነው"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"አዲስ መልዕክት"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"ይመልከቱ"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"ምስል"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"አግባብ የሆነውን መተግበሪያ ማግኘት አልተቻለም"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"ተቀባይ አስወግድ"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"አዲስ መልዕክት"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"ተወው"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"የመድረሻ ነጥብ ያርትዑ"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"አልተዘጋጀም"</string>
+ <string name="apn_name" msgid="1572691851070894985">"ስም"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"ኤ.ፒ.ኤን."</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"የMMS እጅ አዙር"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"የMMS ወደብ"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"የኤ.ፒ.ኤን አይነት"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"ኤ.ፒ.ኤን. ሰርዝ"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"አዲስ ኤ.ፒ.ኤን."</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"አስቀምጥ"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ጣለው"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"የስም መስክ ባዶ ሊሆን አይችልም"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"ኤ.ፒ.ኤን. ባዶ መሆን አይችልም።"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"የMCC መስክ 3 አሀዝ መሆን አለበት።"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"የMNC መስክ 2 ወይም 3 አሀዝ መሆን አለበት።"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ነባሪ የኤ.ፒ.ኤን. ቅንብሮችን እነበረበት መመለስ።"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"ወደ ነባሪ ዳግም አስጀምር"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"የዳግም አስጀምር ነባሪ ኤ.ፒ.ኤን. ቅንብሮች ተጠናቀዋል።"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ርዕስ አልባ"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"የመዳረሻ ነጥብ ስም"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"ኤ.ፒ.ኤኖች."</string>
+ <string name="menu_new" msgid="8286285392706532511">"አዲስ ኤ.ፒ.ኤን."</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"የመዳረሻ ነጥብ ስም ቅንብሮች ለዚህ ተጠቃሚ አይገኙም"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ወደ የቅንጥብ ሰሌዳ ይቀዳ?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"ቅዳ"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ለ<xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"አጠቃላይ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"የላቀ"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"አጠቃላይ ቅንብሮች"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"የላቁ ቅንብሮች"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"የ«<xliff:g id="SIM_NAME">%s</xliff:g>» ሲም"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"የነጠላ ኤስኤምኤስ መልዕክቶችን ለሁሉም ተቀባዮች ይላኩ። እርስዎ ብቻ ነዎት ማንኛቸውም ምላሾችን የሚቀበሉት"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"አንዲት ኤምኤምኤስ ለሁሉም ተቀባዮች ይላኩ"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"ያልታወቀ ቁጥር"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"አዲስ መልዕክት"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"አዲስ መልዕክት"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"ሲም መራጭ"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ተመርጧል፣ የሲም መራጭ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"ርዕሰ ጉዳይ ያርትዑ"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"ሲም ይምረጡ ወይም ርዕሰ ጉዳይን ያርትዑ"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ተሰሚን ለመቅረጽ ይንኩና ይያዙ"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"አዲስ ውይይት ጀምር"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"አላላክ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"የአላላክ ዝርዝር"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"አላላክ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"አዲስ መልዕክት"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"የውይይት ዝርዝር"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"ውይይቶችን በመጫን ላይ"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"መልዕክቶች በመስቀል ላይ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"ተጨማሪ ውይይቶችን ይመልከቱ"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"ተጨማሪ መልዕክቶችን አሳይ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"ውይይት ተሰርዟል"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"ውይይት ተሰርዟል። የተለየ የአላላክ ውይይት ለማሳየት ይንኩ"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ታግዷል"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"አልታገደም"</string>
+ <string name="db_full" msgid="8459265782521418031">"የማከማቻ ቦታ ዝቅተኛ ነው። አንዳንድ ውሂብ ሊጠፋ ይችላል።"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ዓባሪዎችን ይምረጡ"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"ምርጫ አረጋግጥ"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ተመርጠዋል"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"እባክዎ አንድ ወይም ተጨማሪ ዓባሪዎችን ያስወግዱና እንደገና ይሞክሩ።"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"መልዕክትዎን ለመላክ መሞከር ይችላሉ፣ ነገር ግን አንድ ወይም ተጨማሪ ዓባሪዎችን እስኪያስወግዱ ድረስ ላይደርስ ይችላል።"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"በአንድ መልዕክት አንድ ቪዲዮ ብቻ ለመላክ ይችላሉ። እባክዎ ተጨማሪ ቪዲዮዎችን ያስወግዱ እና እንደገና ይሞክሩ።"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"አላላክ አባሪን መጫን አልቻለም።"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ለማንኛውም ላክ"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"ውይይት መጀመር አልተቻለም"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ተመርጧል"</string>
+</resources>
diff --git a/res/values-ar/arrays.xml b/res/values-ar/arrays.xml
new file mode 100644
index 0000000..0cf1702
--- /dev/null
+++ b/res/values-ar/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"بدون موضوع"</item>
+ <item msgid="272485471009191934">"بدون موضوع"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"نعم"</item>
+ <item msgid="6049132459802288033">"لا"</item>
+ <item msgid="3084376867445867895">"موافق"</item>
+ <item msgid="3155097332660174689">"هاها"</item>
+ <item msgid="2611328818571146775">"شكرًا"</item>
+ <item msgid="4881335087096496747">"أوافق"</item>
+ <item msgid="2422296858597420738">"لطيف"</item>
+ <item msgid="4805581752819452687">"في الطريق"</item>
+ <item msgid="4746700499431366214">"حسنًا، سأتناول ذلك لاحقًا"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..8a58c4d
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,623 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"المراسلة"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"المراسلة"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"تحديد محادثة"</string>
+ <string name="action_settings" msgid="1329008122345201684">"الإعدادات"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"إرسال رسالة"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"إضافة مرفق"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"مساعدة"</string>
+ <string name="welcome" msgid="2857560951820802321">"مرحبًا"</string>
+ <string name="skip" msgid="7238879696319945853">"تخطٍ"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"‏التالي &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"التالي"</string>
+ <string name="exit" msgid="1905187380359981199">"خروج"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"‏الإعدادات &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"الإعدادات"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"‏يحتاج تطبيق المراسلة إلى إذن للوصول إلى الرسائل القصيرة SMS والهاتف وجهات الاتصال."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"‏يمكنك تغيير الأذونات من خلال الإعدادات &gt; التطبيقات &gt; المراسلة &gt; الأذونات."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"يمكنك تغيير الأذونات من خلال الانتقال إلى الإعدادات، التطبيقات، المراسلة، الأذونات."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"من يتم الاتصال بهم بشكل متكرر"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"كل جهات الاتصال"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"إرسال إلى <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"التقاط الصور أو الفيديو"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"اختيار صور من هذا الجهاز"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"تسجيل الصوت"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"اختيار صورة"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"تم تحديد الوسائط."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"تم إلغاء تحديد الوسائط."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"تم تحديد <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"صورة <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" msgid="8142175029053370662">"صورة"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"تسجيل الصوت"</string>
+ <string name="action_share" msgid="2143483844803153871">"المشاركة"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"للتو"</string>
+ <string name="posted_now" msgid="867560789350406701">"الآن"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> دقيقة</item>
+ <item quantity="two">دقيقتان (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> دقائق</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> دقيقة</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> من الدقائق</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> دقيقة</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> ساعة</item>
+ <item quantity="two">ساعتان (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> ساعات</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> ساعة</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> من الساعات</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ساعة</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> يوم</item>
+ <item quantity="two">يومان (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> أيام</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> يومًا</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> من الأيام</item>
+ <item quantity="one">يوم واحد (<xliff:g id="COUNT_0">%d</xliff:g>)</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="zero"><xliff:g id="COUNT">%d</xliff:g> أسبوع</item>
+ <item quantity="two">أسبوعان (<xliff:g id="COUNT">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> أسابيع</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> أسبوعًا</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> من الأسابيع</item>
+ <item quantity="one">أسبوع</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="zero"><xliff:g id="COUNT">%d</xliff:g> شهر</item>
+ <item quantity="two">شهران (<xliff:g id="COUNT">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> أشهر</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> شهرًا</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> من الأشهر</item>
+ <item quantity="one">شهر</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="zero"><xliff:g id="COUNT">%d</xliff:g> عام</item>
+ <item quantity="two">عامان (<xliff:g id="COUNT">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> أعوام</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> عامًا</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> من الأعوام</item>
+ <item quantity="one">عام</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"رسالة من الفئة 0"</string>
+ <string name="save" msgid="5081141452059463572">"حفظ"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"مساحة الجهاز منخفضة. سيحذف تطبيق المراسلة الرسائل القديمة تلقائيًا لتحرير مساحة."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"مساحة التخزين منخفضة"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"ربما لا يرسل تطبيق المراسلة رسائل أو يتلقاها ما لم تتوفر المزيد من المساحة على جهازك."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"‏سعة تخزين الرسائل القصيرة SMS منخفضة. ربما تحتاج إلى حذف بعض الرسائل."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"تأكيد رقم الهاتف"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ستضمن هذه الخطوة - التي يتم تنفيذها مرة واحدة فقط - تسليم تطبيق المراسلة لرسائلك الجماعية بشكل صحيح."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"رقم الهاتف"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"حذف جميع الرسائل التي تتضمن وسائط"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"حذف الرسائل الأقدم من <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"حذف الرسائل الأقدم من <xliff:g id="DURATION">%s</xliff:g> تلقائيًا"</string>
+ <string name="ignore" msgid="7063392681130898793">"تجاهل"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"هل تريد حذف كل الرسائل التي تتضمن ملفات وسائط؟"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"هل تريد حذف الرسائل الأقدم من <xliff:g id="DURATION">%s</xliff:g>؟"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"هل تريد حذف الرسائل الأقدم من <xliff:g id="DURATION">%s</xliff:g> وتشغيل الحذف التلقائي؟"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"قال <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"أنت قلت"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"رسالة من <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"لقد أرسلت رسالة"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"جارٍ الإرسال..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"لم يتم إرسال الرسالة. المس لإعادة المحاولة."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"لم يتم إرسال الرسالة. جارٍ إعادة المحاولة…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"إعادة إرسال أو حذف"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"الرجاء إجراء مكالمة صوتية لخدمات الطوارئ. يتعذر توصيل رسالتك النصية في الوقت الحالي."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"أخفق الإرسال"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"هناك رسالة وسائط متعددة جديدة للتنزيل"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"رسالة وسائط متعددة جديدة"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"تعذر التنزيل"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"المس لإعادة المحاولة"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"المس للتنزيل"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"تنزيل أو حذف"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"جارٍ التنزيل…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"انتهت صلاحية الرسالة أو ليست متاحة"</string>
+ <string name="mms_info" msgid="3402311750134118165">"الحجم: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>، تاريخ انتهاء الصلاحية: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"يتعذر الإرسال. المستلم غير صالح."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"لم يتم تنشيط الخدمة على الشبكة"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"تعذر الإرسال بسبب حدوث مشكلة في الشبكة"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"انتهت صلاحية الرسالة أو ليست متاحة"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(بلا موضوع)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"مرسل غير معروف"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"تم التسليم"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"تعذر تنزيل الرسالة <xliff:g id="SUBJECT">%1$s</xliff:g> من <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"تعذر إتمام تشغيل قاعدة البيانات نظرًا لانخفاض الذاكرة"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"لم يتم إرسال الرسالة"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"لم يتم إرسال بعض الرسائل في تطبيق المراسلة"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="zero"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> محادثة</item>
+ <item quantity="two"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في محادثتين (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> محادثات</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> محادثة</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> من المحادثات</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> من الرسائل في محادثة واحدة</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"لم يتم تنزيل الرسالة"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"لم يتم تنزيل بعض الرسائل في تطبيق المراسلة"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="zero"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> محادثة</item>
+ <item quantity="two"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في محادثتين (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> محادثات</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> محادثة</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> من الرسائل في <xliff:g id="CONVERSATIONS">%d</xliff:g> من المحادثات</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> من الرسائل في محادثة واحدة</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"لم يتم إرسال رسالة إلى <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"الرجاء إجراء مكالمة صوتية لخدمات الطوارئ. يتعذر إرسال رسالتك النصية إلى <xliff:g id="NUMBER">%1$s</xliff:g> في الوقت الحالي."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="zero">ليست هناك أية رسالة جديدة (<xliff:g id="MESSAGES">%d</xliff:g>)</item>
+ <item quantity="two">رسالتان (<xliff:g id="MESSAGES">%d</xliff:g>) جديدتان</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> رسائل جديدة</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> رسالة جديدة</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> من الرسائل الجديدة</item>
+ <item quantity="one">رسالة جديدة</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"بدء"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"لا تتوفر كاميرا"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"لا تتوفر كاميرا"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ميزة التقاط الفيديو غير متاحة"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"يتعذر حفظ ملف الوسائط"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"لا يمكن التقاط الصورة"</string>
+ <string name="back" msgid="1477626055115561645">"رجوع"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"المؤرشفة"</string>
+ <string name="action_delete" msgid="4076795795307486019">"حذف"</string>
+ <string name="action_archive" msgid="5437034800324083170">"أرشيف"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"إزالة من الأرشيف"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"إيقاف الإشعارات"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"تشغيل الإشعارات"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"إضافة جهة اتصال"</string>
+ <string name="action_download" msgid="7786338136368564146">"تنزيل"</string>
+ <string name="action_send" msgid="377635240181672039">"إرسال"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"حذف"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"هل تريد حذف هذه الرسالة؟"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"لا يمكن التراجع عن هذا الإجراء."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"حذف"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="zero">ليست هناك محادثات.</item>
+ <item quantity="two">هل تريد حذف هاتين المحادثتين؟</item>
+ <item quantity="few">هل تريد حذف هذه المحادثات؟</item>
+ <item quantity="many">هل تريد حذف هذه المحادثات؟</item>
+ <item quantity="other">هل تريد حذف هذه المحادثات؟</item>
+ <item quantity="one">هل تريد حذف هذه الدردشة؟</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"حذف"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"إلغاء"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"إلى"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"تحديد صور متعددة"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"تأكيد الاختيار"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"يتعذر تسجيل الصوت. أعد المحاولة."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"يتعذر تشغيل الصوت. أعد المحاولة."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"تعذر حفظ الصوت. أعد المحاولة."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"اللمس مع الاستمرار"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"، "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"الصورة"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"مقطع صوتي"</string>
+ <string name="notification_video" msgid="4331423498662606204">"الفيديو"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"بطاقة جهة الاتصال"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"تنزيل"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"‏الرد عبر رسالة قصيرة SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"الرد عبر رسالة وسائط متعددة"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"الرد"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="zero">ليس هناك أي مشارك (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="two">مشاركان (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> مشاركين</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> مشاركًا</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> من المشاركين</item>
+ <item quantity="one">مشارك واحد (<xliff:g id="COUNT_0">%d</xliff:g>)</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"أنا"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"تم حظر جهة الاتصال وأرشفتها"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"تم إلغاء حظر جهة الاتصال وإلغاء أرشفتها"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"تم وضع <xliff:g id="COUNT">%d</xliff:g> في الأرشيف"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"تمت إزالة <xliff:g id="COUNT">%d</xliff:g> من الأرشيف"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"تم تعطيل الإشعارات"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"تم تشغيل الإشعارات"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"اكتمل الإعداد. المس \"إرسال\" مرة أخرى."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"‏تم تعيين تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي بنجاح."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="zero">ليست هناك مرفقات</item>
+ <item quantity="two">تجاهل المرفقين</item>
+ <item quantity="few">تجاهل المرفقات</item>
+ <item quantity="many">تجاهل المرفقات</item>
+ <item quantity="other">تجاهل المرفقات</item>
+ <item quantity="one">تجاهل المرفق</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"مرفق صوتي"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"تشغيل المرفق الصوتي"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"إيقاف مؤقت"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"رسالة من <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"رسالة تعذر إرسالها من <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"رسالة من <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"رسالة لم يتم إرسالها إلى <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"جارٍ إرسال رسالة إلى <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"رسالة تعذر إرسالها إلى <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"رسالة إلى <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"رسالة تعذر إرسالها من <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"رسالة من <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"رسالة لم يتم إرسالها إلى <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"جارٍ إرسال رسالة إلى <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"رسالة تعذر إرسالها إلى <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"رسالة إلى <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. الوقت: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"رسالة تعذر إرسالها. المس لإعادة المحاولة."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"محادثة مع <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"حذف الموضوع"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"التقاط فيديو"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"التقاط صورة ثابتة"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"التقاط صورة"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"بدء تسجيل الفيديو"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"التبديل إلى الكاميرا بملء الشاشة"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"التبديل بين الكاميرا الأمامية والخلفية"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"وقف التسجيل وإرفاق مقطع الفيديو"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"إيقاف تسجيل الفيديو"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"صور المراسلة"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="zero">لم يتم حفظ أية صورة (<xliff:g id="QUANTITY_2">%d</xliff:g>) في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="two">تم حفظ صورتين (<xliff:g id="QUANTITY_2">%d</xliff:g>) في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> صور في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> صورة في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> من الصور في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">تم حفظ صورة واحدة (<xliff:g id="QUANTITY_0">%d</xliff:g>) في الألبوم \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="zero">لم يتم حفظ أي مقطع فيديو (<xliff:g id="QUANTITY_2">%d</xliff:g>) في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="two">تم حفظ مقطعي فيديو (<xliff:g id="QUANTITY_2">%d</xliff:g>) في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> مقاطع فيديو في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> مقطع فيديو في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> من مقاطع الفيديو في الألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">تم حفظ مقطع فيديو واحد (<xliff:g id="QUANTITY_0">%d</xliff:g>) في الألبوم \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="zero">لم يتم حفظ أي مرفق (<xliff:g id="QUANTITY_2">%d</xliff:g>) في ألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="two">تم حفظ مرفقين (<xliff:g id="QUANTITY_2">%d</xliff:g>) في ألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> مرفقات في ألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> مرفقًا في ألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other">تم حفظ <xliff:g id="QUANTITY_2">%d</xliff:g> من المرفقات في ألبوم \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">تم حفظ مرفق <xliff:g id="QUANTITY_0">%d</xliff:g> في ألبوم \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="zero">لم يتم حفظ أي مرفق (<xliff:g id="QUANTITY_1">%d</xliff:g>) في \"التنزيلات\"</item>
+ <item quantity="two">تم حفظ مرفقين (<xliff:g id="QUANTITY_1">%d</xliff:g>) في \"التنزيلات\"</item>
+ <item quantity="few">تم حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> مرفقات في \"التنزيلات\"</item>
+ <item quantity="many">تم حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> مرفقًا في \"التنزيلات\"</item>
+ <item quantity="other">تم حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> من المرفقات في \"التنزيلات\"</item>
+ <item quantity="one">تم حفظ مرفق واحد (<xliff:g id="QUANTITY_0">%d</xliff:g>) في \"التنزيلات\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="zero">لم يتم حفظ أية مرفقات (<xliff:g id="QUANTITY_1">%d</xliff:g>)</item>
+ <item quantity="two">تم حفظ مرفقين (<xliff:g id="QUANTITY_1">%d</xliff:g>)</item>
+ <item quantity="few">تم حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> مرفقات</item>
+ <item quantity="many">تم حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> مرفقًا</item>
+ <item quantity="other">تم حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> من المرفقات</item>
+ <item quantity="one">تم حفظ مرفق واحد <xliff:g id="QUANTITY_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="zero">لم يتعذر حفظ أية مرفقات (<xliff:g id="QUANTITY_1">%d</xliff:g>)</item>
+ <item quantity="two">تعذر حفظ مرفقين (<xliff:g id="QUANTITY_1">%d</xliff:g>)</item>
+ <item quantity="few">تعذر حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> مرفقات</item>
+ <item quantity="many">تعذر حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> مرفقًا</item>
+ <item quantity="other">تعذر حفظ <xliff:g id="QUANTITY_1">%d</xliff:g> من المرفقات</item>
+ <item quantity="one">تعذر حفظ مرفق واحد (<xliff:g id="QUANTITY_0">%d</xliff:g>)</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"تم حفظ مرفق رسالة الوسائط المتعددة"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"الإعدادات"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"المؤرشفة"</string>
+ <string name="action_close" msgid="1840519376200478419">"إغلاق"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"رسالة وسائط متعددة"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"إعدادات متقدمة"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"تصحيح الأخطاء"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"الإشعارات"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"الصوت"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"صامت"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"اهتزاز"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"محظور"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"‏تقارير تسليم الرسائل القصيرة SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"‏طلب تقرير تسليم لكل رسالة قصيرة SMS ترسلها"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"استرداد تلقائي"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"استرداد رسائل الوسائط المتعددة تلقائيًا"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"استرداد تلقائي للتجوال"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"استرداد رسائل الوسائط المتعددة عند التجوال تلقائيًا"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"المراسلة الجماعية"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"يمكنك استخدام رسائل الوسائط المتعددة لإرسال رسالة واحدة عندما يكون هناك عدة مستلمين"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"‏تطبيق الرسائل القصيرة SMS الافتراضي"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"‏تطبيق الرسائل القصيرة SMS الافتراضي"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"رقم هاتفك"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"غير معروف"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"أصوات الرسائل الصادرة"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"‏تفريغ الرسائل القصيرة SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"‏تفريغ البيانات الأولية للرسائل القصيرة SMS في ملف تخزين خارجي"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"تفريغ رسائل الوسائط المتعددة"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"تفريغ البيانات الأولية لرسائل الوسائط المتعددة في ملف تخزين خارجي"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"تنبيهات لاسلكية"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"خيارات الرسالة"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"نسخ النص"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"عرض التفاصيل"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"حذف"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"إعادة توجيه"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"تفاصيل الرسالة"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"النوع: "</string>
+ <string name="text_message" msgid="7415419755252205721">"رسالة نصية"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"رسالة وسائط متعددة"</string>
+ <string name="from_label" msgid="1947831848146564875">"من: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"إلى: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"تاريخ الإرسال: "</string>
+ <string name="received_label" msgid="4442494712757995203">"تاريخ التلقي: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"الموضوع: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"الحجم: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"الأولوية: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"عالية"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"عادية"</string>
+ <string name="priority_low" msgid="7398724779026801851">"منخفضة"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"عنوان المرسل المخفي"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"يتعذر إرسال رسالة أثناء تحميل المرفقات."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"يتعذر تحميل المرفق. أعد المحاولة."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"الشبكة ليست جاهزة بعد. يمكنك إعادة المحاولة."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"حذف النص"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"التبديل بين إدخال النص والأرقام"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"إضافة مزيد من المشاركين"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"تأكيد المشاركين"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"بدء محادثة جديدة"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"تحديد هذا العنصر"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"تشغيل الفيديو"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"الأشخاص والخيارات"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"تصحيح الأخطاء"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"الأشخاص والخيارات"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"عام"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"الأشخاص في هذه المحادثة"</string>
+ <string name="action_call" msgid="6596167921517350362">"إجراء مكالمة"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"إرسال رسالة"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"‏إرسال رسالة&lt;br/&gt;&lt;small&gt;من <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="zero">ليست هناك صور لإرسالها</item>
+ <item quantity="two">إرسال الصورتين</item>
+ <item quantity="few">إرسال الصور</item>
+ <item quantity="many">إرسال الصور</item>
+ <item quantity="other">إرسال الصور</item>
+ <item quantity="one">إرسال الصورة</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="zero">ليست هناك ملفات صوت لإرسالها</item>
+ <item quantity="two">إرسال ملفيّ الصوت</item>
+ <item quantity="few">إرسال ملفات الصوت</item>
+ <item quantity="many">إرسال ملفات الصوت</item>
+ <item quantity="other">إرسال ملفات الصوت</item>
+ <item quantity="one">إرسال ملف الصوت</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="zero">ليست هناك ملفات فيديو لإرسالها</item>
+ <item quantity="two">إرسال ملفي الفيديو</item>
+ <item quantity="few">إرسال ملفات الفيديو</item>
+ <item quantity="many">إرسال ملفات الفيديو</item>
+ <item quantity="other">إرسال ملفات الفيديو</item>
+ <item quantity="one">إرسال ملف الفيديو</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="zero">ليست هناك بطاقات جهة اتصال لإرسالها</item>
+ <item quantity="two">إرسال بطاقتي جهة الاتصال</item>
+ <item quantity="few">إرسال بطاقات جهة الاتصال</item>
+ <item quantity="many">إرسال بطاقات جهة الاتصال</item>
+ <item quantity="other">إرسال بطاقات جهة الاتصال</item>
+ <item quantity="one">إرسال بطاقة جهة الاتصال</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="zero">ليست هناك مرفقات لإرسالها</item>
+ <item quantity="two">إرسال المرفقين</item>
+ <item quantity="few">إرسال المرفقات</item>
+ <item quantity="many">إرسال المرفقات</item>
+ <item quantity="other">إرسال المرفقات</item>
+ <item quantity="one">إرسال المرفق</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="zero">لم يتم تحديد أي مرفقات</item>
+ <item quantity="two">مرفقان (<xliff:g id="ATTACHMENT_COUNT">%d</xliff:g>) جاهزان للإرسال</item>
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> مرفقات جاهزة للإرسال</item>
+ <item quantity="many"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> مرفقًا جاهزًا للإرسال</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> من المرفقات الجاهزة للإرسال</item>
+ <item quantity="one">مرفق واحد جاهز للإرسال</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"إرسال تعليقات"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"‏عرض في متجر Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"معلومات الإصدار"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"‏الإصدار %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"تراخيص برامج مفتوحة مصدر"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"الإشعارات"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"تم بلوغ حد المرفقات"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"أخفق تحميل المرفق."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"إضافة إلى جهات الاتصال؟"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"إضافة جهة اتصال"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"الموضوع"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"الموضوع: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"جارٍ تحميل بطاقة جهة الاتصال"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"تعذر تحميل بطاقة جهة الاتصال"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"عرض بطاقة جهة الاتصال"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="zero">ليست هناك أية جهة اتصال (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="two">جهتا اتصال (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> جهات اتصال</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> جهة اتصال</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> من جهات الاتصال</item>
+ <item quantity="one">جهة اتصال واحدة (<xliff:g id="COUNT_0">%d</xliff:g>)</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"بطاقات جهات الاتصال"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"تاريخ الميلاد"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ملاحظات"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"إعادة توجيه الرسالة"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"رد"</string>
+ <string name="plus_one" msgid="9010288417554932581">"1+"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"‏تم تعطيل الرسائل القصيرة SMS"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"‏للإرسال، عيّن تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"‏يجب تعيين تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"تغيير"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"‏لاستلام رسائل، عيّن تطبيق المراسلة كتطبيق الرسائل القصيرة SMS الافتراضي"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"‏لم يتم تحديد شريحة SIM المفضلة لإرسال رسائل قصيرة SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"لم يتم السماح لهذا التطبيق بواسطة مالك الجهاز."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"موافق"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"هناك عدد كبير جدًا من المشاركين في محادثة"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="zero">ليست هناك جهات اتصال غير صالحة</item>
+ <item quantity="two">جهتا اتصال غير صالحتين</item>
+ <item quantity="few">جهات اتصال غير صالحة</item>
+ <item quantity="many">جهات اتصال غير صالحة</item>
+ <item quantity="other">جهات اتصال غير صالحة</item>
+ <item quantity="one">جهة اتصال غير صالحة</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"يتعذر تحميل صورة الكاميرا"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"أنت: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"مسودة"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"بعد بدء دردشة جديدة، ستظهر مدرجة هنا"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"الإشعارات المؤرشفة تظهر هنا"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"جارٍ تحميل المحادثات..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"صورة"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"مقطع صوتي"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"فيديو"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"بطاقة جهة الاتصال"</string>
+ <string name="mms_text" msgid="1528791558806015806">"رسالة وسائط متعددة"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"تراجع"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"إعادة المحاولة"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"أدخل اسم جهة اتصال أو رقم هاتف لبدء رسالة جديدة"</string>
+ <string name="action_block" msgid="9032076625645190136">"حظر"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"حظر <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"إلغاء حظر <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"هل تريد حظر <xliff:g id="DESTINATION">%s</xliff:g>؟"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ستستمر في استلام الرسائل من هذا الرقم ولكن لن يتم إرسال إشعارات إليك مرة أخرى. ستتم أرشفة هذه المحادثة."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"جهات الاتصال المحظورة"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"إلغاء الحظر"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"جهات الاتصال المحظورة"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"اختيار صورة من مكتبة المستندات"</string>
+ <string name="sending_message" msgid="6363584950085384929">"جارٍ إرسال الرسالة"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"تم إرسال الرسالة"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"تم إيقاف اتصال بيانات الجوّال. تحقق من إعداداتك."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"يتعذر إرسال رسائل في وضع الطائرة"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"تعذر إرسال الرسالة"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"تم تنزيل الرسالة"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"تم إيقاف اتصال بيانات الجوّال. تحقق من إعداداتك."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"يتعذر تنزيل الرسائل في وضع الطائرة"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"تعذر تنزيل الرسالة"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"صفر"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"واحد"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"اثنان"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"ثلاثة"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"أربعة"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"خمسة"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ستة"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"سبعة"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ثمانية"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"تسعة"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"يتعذر إرسال الرسالة مع <xliff:g id="CARRIERNAME">%1$s</xliff:g>، الخطأ <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"يتعذر إرسال الرسالة مع مشغِّل شبكة جوّال غير معروف، الخطأ <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"إعادة التوجيه: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"لم يتم إرسال الرسالة: لم يتم تنشيط الخدمة على الشبكة"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"لم يتم إرسال الرسالة: عنوان الوجهة غير صالح"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"لم يتم إرسال الرسالة: رسالة غير صالحة"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"لم يتم إرسال الرسالة: محتوى غير متوافق"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"لم يتم إرسال الرسالة: رسالة غير متوافقة"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"لم يتم إرسال الرسالة: الحجم كبير جدًا"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"رسالة جديدة"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"عرض"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"صورة"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"تعذر العثور على تطبيق مناسب"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"إزالة المستلم"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"رسالة جديدة"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"إلغاء"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"تعديل نقطة الدخول"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"لم يتم التعيين"</string>
+ <string name="apn_name" msgid="1572691851070894985">"الاسم"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"الخادم الوكيل لرسائل الوسائط المتعددة"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"منفذ رسائل الوسائط المتعددة"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"‏نوع APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"‏حذف APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"‏APN جديد"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"حفظ"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"تجاهل"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"لا يمكن أن يكون حقل الاسم فارغًا."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"‏لا يمكن أن يكون APN فارغًا."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"‏يجب أن يكون حقل MCC من ثلاثة أرقام."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"‏يجب أن يتكون حقل MNC من رقمين أو ثلاثة أرقام."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"‏جارٍ استعادة إعدادات APN الافتراضية."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"إعادة تعيين إلى الإعداد الافتراضي"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"‏اكتملت إعادة تعيين إعدادات APN الافتراضية."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"بلا عنوان"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"أسماء نقاط الدخول"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"‏أسماء نقاط الدخول (APN)"</string>
+ <string name="menu_new" msgid="8286285392706532511">"‏APN جديد"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"إعدادات اسم نقطة الدخول ليست متوفرة لهذا المستخدم"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"هل تريد النسخ في الحافظة؟"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"نسخ"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"إلى <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"عام"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"إعدادات متقدمة"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"الإعدادات العامة"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"إعدادات متقدمة"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"‏شريحة SIM من \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"إرسال رسالة قصيرة واحدة إلى جميع المستلمين. ولن تصل الردود إلا إليك"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"إرسال رسالة وسائط متعددة إلى جميع المستلمين"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"رقم غير معروف"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"رسالة جديدة"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"رسالة جديدة."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"‏محدد SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"‏تم اختيار <xliff:g id="SIM_0">%1$s</xliff:g>، أداة اختيار شريحة SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"تعديل الموضوع"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"‏تحديد SIM أو تعديل الموضوع"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"المس مع الاستمرار لتسجيل الصوت"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"بدء محادثة جديدة"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"المراسلة"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"قائمة المراسلة"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"المراسلة"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"رسالة جديدة"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"قائمة المحادثات"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"جارٍ تحميل المحادثات."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"جارٍ تحميل الرسائل"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"عرض المزيد من المحادثات"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"عرض المزيد من الرسائل"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"تم حذف المحادثة"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"تم حذف المحادثة. المس لعرض محادثة مختلفة في تطبيق المراسلة"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"تم الحظر"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"تم إلغاء الحظر"</string>
+ <string name="db_full" msgid="8459265782521418031">"مساحة التخزين منخفضة. ربما يتم فقد بعض البيانات."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"تحديد المرفقات"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"تأكيد التحديد"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"تم تحديد <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"يُرجى إزالة مرفق أو أكثر وإعادة المحاولة."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"يمكنك محاولة إرسال الرسالة، ولكن ربما لا يتم تسليمها ما لم تتم إزالة مرفق أو أكثر من هذه الرسالة."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"لا يمكنك إرسال أكثر من مقطع فيديو واحد لكل رسالة. يُرجى إزالة مقاطع الفيديو الإضافية وإعادة المحاولة."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"أخفق تطبيق المراسلة في تحميل المرفق."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"إرسال على أية حال"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"لا يمكن بدء المحادثة"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"تم اختيار <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-az-rAZ/arrays.xml b/res/values-az-rAZ/arrays.xml
new file mode 100644
index 0000000..f4b43e4
--- /dev/null
+++ b/res/values-az-rAZ/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"mövzu yoxdur"</item>
+ <item msgid="272485471009191934">"mövzusuz"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Hə"</item>
+ <item msgid="6049132459802288033">"Yox"</item>
+ <item msgid="3084376867445867895">"Ok"</item>
+ <item msgid="3155097332660174689">"Heh"</item>
+ <item msgid="2611328818571146775">"Çox sağ olun"</item>
+ <item msgid="4881335087096496747">"Razıyam"</item>
+ <item msgid="2422296858597420738">"Nizza"</item>
+ <item msgid="4805581752819452687">"Yolumda"</item>
+ <item msgid="4746700499431366214">"Ok, Sizinlə sonra əlaqə saxlayacam"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
new file mode 100644
index 0000000..0bb8ef8
--- /dev/null
+++ b/res/values-az-rAZ/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mesajlaşma"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mesajlaşma"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Söhbəti seç"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Ayarlar"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Mesaj göndər"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Qoşma əlavə edin"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Yardım"</string>
+ <string name="welcome" msgid="2857560951820802321">"Xoş gəlmisiniz"</string>
+ <string name="skip" msgid="7238879696319945853">"Ötürün"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Sonrakı &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Növbəti"</string>
+ <string name="exit" msgid="1905187380359981199">"Çıxış"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"&amp;gt ayarlar;"</string>
+ <string name="settings" msgid="1045695083932447399">"Ayarlar"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mesajlaşma SMS, Telefon və Kontaktlara icazə istəyir."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"İcazələri Ayarlar &gt; Tətbiqlər &gt; Mesajlaşma &gt; İcazələr bölməsindən dəyişə bilərsiniz."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"İcazələri Ayarlar, Tətbiqlər, Mesajlaşma, İcazələr bölməsindən dəyişə bilərsiniz."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Tez-tez olanlar"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Bütün kontaktlar"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> ünvanına göndərin"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Şəkilləri və ya videonu çəkin"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Bu cihazdan şəkillər seçin"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Səs yazın"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Foto seçin"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Media seçildi."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Media seçilmədi."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> seçilib"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"təsvir <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" msgid="8142175029053370662">"təsvir"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Səs yazın"</string>
+ <string name="action_share" msgid="2143483844803153871">"Paylaş"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"İndicə"</string>
+ <string name="posted_now" msgid="867560789350406701">"İndi"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dəq</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dəq</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> saat</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> saat</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> gün</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> gün</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> həftə</item>
+ <item quantity="one">bir həftə</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ay</item>
+ <item quantity="one">bir ay</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> il</item>
+ <item quantity="one">bir il</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Klass 0 mesaj"</string>
+ <string name="save" msgid="5081141452059463572">"Yadda saxlayın"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Cihazda boş yer azdır. Yer boşaltmaq üçün Mesajlaşma köhnə mesajları avtomatik siləcək."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Yaddaş ehtiyatı qurtarır"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Mesajlaşma cihazınızda kifayət qədər yaddaş olmayana kimi mesaj göndərə və qəbul etməyə bilər."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS yaddaşı azdır. Mesajları silmə ehtiyacı hiss edə bilərsiniz."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Telefon nömrənizi təsdiq edin"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Bu bir addımlı mərhələ Mesajlaşmanın qrup mesajlarınızı düzgün şəkildə çatdıracağını təmin edəcək."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefon nömrəsi"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Media ilə olan bütün mesajları silin"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> müddətindən əvvəl köhnə mesajları silin"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> müddətindən köhnə mesajlar avtomatik silinsin"</string>
+ <string name="ignore" msgid="7063392681130898793">"İqnor edin"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Bütün mesajlar media ilə birlikdə silinsin?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> müddətindən köhnə mesajlar silinsin?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> müddətindən köhnə mesajlar avtomatik silinsin və avtomatik silinmə aktiv olsun?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> bildirib"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Bildirdiniz"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> tərəfindən mesaj:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Mesajı göndərdiniz"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Göndərilir..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Göndərilmədi. Yenidən cəhd etmək üçün toxunun."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Göndərilmədi. Yenidən cəhd edilir..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Resend or delete"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Lütfən, təcili yardım xidmətlərinə səsli zəng edin. Mətn mesajınız bu vaxt çatdırıla bilməz."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Alınmadı"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Yükləmək üçün yeni MMS mesajı"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Yeni MMS mesajı"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Yüklənmədi"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Yenidən cəhd etmək üçün toxun"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Yükləmək üçün toxun"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Yüklə və ya sil"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Yüklənir..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Mesajın vaxtı bitib və ya mövcud deyil"</string>
+ <string name="mms_info" msgid="3402311750134118165">"size: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Göndərə bilmir. Alıcı etibarsızdır."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Xidmət şəbəkədə aktiv deyil"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Şəbəkə probleminə görə göndərmək olmadı"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Mesajın vaxtı keçib və ya əlçatmazdır"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Mövzusuz)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Naməlum göndərən"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Çatdırılıb"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="SUBJECT">%1$s</xliff:g> mesajı <xliff:g id="FROM">%2$s</xliff:g> bölməsindən endirilə bilmir."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Az yaddaş ilə əlaqədar olaraq verilənlər bazası əməliyyatı tamamlana bilmədi"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mesaj göndərilmədi"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Bəzi mesajlar Mesajlaşmada göndərilməyib"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> söhbətdə <xliff:g id="MESSAGES_1">%d</xliff:g> mesaj</item>
+ <item quantity="one">bir söhbətdə <xliff:g id="MESSAGES_0">%d</xliff:g> mesaj</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mesaj endirilmədi"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Bəzi mesajlar Mesajlaşmada endirilməyib"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> söhbətdə <xliff:g id="MESSAGES_1">%d</xliff:g> mesaj</item>
+ <item quantity="one">bir söhbətdə <xliff:g id="MESSAGES_0">%d</xliff:g> mesaj</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> nömrəsinə mesaj göndərilmədi"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Lütfən, təcili yardım xidmətlərinə səsli zəng edin. Mətn mesajınız <xliff:g id="NUMBER">%1$s</xliff:g> nömrəsinə bu vaxt çatdırıla bilməz."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> yeni mesaj</item>
+ <item quantity="one">Yeni mesaj</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Başla"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera əlçatmazdır"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera əlçatmazdır"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video çəkiliş əlçatmazdır"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Medianı yadda saxlamaq olmur"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Şəkil çəkmək mümkün deyil"</string>
+ <string name="back" msgid="1477626055115561645">"Geri"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arxivləşmiş"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Sil"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arxiv"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Arxivdən çıxarın"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Bildirişləri deaktiv edin"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Bildirişləri aktivləşdirin"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Kontakt əlavə edin"</string>
+ <string name="action_download" msgid="7786338136368564146">"Endirin"</string>
+ <string name="action_send" msgid="377635240181672039">"Göndər"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Sil"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Bu mesaj silinsin?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Bu əməliyyat geri qaytarıla bilməz."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Sil"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Bu söhbətlər silinsin?</item>
+ <item quantity="one">Bu söhbət silinsin?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Sil"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Ləğv et"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Kimə"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Çox şəkil seçin"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Seçimi təsdiq edin"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Audionu qeydə almaq olmur. Yenidən cəhd edin."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Audionu oxutmaq olmur. Yenidən cəhd edin."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Audionu saxlamaq olmadı. Yenidən cəhd edin."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Toxunun və saxlayın"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Şəkil"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio klip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Videolar"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontakt kartı"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Endirin"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS ilə cavab verin"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS vasitəsilə cavab verin"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Cavabla"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> iştirakçı</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> iştirakçı</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Mən"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt bloklandı və arxivləndi"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt blokdan və arxivdən çıxarıldı"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> arxivlənib"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> arxivdən çıxarıldı"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Bildirişlər deaktiv edildi"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Bildirişlər aktiv edildi"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Hamısı hazırdır. Göndərmək üçün toxunun."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Mesajlaşma uğurla defolt SMS tətbiqi seçildi."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Qoşmaları ləğv edin</item>
+ <item quantity="one">Qoşmanı ləğv edin</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audio qoşma"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Audio qoşmanı ifa et"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pauza"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> adlı göndərəndən mesaj: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> adlı göndərənin mesajı getmədi: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> adlı göndərən tərəfindən mesaj: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> adlı əlaqəyə göndərilməmiş mesaj: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> adlı əlaqəyə mesaj göndərilir: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> adlı əlaqəyə mesaj göndərilmədi: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> adlı əlaqəyə mesaj: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> adlı göndərənin mesajı getmədi: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> adlı göndərən tərəfindən mesaj: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> istifadəçisinə göndərilməmiş mesaj: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> istifadəçisinə mesaj göndərilir: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> istifadəçisinə mesaj göndərilmədi: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> istifadəçisinə mesaj: <xliff:g id="MESSAGE">%s</xliff:g>. Vaxt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mesaj göndərilmədi. Təkrar cəhd etmək üçün toxunun."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> ilə söhbət"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Mövzunu silin"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Video çəkin"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Durğun şəkli çəkin"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Şəkil çəkin"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Video çəkilişə başlayın"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Tam ekranlı kameraya keçin"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Ön və arxa kameralar arasında seçim edin"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Çəkilişi dayandırın və videonu qoşun"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Video qeydi dayandırın"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto mesajlaşma"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foto \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albomunda yadda saxlanıldı</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albomunda yadda saxlanıldı</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albomunda yadda saxlanıldı</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albomunda yadda saxlanıldı</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> qoşma \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albomunda yadda saxlanıldı</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> qoşma \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albomunda yadda saxlanıldı</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> qoşma \"Endirilənlər\" qovluğunda yadda saxlanıldı</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> qoşma \"Endirilənlər\" qovluğunda yadda saxlanıldı</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> qoşma yadda saxlanıldı</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> qoşma yadda saxlanıldı</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> qoşma yadda saxlanıla bilmədi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> qoşma yadda saxlanıla bilmədi</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS qoşması yadda saxlandı"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Ayarlar"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arxivləşmiş"</string>
+ <string name="action_close" msgid="1840519376200478419">"Bağla"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Qabaqcıl"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Sazlama"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Bildirişlər"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Səs"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Səssiz"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Titrət"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloklanmış"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS çatdırma xətaları"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Hər göndərdiyiniz SMS üçün çatdırılma raportu tələb edin"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Avtomatik əldə edin"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS mesajları avtomatik əldə edin"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Rouminq zamanı avtomatik əldə edin"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Rominq zamanı MMS-i avtomatim bərpa edin"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Qrup mesajlaşması"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Bir neçə alıcının olduğu halda bir mesaj göndərmək üçün MMS istifadə edin"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Defolt SMS proqramı"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Defolt SMS proqramı"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Telefon nömrəniz"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Naməlum"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Gedən mesaj səsləri"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Damp SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Damp xarici saxlanma faylına emal edilməmiş SMS datası qəbul etdi"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Damp MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Damp xarici saxlanma faylına emal edilməmiş MMS datası qəbul etdi"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Simsiz siqnallar"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Mesaj seçimləri"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Mətni kopyalayın"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Detallara baxın"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Sil"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Yönləndir"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Mesaj detalları"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Növ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mətn mesajı"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedia mesajı"</string>
+ <string name="from_label" msgid="1947831848146564875">"Kimdən: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kimə: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Göndərilən: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Qəbul edilən: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Mövzu: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Ölçü: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Yüksək"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Aşağı"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Gizli göndərici ünvanı"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Qoşmalar yüklənən zaman mesaj göndərmək olmur."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Qoşma yüklənə bilmir. Yenidən cəhd edin."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Şəbəkə hazır deyil. Yenidən cəhd edin."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Mətni silin"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Mətn və nömrə daxiletməsi arasında seçim edin"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"İştirakçı əlavə edin"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"İştirakçıları təsdiq edin"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Yeni söhbətə başlayın"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Bu elementi seçin"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Videonu oxut"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Adamlar və seçimlər"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Sazlama"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Adamlar və seçimlər"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Ümumi"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Bu söhbətdəki adamlar"</string>
+ <string name="action_call" msgid="6596167921517350362">"Zəng edin"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Mesaj göndərin"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Mesaj göndərin&lt;br/&gt;&lt;kiçik&gt;buradan: <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/kiçik&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Fotolar göndərin</item>
+ <item quantity="one">Foto göndərin</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Audioları göndərin</item>
+ <item quantity="one">Audio göndərin</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Videoları göndərin</item>
+ <item quantity="one">Video göndərin</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Kontakt kartlarını göndərin</item>
+ <item quantity="one">Kontakt kartını göndərin</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Qoşmaları göndərin</item>
+ <item quantity="one">Qoşma göndərin</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> qoşma göndərilmə üçün hazırdır</item>
+ <item quantity="one">1 qoşma göndərilmə üçün hazırdır</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Geri əlaqə göndərin"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store\'də bax"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versiya haqqında"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versiya %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Açıq mənbə lisenziyaları"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Bildirişlər"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Qoşma limitini keçdi"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Qoşmanı yükləyə bilmədi."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Kontaktlara əlavə edilsin?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Kontakt əlavə edin"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Mövzu"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Mövzu: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kontakt kartı yüklənir"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kontakt kartını yükləmək alınmadı"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Kontakt kartına baxın"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakt</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontakt kartları"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Doğum günü"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Qeydlər"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Mesajı yönləndirin"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Cavablayın"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS deaktivdir"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Göndərmək üçün Mesajlaşmanı defolt SMS tətbiqi kimi ayarlayın"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Mesajlaşmanı defolt SMS tətbiqi kimi ayarlayın"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Dəyişin"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Mesaj qəbul etmək üçün Mesajlaşmanı defolt SMS tətbiqi kimi ayarlayın"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS göndərmək üçün tərcih olunan SIM seçilməyib"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Bu tətbiqə cihaz sahibi tərəfindən icazə verilmir."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Ok"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Söhbətdə həddən çox iştirakçı var"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Yanlış kontakt</item>
+ <item quantity="one">Yanlış kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kamera şəklini yükləmək olmur"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Siz: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Qaralama"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Söhbətə başlayan kimi onu burada siyahılanmış görəcəksiniz"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arxivlənmiş söhbətlər burada görünür"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Söhbətlər yüklənir…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Şəkil"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio klip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Əlaqə kartı"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Geri"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Yenidən cəhd edin"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Yeni mesaja başlamaq üçün kontakt adını və ya telefon nömrəsini daxil edin"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blok edin"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> bloklayın"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> blokdan çıxarın"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> bloklansın?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Bu nömrədən mesaj almağa davam edəcəksiniz, lakin mesajın gəlməsinə dair bildiriş olmayacaq. Söhbət arxivlənəcək."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Bloklanmış kontaktlar"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"BLOKDAN ÇIXARIN"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Bloklanmış kontaktlar"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Sənəd kitabxanasından şəkil seçin"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Mesaj göndərilir"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mesaj göndərildi"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobil data deaktivdir. Ayarları yoxlayın."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Təyyarə rejimində mesaj göndərmək olmur"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Mesaj göndərilə bilmədi"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mesaj yükləndi"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobil data deaktivdir. Ayarları yoxlayın."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Təyyarrə rejimində mesajları endirə bilməz"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Mesaj endirilə bilmədi"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Sıfır"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Bir"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"İki"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Üç"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Dörd"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Beş"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Altı"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Yeddi"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Səkkiz"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Doqquz"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Mesajı <xliff:g id="CARRIERNAME">%1$s</xliff:g> ilə göndərmək olmur, xəta <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Mesajı naməlum operator ilə göndərmək olmur, xəta <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Yön: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mesaj göndərilmədi. Xidmət şəbəkədə aktivləşdirilməyib."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mesaj göndərilmədi: Etibarsız təyinat ünvanı"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mesaj göndərilmədi: etibarsız mesaj"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mesaj göndərilmədi: dəstəklənməyən məzmun"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mesaj göndərilmədi: dəstəklənməyən mesaj"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mesaj göndərilmədi: çox böyükdür"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Yeni mesaj"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Göstər"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Təsvir"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Uyğun tətbiqi tapmaq olmadı"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Qəbuledicini silin"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Yeni mesaj"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Ləğv et"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edit access point"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ayarlanmayıb"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Ad"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proksi"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS por"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN növü"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN silin"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Yeni APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Yadda saxlayın"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"İmtina edin"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Ad sahəsi boş ola bilməz."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN boş ola bilməz."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC sahəsində 3 rəqəm olmalıdır."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC sahəsi 2 və ya 3 rəqəm olmalıdır."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Defolt APN ayarları bərpa olunur."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Sıfırlayın"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Varsayılan APN ayarlarının sıfırlanması tamamlandı."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Başlıqsız"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Çatma Nöqtəsi Adları"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Yeni APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Giriş Nöqtəsi Ad Ayarları bu istifadəçi üçün əlçatmazdır"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Panoya kopyalansın?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopyalayın"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"Buraya: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Ümumi"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Qabaqcıl"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Ümumi ayarlar"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Qabaqcıl ayarlar"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Bütün alıcılara fərdi SMS göndər. Hər hansı bir cavabı özünüz olacaq"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Bütün alıcılara bir MMS göndər"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Naməlum nömrə"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Yeni mesaj"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Yeni mesaj."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM seçicisi"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> seçildi, SIM seçicisi"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Mövzunu dəyiş"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SİM seçin və ya mövzu redaktə edin"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Səs yazmaq üçün basın və saxlayın"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Yeni söhbətə başla"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mesajlaşma"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Mesajlaşma Siyahısı"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mesajlaşma"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Yeni mesaj"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Söhbət siyahısı"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Söhbətlər yüklənir"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Mesajlar yüklənir"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Daha çox söhbət görüntülə"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Daha çox mesaja bax"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Söhbət silinib"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Söhbət silindi. Başqa Mesajlaşma söhbəti göstərmək üçün toxunun"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloklanıb"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Blokdan çıxarılıb"</string>
+ <string name="db_full" msgid="8459265782521418031">"Yaddaş yeri azdır. Bəzi məlumatlar itə bilər."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Qoşmaları seçin"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Seçimi təsdiq edin"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> seçilib"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Lütfən, bir və ya daha çox qoşmanı silin və yenidən cəhd edin."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Mesajınızı göndərməyə cəhd edə bilərsiniz, lakin bir və ya daha çox qoşmanı silmədiyiniz halda, mesaj çatmaya bilər."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Hər mesaja yalnız bir video yükləyə bilərsiniz. Əlavə videoları silin və yenidən cəhd edin."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Mesajlaşma qoşmanı yükləyə bilmədi."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"İstənilən halda göndərin"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Söhbətə başlamaq olmadı"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> seçilmiş"</string>
+</resources>
diff --git a/res/values-bg/arrays.xml b/res/values-bg/arrays.xml
new file mode 100644
index 0000000..ac036c4
--- /dev/null
+++ b/res/values-bg/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"без тема"</item>
+ <item msgid="272485471009191934">"няма тема"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Да"</item>
+ <item msgid="6049132459802288033">"Не"</item>
+ <item msgid="3084376867445867895">"ОK"</item>
+ <item msgid="3155097332660174689">"Ха-ха"</item>
+ <item msgid="2611328818571146775">"Благодаря"</item>
+ <item msgid="4881335087096496747">"Приемам"</item>
+ <item msgid="2422296858597420738">"Чудесно"</item>
+ <item msgid="4805581752819452687">"Идвам"</item>
+ <item msgid="4746700499431366214">"OK, ще се свържа с теб по-късно"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..5251fa8
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Съобщения"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Съобщения"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Избиране на разговор"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Настройки"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Изпращане на съобщение"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Добавяне на прикачен файл"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Помощ"</string>
+ <string name="welcome" msgid="2857560951820802321">"Добре дошли"</string>
+ <string name="skip" msgid="7238879696319945853">"Пропускане"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Напред &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Напред"</string>
+ <string name="exit" msgid="1905187380359981199">"Изход"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Настройки &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Настройки"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Приложението Съобщения се нуждае от разрешение за SMS, телефон и контакти."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Можете да промените разрешенията от „Настройки“ &gt; „Приложения“ &gt; „Съобщения“ &gt; „Разрешения“."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Можете да промените разрешенията от „Настройки“, „Приложения“, „Съобщения“, „Разрешения“."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Често търсени"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Всички контакти"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Изпращане до <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Правете снимки или видеоклипове"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Избиране на изображения от това устройство"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Запишете звук"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Избиране на снимка"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Мултимедийният файл е избран."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Изборът на мултимедиен файл е премахнат."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Избрахте <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"изображение от <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" msgid="8142175029053370662">"изображение"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Запис на звук"</string>
+ <string name="action_share" msgid="2143483844803153871">"Споделяне"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Току-що"</string>
+ <string name="posted_now" msgid="867560789350406701">"Сега"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> мин</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> мин</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> часа</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> час</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> дни</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ден</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> седмици</item>
+ <item quantity="one">една седмица</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> месеца</item>
+ <item quantity="one">един месец</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> години</item>
+ <item quantity="one">една година</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Съобщения от клас 0"</string>
+ <string name="save" msgid="5081141452059463572">"Запазване"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Мястото на устройството не е достатъчно. Приложението Съобщения автоматично ще изтрие по-старите кореспонденции, за да освободи място."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Мястото в хранилището е на изчерпване"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Възможно е Съобщения да не изпраща, нито да получава съобщения, докато на устройството не се освободи повече място."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Мястото в хранилището за SMS намалява. Може да се наложи да изтриете съобщения."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Потвърждаване на телефонния ви номер"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Тази еднократна стъпка ще гарантира, че Съобщения ще доставя правилно груповите ви съобщения."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Телефонен номер"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Изтриване на всички съобщения с мултимедия"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Изтриване на съобщенията, по-стари от <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Автоматично изтриване на съобщенията, по-стари от <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Пренебрегване"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Да се изтрият ли всички съобщения с медийни файлове?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Да се изтрият ли съобщенията, по-стари от <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Да се изтрият ли съобщенията, по-стари от <xliff:g id="DURATION">%s</xliff:g>, и да се включи ли функцията за автоматично изтриване?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> каза"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Казахте"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Съобщение от <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Изпратихте съобщение"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Изпраща се…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Не е изпратено. Докоснете за нов опит."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Не е изпратено. Извършва се нов опит…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Повторно изпращане или изтриване"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Моля, осъществете гласово обаждане до спешните служби. Понастоящем текстовото ви съобщение не можа да бъде доставено."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Не бе успешно"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Ново MMS съобщение за изтегляне"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Ново MMS съобщение"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Не можа да се изтегли"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Докоснете за нов опит"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Докоснете за изтегляне"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Изтегляне или изтриване"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Изтегля се…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Съобщението е с изтекла валидност или не е налице"</string>
+ <string name="mms_info" msgid="3402311750134118165">"размер: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, валидност: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Не може да се изпрати. Получателят не е валиден."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Услугата не е активирана за мрежата"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Не можа да се изпрати поради проблем с мрежата"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Съобщението е с изтекла валидност или не е налице"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Без тема)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Неизвестен подател"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Доставено"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Съобщението „<xliff:g id="SUBJECT">%1$s</xliff:g>“ от <xliff:g id="FROM">%2$s</xliff:g> не можа да се изтегли."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Операцията в базата от данни не можа да завърши поради недостиг на памет"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Съобщението не е изпратено"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Някои съобщения в приложението Съобщения не са изпратени"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> съобщения в <xliff:g id="CONVERSATIONS">%d</xliff:g> разговора</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> съобщения в един разговор</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Съобщението не е изтеглено"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Някои съобщения в приложението Съобщения не са изтеглени"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> съобщения в <xliff:g id="CONVERSATIONS">%d</xliff:g> разговора</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> съобщения в един разговор</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Съобщението до <xliff:g id="NUMBER">%1$s</xliff:g> не е изпратено"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Моля, осъществете гласово обаждане до спешните служби. Понастоящем текстовото ви съобщение до <xliff:g id="NUMBER">%1$s</xliff:g> не можа да бъде доставено."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> нови съобщения</item>
+ <item quantity="one">Ново съобщение</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Стартиране"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Няма достъп до камерата"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Няма достъп до камерата"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Функцията за заснемане на видео не е налице"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Медийният файл не може да се запази"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Не може да се направи снимка"</string>
+ <string name="back" msgid="1477626055115561645">"Назад"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архивирани"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Изтриване"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архивиране"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Деархивиране"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Изключване на известията"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Включване на известията"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Добавяне на контакт"</string>
+ <string name="action_download" msgid="7786338136368564146">"Изтегляне"</string>
+ <string name="action_send" msgid="377635240181672039">"Изпращане"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Изтриване"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Да се изтрие ли това съобщение?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Това действие не може да се отмени."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Изтриване"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Да се изтрият ли тези разговори?</item>
+ <item quantity="one">Да се изтрие ли този разговор?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Изтриване"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Отказ"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"До"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Избиране на няколко изображения"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Потвърждаване на избора"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Аудиото не може да се запише. Опитайте отново."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Аудиото не може да се възпроизведе. Опитайте отново."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Аудиото не можа да се запази. Опитайте отново."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Докоснете и задръжте"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" ,"</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" :"</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Снимка"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудиоклип"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Видеоклип"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Визитна картичка"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Изтегляне"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Отговор чрез SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Отговор чрез MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Отговор"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> участници</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> участник</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Аз"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Контактът е блокиран и архивиран"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Контактът е отблокиран и деархивиран"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Архивирахте <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Деархивирахте <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Известията са изключени"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Известията са включени"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Готово. Докоснете „Изпращане“ отново."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Успешно зададохте Съобщения като стандартно приложение за SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Отхвърляне на прикачените файлове</item>
+ <item quantity="one">Отхвърляне на прикачения файл</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Прикачен аудиофайл"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Пускане на прикачения аудиофайл"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Пауза"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Съобщение от <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Неуспешно съобщение от <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Съобщение от <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Неизпратено съобщение до <xliff:g id="CONTACT">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Изпраща се съобщение до <xliff:g id="CONTACT">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Неуспешно съобщение до <xliff:g id="CONTACT">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Съобщение до <xliff:g id="CONTACT">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Неуспешно съобщение от <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Съобщение от <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Неизпратено съобщение до <xliff:g id="GROUP">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Изпраща се съобщение до <xliff:g id="GROUP">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Неуспешно съобщение до <xliff:g id="GROUP">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Съобщение до <xliff:g id="GROUP">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Неуспешно съобщение. Докоснете, за да опитате отново."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Разговор с/ъс <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Изтриване на темата"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Заснемане на видеоклип"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Заснемане на кадър"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Правене на снимка"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Стартиране на записване на видеоклип"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Превключване към камера на цял екран"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Превключване между предната и задната камера"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Спиране на записването и прикачване на видеоклипа"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Спиране на видеозаписа"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Снимки в Съобщения"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> снимки са запазени в албума „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> снимка е запазена в албума „<xliff:g id="ALBUMNAME_1">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> видеоклипа са запазени в албума „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> видеоклип е запазен в албума „<xliff:g id="ALBUMNAME_1">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> прикачени файла са запазени в албума „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> прикачен файл е запазен в албума „<xliff:g id="ALBUMNAME_1">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прикачени файла са запазени в „Изтегляния“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> прикачен файл е запазен в „Изтегляния“</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прикачени файла са запазени</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> прикачен файл е запазен</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прикачени файла не можаха да бъдат запазени</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> прикачен файл не можа да се запази</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Запазен прикачен файл от MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Настройки"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архивирани"</string>
+ <string name="action_close" msgid="1840519376200478419">"Затваряне"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Разширени"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Отстраняване на грешки"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Известия"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Звук"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Тих режим"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Вибриране"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Блокирано"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Отчети за получаване на SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Заявяване на отчет за доставка за всеки изпратен от вас SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Автоматично извличане"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Автоматично извличане на мултимедийните съобщения (MMS)"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Автоматично извличане при роуминг"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Автоматично извличане на MMS при роуминг"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Групови съобщения"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Използвайте MMS, за да изпратите едно съобщение, когато има няколко получатели"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Стандартно приложение за SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Стандартно приложение за SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Вашият телефонен номер"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Няма информация"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Звуци за изходящи съобщения"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Разтоварване на SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Разтоварване на необработените данни от получените SMS съобщения във файл във външното хранилище"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Разтоварване на MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Разтоварване на необработените данни от получените MMS съобщения във файл във външното хранилище"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Безжични сигнали"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Опции на съобщението"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Копиране на текста"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Преглед на подробностите"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Изтриване"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Напред"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Подробности за съобщението"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Тип: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Текстово съобщение"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Мултимедийно съобщение"</string>
+ <string name="from_label" msgid="1947831848146564875">"От: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"До: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Изпратено: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Получено: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Тема: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Размер: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Приоритет: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Висок"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Нормален"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Нисък"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Скрит адрес на изпращача"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Съобщението не може да се изпрати, докато прикачените файлове се зареждат."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Прикаченият файл не може да се зареди. Опитайте отново."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Мрежата не е готова. Опитайте отново."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Изтриване на текста"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Превключване между букви и цифри"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Добавяне на още участници"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Потвърждаване на участниците"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Начало на нова кореспонденция"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Избиране на този елемент"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Пускане на видеоклипа"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Хора и опции"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Отстраняване на грешки"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Хора и опции"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Общи настройки"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Хора в този разговор"</string>
+ <string name="action_call" msgid="6596167921517350362">"Обаждане по телефона"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Изпращане на съобщение"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Изпращане на съобщение&lt;br/&gt;&lt;small&gt;от <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Изпращане на снимките</item>
+ <item quantity="one">Изпращане на снимката</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Изпращане на аудиозаписите</item>
+ <item quantity="one">Изпращане на аудиозаписа</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Изпращане на видеоклиповете</item>
+ <item quantity="one">Изпращане на видеоклипа</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Изпращане на визитните картички</item>
+ <item quantity="one">Изпращане на визитната картичка</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Изпращане на прикачените файлове</item>
+ <item quantity="one">Изпращане на прикачения файл</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прикачени файла са готови за изпращане</item>
+ <item quantity="one">Един прикачен файл е готов за изпращане</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Изпращане на отзиви"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Преглед в Google Play Магазин"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Информация за версията"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Версия %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Лицензи за отворен код"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Известия"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Ограничението за прикачени файлове е достигнато"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Зареждането на прикачения файл не бе успешно."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Да се добави ли към контактите?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Добавяне на контакт"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Тема"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Тема: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"„<xliff:g id="SUBJECT_LABEL">%s</xliff:g>“ – „<xliff:g id="MESSAGETEXT">%s</xliff:g>“"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Визитната картичка се зарежда"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Визитната картичка не можа да се зареди"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Преглед на визитната картичка"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> контакта</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> контакт</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Визитни картички"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Рожден ден"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Бележки"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Препращане на съобщението"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Отговор"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS съобщенията са деактивирани"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"За да изпращате съобщения, задайте Съобщения като стандартно приложение за SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Задайте Съобщения като стандартно приложение за SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Промяна"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"За да получавате съобщения, задайте Съобщения като стандартно приложение за SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Няма избрана предпочитана SIM карта за изпращане на SMS съобщения"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Това приложение не е разрешено от собственика на устройството."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ОK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Твърде много участници в разговора"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Контактите са невалидни</item>
+ <item quantity="one">Контактът е невалиден</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Изображението от камерата не можа да се зареди"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Вие: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Чернова"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Когато започнете нов разговор, ще го видите посочен тук"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Тук се показват архивираните кореспонденции"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Кореспонденциите се зареждат…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Снимка"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудиоклип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Видеоклип"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Визитна картичка"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Отмяна"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Повторен опит"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Въведете име на контакт или телефонен номер, за да започнете ново съобщение"</string>
+ <string name="action_block" msgid="9032076625645190136">"Блокиране"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Блокиране на <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Отблокиране на <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Да се блокира ли <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Ще продължите да получавате съобщения от този номер, но вече няма да бъдете известявани. Кореспонденцията ще се архивира."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Блокирани контакти"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ОТБЛОКИРАНЕ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Блокирани контакти"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Избиране на изображение от библиотеката с документи"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Съобщението се изпраща"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Съобщението е изпратено"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Мобилните данни са изключени. Проверете настройките си."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Не можете да изпращате съобщения в самолетен режим"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Съобщението не можа да се изпрати"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Съобщението е изтеглено"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Мобилните данни са изключени. Проверете настройките си."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Съобщенията не могат да се изтеглят в самолетен режим"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Съобщението не можа да бъде изтеглено"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"нула"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"едно"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"две"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"три"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"четири"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"пет"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"шест"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"седем"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"осем"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"девет"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Съобщението не може да се изпрати с/ъс <xliff:g id="CARRIERNAME">%1$s</xliff:g> – грешка <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Съобщението не може да се изпрати с неизвестен оператор – грешка <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Препр.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Съобщението не е изпратено: Услугата не е активирана за мрежата"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Съобщението не е изпратено: невалиден целеви адрес"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Съобщението не е изпратено: невалидно съобщение"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Съобщението не е изпратено: неподдържано съдържание"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Съобщението не е изпратено: неподдържано съобщение"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Съобщението не е изпратено: твърде голямо"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Ново съобщение"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Преглед"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Изображение"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Не можа да бъде намерено подходящо приложение"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Премахване на получателя"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Ново съобщение"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Отказ"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Редактиране на точката за достъп"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Не е зададено"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Име"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Име на точката за достъп (APN)"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS прокси сървър"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS порт"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"Mобилен код на държава"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"Код на мобилна мрежа (MNC)"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Тип на името на точката за достъп (APN)"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Изтриване на името на точката за достъп (APN)"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Ново име на точката за достъп (APN)"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Запазване"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Отхвърляне"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Полето за име трябва да се попълни."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Името на точката за достъп (APN) трябва да се попълни."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Полето за мобилен код на държавата трябва да е 3 цифри."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Полето за код на мобилна мрежа (MNC) трябва да е с 2 или 3 цифри."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Стандартните настройки за името на точката за достъп (APN) се възстановяват."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Възстановяване на стандартните"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Възстановяването на стандартните настройки за името на точката за достъп (APN) завърши."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Без име"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Имена на точката за достъп"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Имена на точката за достъп (APN)"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Ново име на точката за достъп (APN)"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Настройките за име на точката за достъп не са налице за този потребител"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Да се копира ли в буферната памет?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Копиране"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"до „<xliff:g id="SIM_NAME">%s</xliff:g>“"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Общи настройки"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Разширени настройки"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Общи настройки"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Разширени настройки"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM карта „<xliff:g id="SIM_NAME">%s</xliff:g>“"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Изпращане на отделни SMS съобщения до всички участници. Само вие ще получавате отговорите"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Изпращане на един MMS до всички участници"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Неизвестен номер"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Ново съобщение"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Ново съобщение."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Инструмент за избор на SIM карта"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Избрахте „<xliff:g id="SIM_0">%1$s</xliff:g>“ – инструмент за избиране на SIM карти"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Редактиране на темата"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Избиране на SIM карта или редактиране на темата"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Докоснете и задръжте, за да запишете аудио"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Начало на нова кореспонденция"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Съобщения"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Списък със съобщения"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Съобщения"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Ново съобщение"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Списък с кореспонденции"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Кореспонденциите се зареждат"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Съобщенията се зареждат"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Преглед на още кореспонденции"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Преглед на още съобщения"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Кореспонденцията бе изтрита"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Разговорът е изтрит. Докоснете за показване на друг от Съобщения"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Блокирано"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Отблокирано"</string>
+ <string name="db_full" msgid="8459265782521418031">"Мястото в хранилището е малко. Може да изгубите някои данни."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Избиране на прикачени файлове"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Потвърждаване на избора"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Избрахте <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Моля, премахнете един или повече прикачени файлове и опитайте отново."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Можете да опитате да изпратите съобщението си, но е възможно то да не бъде доставено, ако не премахнете един или повече прикачени файлове."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Можете да изпращате само по един видеоклип с всяко съобщение. Моля, премахнете допълнителните клипове и опитайте отново."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Зареждането на прикачения файл от Съобщения не бе успешно."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Изпращане въпреки това"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Разговорът не можа да започне"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Избрахте <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-bn-rBD/arrays.xml b/res/values-bn-rBD/arrays.xml
new file mode 100644
index 0000000..bdf164c
--- /dev/null
+++ b/res/values-bn-rBD/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"কোনো বিষয় নেই"</item>
+ <item msgid="272485471009191934">"বিষয়হীন"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"হ্যাঁ"</item>
+ <item msgid="6049132459802288033">"না"</item>
+ <item msgid="3084376867445867895">"ঠিক আছে"</item>
+ <item msgid="3155097332660174689">"হিহি"</item>
+ <item msgid="2611328818571146775">"ধন্যবাদ"</item>
+ <item msgid="4881335087096496747">"আমি রাজি"</item>
+ <item msgid="2422296858597420738">"সুন্দর"</item>
+ <item msgid="4805581752819452687">"রাস্তায় আছি"</item>
+ <item msgid="4746700499431366214">"ঠিক আছে, পরে আপনার সঙ্গে এই নিয়ে কথা বলছি"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
new file mode 100644
index 0000000..8c58185
--- /dev/null
+++ b/res/values-bn-rBD/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"বার্তাপ্রেরণ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"বার্তাপ্রেরণ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"কথোপকথন নির্বাচন করুন"</string>
+ <string name="action_settings" msgid="1329008122345201684">"সেটিংস"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"বার্তা পাঠান"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"একটি সংযুক্তি যোগ করুন"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"সহায়তা"</string>
+ <string name="welcome" msgid="2857560951820802321">"স্বাগতম"</string>
+ <string name="skip" msgid="7238879696319945853">"এড়িয়ে যান"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"পরবর্তী &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"পরবর্তী"</string>
+ <string name="exit" msgid="1905187380359981199">"প্রস্থান করুন"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"সেটিংস &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"সেটিংস"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"SMS, ফোন এবং পরিচিতিগুলিতে বার্তাপ্রেরণ -এর অনুমতির প্রয়োজন।"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"আপনি সেটিংস &gt; অ্যাপ্লিকেশানগুলি &gt; বার্তাপ্রেরণ &gt; অনুমতিগুলি এ অনুমতিগুলি পরিবর্তন করতে পারেন৷"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"আপনি সেটিংস, অ্যাপ্লিকেশানগুলি, বার্তাপ্রেরণ, অনুমতিগুলি এ অনুমতিগুলি পরিবর্তন করতে পারেন৷"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"প্রায়শই ব্যবহৃত"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"সকল পরিচিতি"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> এ পাঠান"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ছবি বা ভিডিও ক্যাপচার করুন"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"এই ডিভাইস থেকে চিত্র নির্বাচন করুন"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"অডিও রেকর্ড করুন"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ফটো চয়ন করুন"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"মিডিয়া নির্বাচিত হয়েছে।"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"মিডিয়া নির্বাচিত হয় নি।"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g>টি নির্বাচন করা হয়েছে"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"চিত্র <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" msgid="8142175029053370662">"চিত্র"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"অডিও রেকর্ড করুন"</string>
+ <string name="action_share" msgid="2143483844803153871">"ভাগ করুন"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"এইমাত্র"</string>
+ <string name="posted_now" msgid="867560789350406701">"এখনই"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> মিনিট</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> মিনিট</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ঘণ্টা</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ঘণ্টা</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> দিন</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> দিন</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> সপ্তাহ</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> সপ্তাহ</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> মাস</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> মাস</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> বছর</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> বছর</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"ক্লাস ০ বার্তা"</string>
+ <string name="save" msgid="5081141452059463572">"সংরক্ষণ করুন"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ডিভাইসে কম জায়গা আছে। বার্তাপ্রেরণ জায়গা খালি করতে স্বয়ংক্রিয়ভাবে পুরানো পাঠ্য বার্তাগুলি মুছে ফেলবে।"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"সঞ্চয়স্থান পূর্ণ হতে চলেছে"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"আপনার ফোনে অরো বেশি সঞ্চয়স্থান উপলব্ধ না থাকলে, বার্তাপ্রেরণ বার্তাগুলি পাঠাতে বা গ্রহণ করতে নাও পারে৷"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS সংগ্রহস্থল বেশি নেই৷ আপনাকে বার্তা মুছতে হতে পারে৷"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"আপনার ফোন নম্বর নিশ্চিত করুন"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"এই এককালীন ধাপটি নিশ্চিত করে যে বার্তাপ্রেরণ গোষ্ঠী বার্তাগুলিকে সঠিকভাবে বিতরণ করবে৷"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ফোন নম্বর"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"মিডিয়ার সাথে সমস্ত বার্তা মুছুন"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> এর থেকে পুরানো বার্তাগুলি মুছুন"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g>-এর থেকে পুরানো বার্তা স্বংয়ক্রিয়ভাবে মুছুন"</string>
+ <string name="ignore" msgid="7063392681130898793">"উপেক্ষা করুন"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"মিডিয়ার সমন্বিত সমস্ত বার্তা মুছুন"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g>-এর থেকে পুরানো বার্তাগুলি মুছবেন?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g>-এর থেকে পুরানো বার্তা মুছে দিন এবং স্বয়ংক্রিয়ভাবে মোছা চালু করবেন?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> বলেছেন"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"আপনি বলেছেন"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> এর থেকে বার্তা"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"আপনি একটি বার্তা পাঠিয়েছেন"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"পাঠানো হচ্ছে..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"পাঠানো হয়নি। আবার চেষ্টা করতে স্পর্শ করুন।"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"পাঠানো হয়নি। আবার চেষ্টা হচ্ছে…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"আবার পাঠান অথবা মুছে ফেলুন"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"অনুগ্রহ করে জরুরি পরিষেবাগুলিতে একটি ভয়েস কল করুন৷ আপনার পাঠ্য বার্তাগুলিকে এই মূহুর্তে বিতরণ করা যাবে না৷"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"ব্যর্থ হয়েছে"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ডাউনলোড করার জন্য নতুন MMS বার্তা"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"নতুন MMS বার্তা"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ডাউনলোড করা যায়নি"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"আবার চেষ্টা করতে স্পর্শ করুন"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ডাউনলোড করার জন্য স্পর্শ করুন"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ডাউনলোড করুন অথবা মুছুন"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ডাউনলোড হচ্ছে..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"বার্তার মেয়াদ শেষ হয়ে গেছে বা অনুপলব্ধ"</string>
+ <string name="mms_info" msgid="3402311750134118165">"আকার: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, মেয়াদ শেষ: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"পাঠানো যাবে না। প্রাপক বৈধ নয়।"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"নেটওয়ার্কে পরিষেবা সক্রিয় নেই"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"নেটওয়ার্ক সমস্যার কারণে পাঠানো যায়নি"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"বার্তার মেয়াদ শেষ হয়ে গেছে বা অনুপলব্ধ"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(কোনো বিষয় নেই)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"অজ্ঞাত প্রেরক"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"পৌঁছে দেওয়া হয়েছে"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> থেকে <xliff:g id="SUBJECT">%1$s</xliff:g> বার্তা ডাউনলোড করা যায়নি।"</string>
+ <string name="low_memory" msgid="5300743415198486619">"কম মেমরি থাকার কারণে ডেটাবেস ক্রিয়াকলাপ সম্পন্ন করা যায়নি"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"বার্তা পাঠানো হয়নি"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"বার্তাপ্রেরণ এ কিছু বার্তা পাঠানো হয়নি"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g>টি কথোপকথনে <xliff:g id="MESSAGES_1">%d</xliff:g>টি বার্তা রয়েছে</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g>টি কথোপকথনে <xliff:g id="MESSAGES_1">%d</xliff:g>টি বার্তা রয়েছে</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"বার্তা ডাউনলোড করা যাবে না"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"বার্তাপ্রেরণ এ কিছু বার্তা ডাউনলোড করা যায়নি"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g>টি কথোপকথনে <xliff:g id="MESSAGES_1">%d</xliff:g>টি বার্তা রয়েছে</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g>টি কথোপকথনে <xliff:g id="MESSAGES_1">%d</xliff:g>টি বার্তা রয়েছে</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> এ বার্তা পাঠানো হয়নি"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"অনুগ্রহ করে জরুরি পরিষেবাগুলিতে একটি ভয়েস কল করুন৷ <xliff:g id="NUMBER">%1$s</xliff:g> এ পাঠানো আপনার পাঠ্য বার্তাগুলিকে এই মূহুর্তে বিতরণ করা যাবে না৷"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g>টি নতুন বার্তা</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g>টি নতুন বার্তা</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"আরম্ভ"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ক্যামেরা উপলব্ধ নয়"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ক্যামেরা উপলব্ধ নয়"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ভিডিও ক্যাপচার উপলব্ধ নয়"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"মিডিয়া সংরক্ষণ করা যাবে না"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ছবি তুলতে পারবেন না"</string>
+ <string name="back" msgid="1477626055115561645">"ফিরুন"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"সংরক্ষণাগারভুক্ত"</string>
+ <string name="action_delete" msgid="4076795795307486019">"মুছুন"</string>
+ <string name="action_archive" msgid="5437034800324083170">"সংরক্ষণ করুন"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"সংরক্ষণাগারমুক্ত করুন"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"বিজ্ঞপ্তিগুলি বন্ধ করুন"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"বিজ্ঞপ্তি চালু করুন"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"পরিচিতি যোগ করুন"</string>
+ <string name="action_download" msgid="7786338136368564146">"ডাউনলোড করুন"</string>
+ <string name="action_send" msgid="377635240181672039">"পাঠান"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"মুছুন"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"এই বার্তাটি মুছবেন?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"এই কাজটিকে পূর্বাবস্থায় ফেরানো যাবে না৷"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"মুছুন"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">এই কথোপকথনগুলি মুছবেন?</item>
+ <item quantity="other">এই কথোপকথনগুলি মুছবেন?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"মুছুন"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"বাতিল করুন"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"প্রাপক"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"একাধিক চিত্র নির্বাচন করুন"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"নির্বাচন নিশ্চিত করুন"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>টি"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"অডিও রেকর্ড করা যাবে না। আবার চেষ্টা করুন।"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"অডিও প্লে করা যাবে না। আবার চেষ্টা করুন।"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"অডিও সংরক্ষণ করা যায়নি। আবার চেষ্টা করুন"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"স্পর্শ করুন এবং ধরে রাখুন"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ছবি"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"অডিও ক্লিপ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ভিডিও"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"পরিচিতি কার্ড"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ডাউনলোড"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS এর মাধ্যমে উত্তর দিন"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS এর মাধ্যমে উত্তর দিন"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"উত্তর দিন"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> জন অংশগ্রহণকারী</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> জন অংশগ্রহণকারী</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"আমি"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"পরিচিতি অবরুদ্ধ ও সংরক্ষণাগারভুক্ত হয়েছে"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"পরিচিতি অবরোধ মুক্ত এবং সংরক্ষণাগারমুক্ত করা হয়েছে"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> টি সংরক্ষণাগারভুক্ত হয়েছে"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> সংরক্ষণাগারমুক্ত হয়েছে"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"বিজ্ঞপ্তিগুলি বন্ধ করা হয়েছে"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"বিজ্ঞপ্তিগুলি চালু করা হয়েছে"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"সবকিছু সেট। আবার পাঠাতে স্পর্শ করুন।"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সফলভাবে সেট করা হয়েছে৷"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">সংযুক্তিগুলি বাতিল করুন</item>
+ <item quantity="other">সংযুক্তিগুলি বাতিল করুন</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"অডিও সংযুক্তি"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"অডিও সংযুক্তি চালান"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"বিরাম দিন"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> এর পাঠানো বার্তা: <xliff:g id="MESSAGE">%s</xliff:g>৷"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>:-র <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠাতে ব্যর্থ হয়েছে। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>: এর থেকে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>: -কে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠানো যায় নি। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>: -কে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠাচ্ছে। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>: -কে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠাতে ব্যর্থ হয়েছে। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>: -কে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠান। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠাতে ব্যর্থ হয়েছেন। সময়: <xliff:g id="TIME">%s</xliff:g>। <xliff:g id="GROUPINFO">%s</xliff:g>।"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>: এর থেকে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা। সময়: <xliff:g id="TIME">%s</xliff:g>। <xliff:g id="GROUPINFO">%s</xliff:g>।"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g>: -কে <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠানো যায় নি। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g>:-এ <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠানো হচ্ছে। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>: -এ <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠাতে ব্যর্থ হয়েছে। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>: -এ <xliff:g id="MESSAGE">%s</xliff:g> বার্তা পাঠান। সময়: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"বার্তা পাঠাতে ব্যর্থ হয়েছে। পুনরায় চেষ্টা করতে স্পর্শ করুন।"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> এর সাথে কথোপকথন"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"বিষয় মুছুন"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ভিডিও ক্যাপচার করুন"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"একটি স্থির চিত্র ক্যাপচার করুন"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ছবি তুলুন"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ভিডিও রেকর্ডিং শুরু করুন"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"সম্পূর্ণ স্ক্রীন ক্যামেরাতে স্যুইচ করুন"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"সামনে ও পিছনে ক্যামেরার মধ্যে স্যুইচ করুন"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"রেকর্ডিং করা বন্ধ করুন এবং ভিডিও সংযুক্ত করুন"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ভিডিও রেকর্ডিং বন্ধ করুন"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"বার্তাপ্রেরণ ফটোগুলি"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g>টি ফটো \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" অ্যালবামে সংরক্ষিত হয়েছে</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g>টি ফটো \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" অ্যালবামে সংরক্ষিত হয়েছে</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g>টি ভিডিও \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" অ্যালবামে সংরক্ষিত হয়েছে</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g>টি ভিডিও \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" অ্যালবামে সংরক্ষিত হয়েছে</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g>টি সংযুক্তি \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" অ্যালবামে সংরক্ষিত হয়েছে</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g>টি সংযুক্তি \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" অ্যালবামে সংরক্ষিত হয়েছে</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g>টি সংযুক্তি \"ডাউনলোডগুলি\" এ সংরক্ষিত হয়েছে</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>টি সংযুক্তি \"ডাউনলোডগুলি\" এ সংরক্ষিত হয়েছে</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g>টি সংযুক্তি সংরক্ষণ করা হয়েছে</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>টি সংযুক্তি সংরক্ষণ করা হয়েছে</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g>টি সংযুক্তি সংরক্ষণ করা যায়নি</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>টি সংযুক্তি সংরক্ষণ করা যায়নি</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS সংযুক্তি সংরক্ষণ করা হয়েছে"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"সেটিংস"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"সংরক্ষণাগারভুক্ত"</string>
+ <string name="action_close" msgid="1840519376200478419">"বন্ধ করুন"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"উন্নত"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ডিবাগ"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"বিজ্ঞপ্তিগুলি"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"আওয়াজ"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"নীরব"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"কম্পন"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"অবরুদ্ধ"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS বিতরণের প্রতিবেদনগুলি"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"আপনি যে SMS পাঠাবেন তার প্রতিটির জন্য পৌঁছে দেওয়ার প্রতিবেদনের অনুরোধ করুন"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"স্বতঃ-উদ্ধার"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"স্বয়ংক্রিয়ভাবে MMS বার্তাগুলি পুনরুদ্ধার করুন"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"রোমিং থাকাকালীন স্বতঃ-উদ্ধার"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"রোমিং-এ থাকার সময় স্বয়ংক্রিয়ভাবে MMS পুনরুদ্ধার করুন"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"গোষ্ঠীর মধ্যে বার্তাপ্রেরণ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"যখন একাধিক প্রাপক রয়েছে তখন একটি বার্তা পাঠাতে MMS ব্যবহার করুন"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ডিফল্ট SMS অ্যাপ্লিকেশান"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ডিফল্ট SMS অ্যাপ্লিকেশান"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"আপনার ফোন নম্বর"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"অজানা"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"আউটগোয়িং বার্তার শব্দগুলি"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"ডাম্প SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"পাওয়া SMS এর অপীষ্কার ডেটাকে বহিরাগত সংগ্রহস্থলে জমা করুন"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"ডাম্প MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"গৃহিত MMS এর কাঁচা ডেটাকে বহিরাগত সংগ্রহস্থলে জমা করুন"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"বেতার সতর্কতাগুলি"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"বার্তা বিকল্পগুলি"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"পাঠ্য অনুলিপি করুন"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"বিশদ বিবরণ দেখুন"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"মুছুন"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ফরওয়ার্ড করুন"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"বার্তার বিশদ বিবরণ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"প্রকার: "</string>
+ <string name="text_message" msgid="7415419755252205721">"পাঠ্য বার্তা"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"মাল্টিমিডিয়া বার্তা"</string>
+ <string name="from_label" msgid="1947831848146564875">"প্রেরক: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"প্রাপক: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"পাঠানো হয়েছে: "</string>
+ <string name="received_label" msgid="4442494712757995203">"গ্রহণ করা হয়েছে: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"বিষয়: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"মাপ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"অগ্রাধিকার: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"উচ্চ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"স্বাভাবিক"</string>
+ <string name="priority_low" msgid="7398724779026801851">"নিম্ন"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"লুকানো প্রেরক ঠিকানা"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"সংযুক্তি লোড করার সময় বার্তা পাঠানো যাচ্ছে না।"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"সংযুক্তি লোড করা যাবে না। আবার চেষ্টা করুন।"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"নেটওয়ার্ক প্রস্তুত নয়৷ আবার চেষ্টা করুন৷"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"পাঠ্য মুছে দিন"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"পাঠ্য এবং সংখ্যা লেখার মধ্যে স্যুইচ করুন"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"আরো অংশগ্রহণকারী যোগ করুন"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"অংশগ্রহণকারীদের নিশ্চিত করুন"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"নতুন কথোপকথন শুরু করুন"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"এই আইটেমটি নির্বাচন করুন"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ভিডিও দেখুন"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ব্যক্তি ও বিকল্পগুলি"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ডিবাগ"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ব্যক্তি ও বিকল্পগুলি"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"সাধারণ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"এই কথোপকথনে থাকা ব্যক্তিগণ"</string>
+ <string name="action_call" msgid="6596167921517350362">"একটি কল করুন"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"বার্তা পাঠান"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; থেকে &lt;br/&gt;&lt;small&gt;from বার্তা পাঠান"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ফটোগুলি পাঠান</item>
+ <item quantity="other">ফটোগুলি পাঠান</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">অডিওগুলি পাঠান</item>
+ <item quantity="other">অডিওগুলি পাঠান</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">ভিডিওগুলি পাঠান</item>
+ <item quantity="other">ভিডিওগুলি পাঠান</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">পরিচিতি কার্ডগুলি পাঠান</item>
+ <item quantity="other">পরিচিতি কার্ডগুলি পাঠান</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">সংযুক্তিগুলি পাঠান</item>
+ <item quantity="other">সংযুক্তিগুলি পাঠান</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one">পাঠানোর জন্য <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g>টি সংযুক্তি প্রস্তুত</item>
+ <item quantity="other">পাঠানোর জন্য <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g>টি সংযুক্তি প্রস্তুত</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"প্রতিক্রিয়া পাঠান"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play স্টোরে দেখুন"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"সংস্করণ তথ্য"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"সংস্করণ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"মুক্ত উৎস লাইসেন্সগুলি"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"বিজ্ঞপ্তিগুলি"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"সংযুক্তির সীমা অতিক্রান্ত হয়েছে"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"সংযুক্তি লোড করতে ব্যর্থ হয়েছে।"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"পরিচিতিতে জুড়ুন"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"পরিচিতি জুড়ুন"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"বিষয়"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"বিষয়: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"পরিচিতি কার্ড লোড হচ্ছে"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"পরিচিতি কার্ড লোড করা যায়নি"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"পরিচিতি কার্ড দেখুন"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g>টি পরিচিতি</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>টি পরিচিতি</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"যোগাযোগ কার্ড"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"জন্মদিন"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"টীকাগুলি"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"বার্তা ফরোয়ার্ড করুন"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"উত্তর"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+১"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS অক্ষম করা হয়েছে"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"পাঠাতে, ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সেট করুন"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সেট করুন"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"পরিবর্তন"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"বার্তাগুলি গ্রহণ করতে, আপনার ডিফল্ট SMS অ্যাপ্লিকেশান হিসাবে বার্তাপ্রেরণ সেট করুন"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS বার্তাগুলি পাঠানোর জন্য কোন পছন্দের SIM নির্বাচিত নেই৷"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"এই অ্যাপ্লিকেশানটি ডিভাইস মালিক দ্বারা অনুমোদিত নয়।"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ঠিক আছে"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"একটি কথোপকথনে খুব বেশি সংখ্যার অংশগ্রহণকারী"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">অবৈধ পরিচিতিগুলি</item>
+ <item quantity="other">অবৈধ পরিচিতিগুলি</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"ক্যামেরা চিত্র লোড করা যায়নি"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"আপনি: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"খসড়া"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"আপনি নতুন কোনো কথোপকথন শুরু করলে, সেটিকে এই তালিকায় দেখতে পাবেন"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"সংরক্ষিত করা কথোপকথন এখানে প্রদর্শিত হয়"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"কথোপকথনগুলি লোড হচ্ছে…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ছবি"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"অডিও ক্লিপ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ভিডিও"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"পরিচিতি কার্ড"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"পূর্বাবস্থায় ফিরুন"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"পুনরায় চেষ্টা করুন"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"একটি নতুন বার্তা পাঠাতে একটি পরিচিতি নাম অথবা ফোন নম্বর লিখুন"</string>
+ <string name="action_block" msgid="9032076625645190136">"অবরুদ্ধ করুন"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>-কে অবরুদ্ধ করুন"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g>-কে অবরোধ মুক্ত করুন"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>-কে অবরুদ্ধ করবেন?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"আপনি এই নম্বর থেকে অবিরত বার্তা গ্রহণ করতে করবেন কিন্তু আপনাকে আর সূচিত করা হবে না। এই কথোপকথন সংরক্ষণ করা হবে।"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"অবরুদ্ধ পরিচিতিগুলি"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"অবরোধ মুক্ত করুন"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"অবরুদ্ধ পরিচিতিগুলি"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"দস্তাবেজ লাইব্রেরি থেকে চিত্র নির্বাচন করুন"</string>
+ <string name="sending_message" msgid="6363584950085384929">"বার্তা পাঠানো হচ্ছে"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"বার্তা পাঠানো হয়েছে"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"সেলুলার ডেটা বন্ধ আছে। আপনার সেটিংস চেক করুন।"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"বিমান মোডে বার্তা পাঠানো যাবে না"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"এই বার্তাটি পাঠানো যায়নি"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"বার্তা ডাউনলোড করা হয়েছে"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"সেলুলার ডেটা বন্ধ আছে। আপনার সেটিংস চেক করুন।"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"বিমান মোডে বার্তাগুলি ডাউনলোড করা যাবে না"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"বার্তা ডাউনলোড করা যায়নি"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"শূন্য"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"এক"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"দুই"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"তিন"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"চার"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"পাঁচ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ছয়"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"সাত"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"আট"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"নয়"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> এর মাধ্যমে বার্তা পাঠানো যাবে না, ত্র্রুটি <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"অজানা পরিষেবা প্রদানকারীর মাধ্যমে বার্তা পাঠানো যাবে না, ত্রুটি <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"ফরওয়ার্ড: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"বার্তা পাঠানো হয়নি: নেটওয়ার্কে পরিষেবা সক্রিয় নেই"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"বার্তা পাঠানো যায়নি: অবৈধ গন্তব্য ঠিকানা"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"বার্তা পাঠানো যায়নি: অবৈধ বার্তা"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"বার্তা পাঠানো যায়নি: অসমর্থিত বিষয়বস্তু"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"বার্তা পাঠানো যায়নি: অসমর্থিত বার্তা"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"বার্তা পাঠানো হয়নি: খুব বড়"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"নতুন বার্তা"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"দেখুন"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"চিত্র"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"কোনো উপযুক্ত অ্যাপ্লিকেশান পাওয়া যায়নি"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"প্রাপককে সরান"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"নতুন বার্তা"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"বাতিল করুন"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"অ্যাক্সেস পয়েন্ট সম্পাদনা করুন"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"সেট করা নেই"</string>
+ <string name="apn_name" msgid="1572691851070894985">"নাম"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS প্রক্সি"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS পোর্ট"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN প্রকার"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN মুছে দিন"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"নতুন APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"সংরক্ষণ করুন"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"খারিজ করুন"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"নামের ক্ষেত্রটি খালি থাকতে পারে না।"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN খালি থাকতে পারে না।"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ক্ষেত্রটিকে ৩ ডিজিটের হতে হবে।"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ক্ষেত্রটিকে ২ অথবা ৩ ডিজিটের হতে হবে।"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ডিফল্ট APN সেটিংস পুনরুদ্ধার করা হচ্ছে।"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"পুনরায় ডিফল্ট হিসাবে সেট করুন"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"ডিফল্ট APN সেটিংস পুনরায় সেট করা সম্পন্ন হয়েছে।"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"শীর্ষকহীন"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"অ্যাক্সেস পয়েন্টের নামগুলি"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNগুলি"</string>
+ <string name="menu_new" msgid="8286285392706532511">"নতুন APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"এই ব্যবহারকারীর জন্য অ্যাক্সেস পয়েন্ট নাম সেটিংস উপলব্ধ নয়"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ক্লিপবোর্ডে অনুলিপি করবেন?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"অনুলিপি করুন"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> এ"</string>
+ <string name="general_settings" msgid="5409336577057897710">"সাধারণ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"আরো বিকল্প"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"সাধারণ সেটিংস"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"উন্নত সেটিংস"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"সকল প্রাপকের কাছে আলাদা আলাদা করে SMS পাঠান৷ শুধুমাত্র আপনিই উত্তরগুলি পাবেন"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"সকল প্রাপকের কাছে একটি নির্দিষ্ট MMS পাঠান"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"অজানা নম্বর"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"নতুন বার্তা"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"নতুন বার্তা৷"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM নির্বাচক"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> নির্বাচিত, SIM নির্বাচক"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"বিষয় সম্পাদনা করুন"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM নির্বাচন করুন বা বিষয় সম্পাদনা করুন"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"অডিও রেকর্ড করতে স্পর্শ করুন এবং ধরে রাখুন"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"নতুন কথোপকথন শুরু করুন"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"বার্তাপ্রেরণ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"বার্তাপ্রেরণ তালিকা"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"বার্তাপ্রেরণ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"নতুন বার্তা"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"কথোপকথনের তালিকা"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"কথোপকথনগুলি লোড হচ্ছে"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"বার্তাগুলি লোড করা হচ্ছে"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"আরো কথোপকথন দেখুন"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"আরো বার্তা দেখুন"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"কথোপকথন মুছে দেওয়া হয়েছে"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"কথোপকথন মুছে ফেলা হয়েছে৷ একটি ভিন্ন বার্তাপ্রেরণ কথোপকথন দেখানোর জন্য স্পর্শ করুন"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"অবরুদ্ধ"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"অবরোধ মুক্ত করা হয়েছে"</string>
+ <string name="db_full" msgid="8459265782521418031">"সঞ্চয়স্থান কম রয়েছে৷ কিছু ডেটা হারিয়ে যেতে পারে৷"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"সংযুক্তিগুলি নির্বাচন করুন"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"নির্বাচন নিশ্চিত করুন"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g>টি নির্বাচন করা হয়েছে"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"একটি অথবা আরও বেশি সংযুক্তি সরিয়ে আবার চেষ্টা করুন।"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"আপনি আপনার বার্তা পাঠানোর চেষ্টা করতে পারেন, কিন্তু আপনি এক বা একাধিক সংযুক্তি অপসারণ না করা পর্যন্ত তা বিতরণ নাও করা হতে পারে।"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"আপনি বার্তা প্রতি শুধুমাত্র একটি ভিডিও পাঠাতে পারেন৷ অতিরিক্ত ভিডিওগুলি সরিয়ে দিয়ে আবার চেষ্টা করুন৷"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"বার্তাপ্রেরণ সংযুক্তি লোড করতে ব্যর্থ হয়েছে৷"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"তবুও পাঠান"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"কথোপকথন শুরু করা যায়নি"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> নির্বাচিত হয়েছে"</string>
+</resources>
diff --git a/res/values-ca/arrays.xml b/res/values-ca/arrays.xml
new file mode 100644
index 0000000..69ce724
--- /dev/null
+++ b/res/values-ca/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sense assumpte"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Sí"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"D\'acord"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Gràcies"</item>
+ <item msgid="4881335087096496747">"Accepto"</item>
+ <item msgid="2422296858597420738">"Molt bé"</item>
+ <item msgid="4805581752819452687">"Ja vinc."</item>
+ <item msgid="4746700499431366214">"D\'acord. Et diré alguna cosa més tard."</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..8923f0c
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Missatges"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Missatges"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Selecció de la conversa"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Configuració"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Envia el missatge"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Afegeix un fitxer adjunt"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ajuda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Et donem la benvinguda"</string>
+ <string name="skip" msgid="7238879696319945853">"Omet"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Següent &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Següent"</string>
+ <string name="exit" msgid="1905187380359981199">"Surt"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Configuració &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Configuració"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Missatges necessita permís per accedir als SMS, al telèfon i als contactes."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Pots canviar els permisos a Configuració &gt; Aplicacions &gt; Missatges &gt; Permisos."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Pots canviar els permisos a Configuració, Aplicacions, Missatges, Permisos."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Freqüents"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Tots els contactes"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Envia al <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Captura fotos o vídeos"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Tria imatges d\'aquest dispositiu."</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Enregistra l\'àudio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Triar una foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"S\'ha seleccionat el fitxer multimèdia."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"No hi ha cap fitxer multimèdia seleccionat."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Seleccionats: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imatge: <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" msgid="8142175029053370662">"imatge"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Enregistra l\'àudio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Comparteix"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Ara mateix"</string>
+ <string name="posted_now" msgid="867560789350406701">"Ara"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hores</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dies</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dia</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> setmanes</item>
+ <item quantity="one">una setmana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mesos</item>
+ <item quantity="one">un mes</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> anys</item>
+ <item quantity="one">un any</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Missatge de classe 0"</string>
+ <string name="save" msgid="5081141452059463572">"Desa"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Queda poc espai al dispositiu. Per alliberar-ne, Missatgeria suprimirà automàticament els missatges antics."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"L\'espai d\'emmagatzematge s\'està esgotant"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Pot ser que Missatges no enviï ni rebi missatges fins que no tinguis més espai disponible al dispositiu."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Poc emmagatzematge per a SMS. És possible que et calgui suprimir missatges."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirma el teu número de telèfon"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Aquest pas únic garanteix que els missatges de grup s\'enviïn correctament amb Missatges."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Número de telèfon"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Suprimeix tots els missatges amb fitxers multimèdia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Suprimeix els missatges de més de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Suprimeix automàticament els missatges de més de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignora"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Confirmes que vols suprimir tots els missatges amb fitxers multimèdia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Confirmes que vols suprimir els missatges de més de <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Confirmes que vols suprimir els missatges de més de <xliff:g id="DURATION">%s</xliff:g> i activar la funció per suprimir-los automàticament?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ha dit:"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Has dit:"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Missatge de: <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Has enviat un missatge."</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"S\'està enviant…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"No s\'ha enviat. Toca per tornar-ho a provar."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"No s\'ha enviat. S\'està tornant a provar…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Reenvia o suprimeix"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Truca als serveis d\'emergència. En aquest moment, el missatge de text no s\'ha pogut enviar."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"S\'ha produït un error."</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Missatge MMS nou per baixar"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Missatge MMS nou"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"No s\'ha pogut baixar."</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Toca per tornar-ho a provar"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Toca per baixar"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Baixa o suprimeix"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"S\'està baixant..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"El missatge ha caducat o no està disponible."</string>
+ <string name="mms_info" msgid="3402311750134118165">"mida: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, caducitat: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"No es pot enviar. El destinatari no és vàlid."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"El servei no està activat a la xarxa."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"No s\'ha pogut enviar a causa d\'un problema de xarxa."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"El missatge ha caducat o no està disponible."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Sense assumpte)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Emissor desconegut"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Entregat"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"No s\'ha pogut baixar el missatge <xliff:g id="SUBJECT">%1$s</xliff:g> de <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"No s\'ha pogut completar l\'operació de la base de dades perquè hi ha poca memòria."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"No s\'ha enviat el missatge."</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Alguns missatges no s\'han enviat a Missatges"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> missatges en <xliff:g id="CONVERSATIONS">%d</xliff:g> converses</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> missatges en una conversa</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"No s\'ha baixat el missatge"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Alguns missatges a Missatges no s\'han baixat"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> missatges en <xliff:g id="CONVERSATIONS">%d</xliff:g> converses</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> missatges en una conversa</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"No s\'ha enviat el missatge al <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Truca als serveis d\'emergència. En aquest moment, el missatge de text al <xliff:g id="NUMBER">%1$s</xliff:g> no s\'ha pogut enviar."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> missatges nous</item>
+ <item quantity="one">Missatge nou</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Inicia"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"La càmera no està disponible."</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"La càmera no està disponible."</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"La captura de vídeo no està disponible."</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"No es pot desar el fitxer multimèdia."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"No es pot fer una foto."</string>
+ <string name="back" msgid="1477626055115561645">"Enrere"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arxivades"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Suprimeix"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arxiu"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Treu de l\'arxiu"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desactiva les notificacions"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activa les notificacions"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Afegeix un contacte"</string>
+ <string name="action_download" msgid="7786338136368564146">"Baixa"</string>
+ <string name="action_send" msgid="377635240181672039">"Envia"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Suprimeix"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Vols suprimir aquest missatge?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Aquesta acció no es pot desfer."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Suprimeix"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Vols suprimir aquestes converses?</item>
+ <item quantity="one">Vols suprimir aquesta conversa?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Suprimeix"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancel·la"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Per a"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Selecciona diverses imatges"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirma la selecció"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"No es pot enregistrar l\'àudio. Torna-ho a provar."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"No es pot reproduir l\'àudio. Torna-ho a provar."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"No s\'ha pogut desar l\'àudio. Torna-ho a provar."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Toca i mantén premut"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imatge"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clip d\'àudio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vídeo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Targeta de contacte"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Baixa"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Respon amb un SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Respon amb un MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Respon"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participants</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participant</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Jo"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contactes bloquejats i arxivats"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contacte desbloquejat i no arxivat"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Arxivades: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"No arxivades: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificacions desactivades"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificacions activades"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Ja està tot llest. Torna a tocar Envia."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Missatges s\'ha definit correctament com a aplicació predeterminada de SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Descarta els fitxers adjunts</item>
+ <item quantity="one">Descarta el fitxer adjunt</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Àudio adjunt"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reprodueix l\'àudio adjunt"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Atura"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Missatge de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Missatge no enviat de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Missatge de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Missatge no enviat a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"S\'està enviant un missatge a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Missatge no enviat a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Missatge a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Missatge no enviat de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Missatge de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Missatge no enviat a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"S\'està enviant un missatge a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Missatge no enviat a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Missatge a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Hi ha hagut un problema amb aquest missatge. Toca per tornar-ho a provar."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversa amb <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Suprimeix l\'assumpte"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Captura un vídeo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Captura una imatge fixa"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Fes una foto"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Comença a enregistrar vídeo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Canvia a la càmera de pantalla completa"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Canvia entre la càmera frontal i la posterior"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Deixa d\'enregistrar i adjunta un vídeo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Atura l\'enregistrament de vídeo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos de missatges"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">S\'han desat <xliff:g id="QUANTITY_2">%d</xliff:g> fotos a l\'àlbum <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one">S\'ha desat <xliff:g id="QUANTITY_0">%d</xliff:g> foto a l\'àlbum <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">S\'han desat <xliff:g id="QUANTITY_2">%d</xliff:g> vídeos a l\'àlbum <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one">S\'ha desat <xliff:g id="QUANTITY_0">%d</xliff:g> vídeo a l\'àlbum <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">S\'han desat <xliff:g id="QUANTITY_2">%d</xliff:g> fitxers adjunts a l\'àlbum <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one">S\'ha desat <xliff:g id="QUANTITY_0">%d</xliff:g> fitxer adjunt a l\'àlbum <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">S\'han desat <xliff:g id="QUANTITY_1">%d</xliff:g> fitxers adjunts a Baixades</item>
+ <item quantity="one">S\'ha desat <xliff:g id="QUANTITY_0">%d</xliff:g> fitxer adjunt a Baixades</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">S\'han desat <xliff:g id="QUANTITY_1">%d</xliff:g> fitxers adjunts</item>
+ <item quantity="one">S\'ha desat <xliff:g id="QUANTITY_0">%d</xliff:g> fitxer adjunt</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">No s\'han pogut desar <xliff:g id="QUANTITY_1">%d</xliff:g> fitxers adjunts</item>
+ <item quantity="one">No s\'ha pogut desar <xliff:g id="QUANTITY_0">%d</xliff:g> fitxer adjunt</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"S\'ha desat el fitxer adjunt MMS."</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Configuració"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arxivades"</string>
+ <string name="action_close" msgid="1840519376200478419">"Tanca"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avançada"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Depuració"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificacions"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"So"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silenci"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibració"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"S\'ha bloquejat"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Informes d\'entrega de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Sol·licita un informe d\'entrega per a cada SMS enviat"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recuperació automàtica"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recupera MMS automàticament"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Recupera aut. els MMS en itinerància"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Recupera MMS automàticament en itinerància"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Missatge de grup"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utilitza MMS per enviar un únic missatge a diversos destinataris"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplicació de SMS predeterminada"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplicació de SMS predeterminada"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Número de telèfon"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Desconegut"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sons per a missatges de sortida"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Envia el contingut dels SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Envia les dades sense processar dels SMS rebuts al fitxer d\'emmagatzematge extern."</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Envia el contingut dels MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Buida les dades sense processar dels MMS rebuts en un fitxer d’emmagatzematge extern"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertes sense fil"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opcions del missatge"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copia el text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Mostra els detalls"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Suprimeix"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Reenvia"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalls del missatge"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipus: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Missatge de text"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Missatge multimèdia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Per a: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Enviat: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Rebut: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Assumpte: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Mida: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritat: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Baixa"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Amaga l\'adreça del remitent"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"No es pot enviar el missatge durant la càrrega dels fitxers adjunts."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"No s\'ha pogut carregar el fitxer adjunt. Torna-ho a provar."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"La xarxa no està a punt. Torna-ho a provar."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Suprimeix el text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Alterna entre escriure lletres i números"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Afegeix més participants"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirma els participants"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Inicia una conversa nova"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Selecciona aquest element."</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reprodueix el vídeo."</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Persones i opcions"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Depura"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Persones i opcions"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"General"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Persones en aquesta conversa"</string>
+ <string name="action_call" msgid="6596167921517350362">"Fes una trucada"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Envia el missatge"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Envia un missatge&lt;br/&gt;&lt;small&gt;des de <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;."</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Envia les fotos</item>
+ <item quantity="one">Envia la foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Envia els àudios</item>
+ <item quantity="one">Envia l\'àudio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Envia els vídeos</item>
+ <item quantity="one">Envia el vídeo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Envia les targetes de contacte</item>
+ <item quantity="one">Envia la targeta de contacte</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Envia els fitxers adjunts</item>
+ <item quantity="one">Envia el fitxer adjunt</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other">Ja es poden enviar <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> fitxers adjunts</item>
+ <item quantity="one">Ja es pot enviar un fitxer adjunt</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Envia suggeriments"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Mostra a Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informació de la versió"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versió %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Llicències de codi obert"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificacions"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"S\'ha assolit el límit de fitxers adjunts"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"No s\'ha pogut carregar el fitxer adjunt."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Vols afegir-lo als contactes?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Afegeix el contacte"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Assumpte"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Assumpte: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"S\'està carregant la targeta de contacte"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"No s\'ha pogut carregar la targeta de contacte"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Mostra la targeta de contactes"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contactes</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contacte</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Targetes de contacte"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Natalici"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notes"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Reenvia el missatge"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Respon"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS desactivats"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Per enviar missatges, defineix Missatges com a aplicació predeterminada de SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Defineix Missatges com a aplicació predeterminada de SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Canvia"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Per rebre missatges, defineix Missatges com a aplicació predeterminada de SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"No s\'ha seleccionat cap SIM preferida per enviar missatges SMS."</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"El propietari del dispositiu no admet l\'aplicació."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"D\'acord"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Hi ha massa participants a la conversa."</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Els contactes no són vàlids</item>
+ <item quantity="one">El contacte no és vàlid</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"No s\'ha pogut carregar la imatge de la càmera."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Tu: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Esborrany"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Quan comencis una conversa nova, es mostrarà aquí."</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Les converses arxivades es mostren aquí."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"S\'estan carregant les converses..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imatge"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clip d\'àudio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vídeo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Targeta de contacte"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Desfés"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Torna-ho a provar"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Introdueix el nom d\'un contacte o el número de telèfon per escriure un missatge nou."</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloqueja"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloqueja <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desbloqueja <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Vols bloquejar <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Continuaràs rebent missatges d\'aquest número, però no se t\'enviarà cap notificació. Aquesta conversa s\'arxivarà."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contactes bloquejats"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOQUEJA"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contactes bloquejats"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Tria una imatge de la biblioteca de documents."</string>
+ <string name="sending_message" msgid="6363584950085384929">"S\'està enviant el missatge"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Missatge enviat"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Les dades del telèfon estan desactivades. Comprova\'n la configuració."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"No es poden enviar missatges amb el mode d\'avió activat."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"No s\'ha pogut enviar el missatge"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Missatge baixat"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Les dades del telèfon estan desactivades. Comprova\'n la configuració."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"No es poden baixar missatges en mode d\'avió"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"El missatge no s\'ha pogut baixar"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Un"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dos"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tres"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Quatre"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinc"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sis"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Set"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Vuit"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nou"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"No es pot enviar el missatge amb <xliff:g id="CARRIERNAME">%1$s</xliff:g>, error <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"No es pot enviar el missatge amb un operador de telefonia mòbil desconegut, error <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Rv: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"No s\'ha enviat el missatge; el servei no s\'ha activat a la xarxa."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Missatge no enviat: adreça de destinació no vàlida"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Missatge no enviat: missatge no vàlid"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Missatge no enviat: contingut no admès"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Missatge no enviat: missatge no admès"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"El missatge no s\'ha enviat: és massa gran"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Missatge nou"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Mostra"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imatge"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"No s\'ha pogut trobar una aplicació adequada."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Suprimeix el destinatari."</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Missatge nou"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancel·la"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edita el punt d\'accés"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"No definit"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nom"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Servidor intermediari MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port per a MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipus d\'APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Suprimeix l\'APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN nou"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Desa"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Descarta"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"El camp Nom no pot quedar buit."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"L\'APN no pot quedar buit."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"El camp MCC ha de tenir 3 dígits."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"El camp MNC ha de tenir 2 o 3 dígits."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"S\'està restaurant la configuració predeterminada d\'APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Restableix al valor predeterminat"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"S\'ha restablert la configuració predeterminada d\'APN."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sense títol"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Noms de punt d\'accés"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN nou"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"La configuració del nom del punt d\'accés no està disponible per a aquest usuari."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Ho vols copiar al porta-retalls?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copia"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"per a <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"General"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avançada"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Configuració general"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Configuració avançada"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Envia missatges SMS individuals a tots els destinataris. Només tu rebràs les respostes."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Envia un MMS individual a tots els destinataris"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Número desconegut"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Missatge nou"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Missatge nou"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Selector de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> seleccionada, selector de SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edita l\'assumpte"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Selecciona la SIM o edita l\'assumpte"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Mantén premut per enregistrar àudio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Inicia una conversa nova"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Missatges"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Llista de missatges"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Missatges"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Missatge nou"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Llista de converses"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"S\'estan carregant les converses"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"S\'estan carregant els missatges"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Mostra més converses"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Mostra més missatges"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"S\'ha suprimit la conversa."</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"S\'ha suprimit la conversa. Toca per mostrar una altra conversa de Missatges"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloquejat"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Desbloquejat"</string>
+ <string name="db_full" msgid="8459265782521418031">"Hi ha poc espai d\'emmagatzematge. És possible que es perdin algunes dades."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Selecciona els fitxers adjunts"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirma la selecció"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Seleccionats: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Suprimeix un o més fitxers adjunts i torna-ho a provar."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Pots provar d\'enviar el missatge, però és possible que no s\'enviï si no suprimeixes un o més fitxers adjunts."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Només pots enviar un vídeo per missatge. Suprimeix els vídeos addicionals i torna-ho a provar."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Missatges no ha pogut carregar el fitxer adjunt."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Envia igualment"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"No s\'ha pogut iniciar la conversa"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> seleccionada"</string>
+</resources>
diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml
new file mode 100644
index 0000000..95c2775
--- /dev/null
+++ b/res/values-cs/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"bez předmětu"</item>
+ <item msgid="272485471009191934">"bezpředmětu"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ano"</item>
+ <item msgid="6049132459802288033">"Ne"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Cha cha"</item>
+ <item msgid="2611328818571146775">"Díky"</item>
+ <item msgid="4881335087096496747">"Souhlasím"</item>
+ <item msgid="2422296858597420738">"Hezké"</item>
+ <item msgid="4805581752819452687">"Jsem na cestě."</item>
+ <item msgid="4746700499431366214">"OK, ozvu se později."</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..8b56e47
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"SMS a MMS"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"SMS a MMS"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Vybrat konverzaci"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Nastavení"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Odeslat zprávu"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Přidat přílohu"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Nápověda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Vítejte"</string>
+ <string name="skip" msgid="7238879696319945853">"Přeskočit"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Další &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Další"</string>
+ <string name="exit" msgid="1905187380359981199">"Ukončit"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Nastavení &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Nastavení"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Aplikace SMS a MMS vyžaduje oprávnění k přístupu k SMS, telefonu a kontaktům."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Oprávnění můžete změnit v Nastavení &gt; Aplikace &gt; SMS a MMS &gt; Oprávnění."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Oprávnění můžete změnit v Nastavení, Aplikace, SMS a MMS, Oprávnění."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Časté kontakty"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Všechny kontakty"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Odeslat na číslo <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Pořídit fotky nebo video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Vyberte obrázky z tohoto zařízení."</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Nahrát zvuk"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Vybrat fotku"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Médium je vybráno."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Výběr média je zrušen."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Vybráno: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"obrázek <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" msgid="8142175029053370662">"obrázek"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Nahrávání zvuku"</string>
+ <string name="action_share" msgid="2143483844803153871">"Sdílet"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Právě teď"</string>
+ <string name="posted_now" msgid="867560789350406701">"Teď"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> hodiny</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> hodiny</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hodin</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hodina</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dny</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> dne</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dnů</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> den</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> týdny</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> týdne</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> týdnů</item>
+ <item quantity="one">jeden týden</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> měsíce</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> měsíce</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> měsíců</item>
+ <item quantity="one">jeden měsíc</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> roky</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> roku</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> let</item>
+ <item quantity="one">jeden rok</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Zpráva třídy 0"</string>
+ <string name="save" msgid="5081141452059463572">"Uložit"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"V zařízení dochází místo. Aplikace SMS a MMS automaticky smaže starší zprávy, aby uvolnila místo."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"V úložišti je málo místa"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Aplikace SMS a MMS nebude moci posílat ani přijímat zprávy, dokud v zařízení nebude k dispozici více místa."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Dochází volné místo na zprávy SMS. Možná bude potřeba nějaké zprávy vymazat."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Ověřit telefonní číslo"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Tento jednorázový krok zajistí, že aplikace SMS a MMS správně doručí vaše skupinové zprávy."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonní číslo"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Smazat všechny zprávy s médii"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Smazat zprávy starší než <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Automaticky smazat zprávy starší než <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorovat"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Vymazat všechny zprávy s médii?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Smazat zprávy starší než <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Chcete smazat zprávy starší než <xliff:g id="DURATION">%s</xliff:g> a zapnout automatické mazání?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> říká"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Řekli jste"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Zpráva od uživatele <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Odeslali jste zprávu"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Odesílání…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Zpráva nebyla odeslána. Klepnutím to zkusíte znovu."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Zpráva nebyla odeslána. Probíhá další pokus…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Znovu odeslat nebo smazat"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Uskutečněte prosím hlasové volání nouzových služeb. Vaši textovou zprávu v současné době nebylo možné doručit."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Nezdařilo se"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nová zpráva MMS ke stažení"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nová zpráva MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Stažení se nezdařilo."</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Klepnutím to zkusíte znovu"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Stáhněte klepnutím."</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Stáhnout nebo smazat"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Stahování…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Platnost zprávy vypršela nebo zpráva není k dispozici."</string>
+ <string name="mms_info" msgid="3402311750134118165">"velikost: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, vypršení platnosti: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Zprávu nelze odeslat. Příjemce není platný."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Služba není v síti aktivována."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Zprávu se nepodařilo odeslat z důvodu chyby v síti."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Platnost zprávy vypršela nebo zpráva není k dispozici."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Bez předmětu)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Neznámý odesílatel"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Doručeno"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Stažení zprávy <xliff:g id="SUBJECT">%1$s</xliff:g> od odesílatele <xliff:g id="FROM">%2$s</xliff:g> se nezdařilo."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Databázovou operaci se nepodařilo dokončit z důvodu nedostatku paměti."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Zprávu se nepodařilo odeslat."</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Několik zpráv v aplikaci SMS a MMS nebylo odesláno."</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="few">Počet zpráv ve <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzacích: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="many">Počet zpráv v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzace: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="other">Počet zpráv v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzacích: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="one">Počet zpráv v jedné konverzaci: <xliff:g id="MESSAGES_0">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Zprávu se nepodařilo stáhnout"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Několik zpráv se do aplikace SMS a MMS nepodařilo stáhnout."</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="few">Počet zpráv ve <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzacích: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="many">Počet zpráv v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzace: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="other">Počet zpráv v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzacích: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="one">Počet zpráv v jedné konverzaci: <xliff:g id="MESSAGES_0">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Zpráva na číslo <xliff:g id="NUMBER">%1$s</xliff:g> nebyla odeslána."</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Uskutečněte prosím hlasové volání nouzových služeb. Vaši textovou zprávu na číslo <xliff:g id="NUMBER">%1$s</xliff:g> v současné době nebylo možné doručit."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> nové zprávy</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> nové zprávy</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nových zpráv</item>
+ <item quantity="one">Nová zpráva</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Spustit"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Fotoaparát není k dispozici"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Fotoaparát není k dispozici."</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Záznam videa není k dispozici."</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Média nelze uložit."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nelze pořídit fotku"</string>
+ <string name="back" msgid="1477626055115561645">"Zpět"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archivováno"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Smazat"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archivovat"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Vyjmout z archivu"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Vypnout oznámení"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Zapnout oznámení"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Přidat kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Stáhnout"</string>
+ <string name="action_send" msgid="377635240181672039">"Odeslat"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Smazat"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Smazat tuto zprávu?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Tuto akci nelze vrátit zpět."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Smazat"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="few">Smazat tyto konverzace?</item>
+ <item quantity="many">Smazat tyto konverzace?</item>
+ <item quantity="other">Smazat tyto konverzace?</item>
+ <item quantity="one">Smazat tuto konverzaci?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Smazat"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Zrušit"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Komu"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Vybrat několik obrázků"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Potvrďte výběr"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Zvuk nelze zaznamenat. Zkuste to znovu."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Zvuk nelze přehrát. Zkuste to znovu."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Zvuk nelze uložit. Zkuste to znovu."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Dotyk s podržením"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Fotografie"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Zvukový klip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Karta kontaktu"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Stáhnout"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Odpovědět – SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Odpovědět v MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Odpovědět"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> účastníci</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> účastníka</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> účastníků</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> účastník</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Já"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt byl zablokován a archivován."</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt byl odblokován a jeho archivace byla zrušena"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Archivováno: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Vyjmuto z archivu: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Oznámení jsou vypnuta"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Oznámení jsou zapnuta"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Vše je připraveno. Znovu klepněte na Odeslat."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Aplikace SMS a MMS byla úspěšně nastavena jako výchozí aplikace pro SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="few">Zahodit přílohy</item>
+ <item quantity="many">Zahodit přílohy</item>
+ <item quantity="other">Zahodit přílohy</item>
+ <item quantity="one">Zahodit přílohu</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Zvuková příloha"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Přehrát zvukovou přílohu"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pozastavit"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Zpráva od uživatele <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Neúspěšná zpráva od uživatele <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Zpráva od uživatele <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Neodeslaná zpráva pro uživatele <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Odesílání zprávy uživateli <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Neúspěšná zpráva pro uživatele <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Zpráva pro uživatele <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Neúspěšná zpráva od uživatele <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Zpráva od uživatele <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Neodeslaná zpráva pro uživatele <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Odesílání zprávy uživateli <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Neúspěšná zpráva pro uživatele <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Zpráva pro uživatele <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Neúspěšná zpráva. Klepnutím pokus opakujte."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Konverzace s uživateli <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Smazat předmět"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Natočit video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Pořídit statický snímek"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Vyfotit"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Začít natáčet video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Přepnout na fotoaparát na celou obrazovku"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Přepnout mezi předním a zadním fotoaparátem"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Zastavit nahrávání a připojit video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Zastavit záznam videa"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotky z aplikace SMS a MMS"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> fotky byly uloženy do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> fotky bylo uloženo do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotek bylo uloženo do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> fotka byla uložena do alba <xliff:g id="ALBUMNAME_1">%s</xliff:g>.</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> videa byla uložena do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> videa bylo uloženo do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videí bylo uloženo do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video bylo uloženo do alba <xliff:g id="ALBUMNAME_1">%s</xliff:g>.</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> přílohy byly uloženy do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> přílohy bylo uloženo do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> příloh bylo uloženo do alba <xliff:g id="ALBUMNAME_3">%s</xliff:g>.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> příloha byla uložena do alba <xliff:g id="ALBUMNAME_1">%s</xliff:g>.</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> přílohy byly uloženy do složky stažených položek.</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> přílohy bylo uloženo do složky stažených položek.</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> příloh bylo uloženo do složky stažených položek.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> příloha byla uložena do složky stažených položek.</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="few">Uloženy <xliff:g id="QUANTITY_1">%d</xliff:g> přílohy</item>
+ <item quantity="many">Uloženo <xliff:g id="QUANTITY_1">%d</xliff:g> přílohy</item>
+ <item quantity="other">Uloženo <xliff:g id="QUANTITY_1">%d</xliff:g> příloh</item>
+ <item quantity="one">Uložena <xliff:g id="QUANTITY_0">%d</xliff:g> příloha</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> přílohy se nepodařilo uložit</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> přílohy se nepodařilo uložit</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> příloh se nepodařilo uložit</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> přílohu se nepodařilo uložit</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Příloha MMS byla uložena"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Nastavení"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archivováno"</string>
+ <string name="action_close" msgid="1840519376200478419">"Zavřít"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Rozšířená nastavení"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Ladit"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Oznámení"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Zvuk"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Tichý režim"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrace"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Zablokováno"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Zprávy o doručení SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"U každé odeslané SMS požadovat potvrzení o doručení"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatické načítání"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automaticky načítat zprávy MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Autom. načítání při roamingu"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Při roamingu automaticky načítat zprávy MMS"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Skupinové zprávy"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Použít MMS k odeslání jedné zprávy, pokud má několik příjemců"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Výchozí aplikace SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Výchozí aplikace SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Vaše telefonní číslo"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Neznámé"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Zvuky odchozích zpráv"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Vypsání zpráv SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Vypsat nezpracovaná data přijatých zpráv SMS do souboru externího úložiště"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Vypsání zpráv MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Vypsat nezpracovaná data přijatých zpráv MMS do souboru externího úložiště"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Bezdrátová upozornění"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Možnosti zprávy"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopírovat text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Zobrazit podrobnosti"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Smazat"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Přeposlat"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Podrobnosti zprávy"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Typ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Textová zpráva"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimediální zpráva"</string>
+ <string name="from_label" msgid="1947831848146564875">"Od: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Komu: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Odesláno: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Přijato: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Předmět: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Velikost: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorita: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM karta: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Vysoká"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normální"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Nízká"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM karta <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Skrytá adresa odesílatele"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Při načítání příloh nelze odeslat zprávu."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Přílohu nelze načíst. Zkuste to znovu."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Síť není připravena. Zkuste to znovu."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Smazat text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Přepnutí mezi zadáváním textu a čísel"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Přidat další účastníky"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Potvrdit účastníky"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Zahájit novou konverzaci"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Vybrat tuto položku"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Přehrát video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Lidé a možnosti"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Ladit"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Lidé a možnosti"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Obecné"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Lidé v této konverzaci"</string>
+ <string name="action_call" msgid="6596167921517350362">"Uskutečnit hovor"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Poslat zprávu"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Odeslat zprávu&lt;br/&gt;&lt;small&gt;ze SIM karty <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="few">Odeslat fotky</item>
+ <item quantity="many">Odeslat fotky</item>
+ <item quantity="other">Odeslat fotky</item>
+ <item quantity="one">Odeslat fotku</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="few">Odeslat zvukové nahrávky</item>
+ <item quantity="many">Odeslat zvukové nahrávky</item>
+ <item quantity="other">Odeslat zvukové nahrávky</item>
+ <item quantity="one">Odeslat zvukovou nahrávku</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="few">Odeslat videa</item>
+ <item quantity="many">Odeslat videa</item>
+ <item quantity="other">Odeslat videa</item>
+ <item quantity="one">Odeslat video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="few">Odeslat vizitky</item>
+ <item quantity="many">Odeslat vizitky</item>
+ <item quantity="other">Odeslat vizitky</item>
+ <item quantity="one">Odeslat vizitku</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="few">Odeslat přílohy</item>
+ <item quantity="many">Odeslat přílohy</item>
+ <item quantity="other">Odeslat přílohy</item>
+ <item quantity="one">Odeslat přílohu</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> přílohy jsou připraveny k odeslání</item>
+ <item quantity="many"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> přílohy je připraveno k odeslání</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> příloh je připraveno k odeslání</item>
+ <item quantity="one">Jedna příloha je připravena k odeslání</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Odeslat zpětnou vazbu"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Zobrazit v Obchodě Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informace o verzi"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Verze %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licence open source"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Oznámení"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Byl dosažen maximální počet příloh"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Načtení přílohy se nezdařilo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Přidat do kontaktů?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Přidat kontakt"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Předmět"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Předmět: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Načítání kontaktní karty"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kartu kontaktu nelze načíst."</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Zobrazit kontaktní kartu"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontakty</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> kontaktu</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktů</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Karty kontaktů"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Narozeniny"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Poznámky"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Přeposlat zprávu"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Odpovědět"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Zprávy SMS jsou zakázány"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Chcete-li odeslat zprávu, nastavte aplikaci SMS a MMS jako výchozí aplikaci pro SMS."</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Nastavte aplikaci SMS a MMS jako výchozí aplikaci pro SMS."</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Změnit"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Chcete-li dostávat zprávy, nastavte aplikaci SMS a MMS jako výchozí aplikaci pro SMS."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Není vybrána preferovaná SIM karta pro odesílání zpráv SMS."</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Tuto aplikaci vlastník zařízení nepovolil."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Konverzace obsahuje příliš mnoho účastníků"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="few">Neplatné kontakty</item>
+ <item quantity="many">Neplatné kontakty</item>
+ <item quantity="other">Neplatné kontakty</item>
+ <item quantity="one">Neplatný kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Obraz z fotoaparátu nelze načíst."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Vy: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Koncept"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Zde budou uvedeny nové konverzace"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Zde se zobrazují archivované konverzace."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Načítání konverzací…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Fotografie"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Zvukový klip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Karta kontaktu"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Vrátit zpět"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Opakovat"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Chcete-li začít psát novou zprávu, zadejte jméno kontaktu nebo telefonní číslo."</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokovat"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokovat <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Odblokovat kontakt <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blokovat <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Zprávy z tohoto čísla budete i nadále dostávat, ale nebude se pro ně již zobrazovat oznámení. Tato konverzace bude archivována."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokované kontakty"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ODBLOKOVAT"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokované kontakty"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Vyberte obrázek z knihovny dokumentů."</string>
+ <string name="sending_message" msgid="6363584950085384929">"Odesílání zprávy"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Zpráva byla odeslána"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobilní datové přenosy jsou vypnuty. Zkontrolujte svá nastavení."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"V režimu Letadlo zprávy nelze odeslat."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Zprávu nelze odeslat"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Zpráva byla stažena"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobilní datové přenosy jsou vypnuty. Zkontrolujte svá nastavení."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"V režimu Letadlo zprávy nelze stahovat"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Zprávu nelze stáhnout"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nula"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Jedna"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dvě"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tři"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Čtyři"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Pět"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Šest"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sedm"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Osm"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Devět"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Zprávu prostřednictvím operátora <xliff:g id="CARRIERNAME">%1$s</xliff:g> nelze odeslat. Chyba <xliff:g id="ERRORCODE">%2$d</xliff:g>."</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Zprávu prostřednictvím neznámého operátora nelze odeslat. Chyba <xliff:g id="ERRORCODE">%1$d</xliff:g>."</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Zpráva nebyla odeslána: služba není v síti aktivována"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Zpráva nebyla odeslána: neplatná cílová adresa"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Zpráva nebyla odeslána: neplatná zpráva"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Zpráva nebyla odeslána: nepodporovaný obsah"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Zpráva nebyla odeslána: nepodporovaná zpráva"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Zpráva nebyla odeslána, protože je příliš velká"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nová zpráva"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Zobrazit"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Obrázek"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Vhodná aplikace nebyla nalezena."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Odebrat příjemce"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nová zpráva"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Zrušit"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Upravit přístupový bod"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nenastaveno"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Název"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Název přístupového bodu (APN)"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy server pro MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port pro MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Typ APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Smazat APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nový název přístupového bodu (APN)"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Uložit"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Zahodit"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Pole Název nesmí být prázdné."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Název přístupového bodu (APN) nesmí být prázdný."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Pole MCC musí obsahovat alespoň 3 číslice."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Pole MNC musí obsahovat 2 nebo 3 číslice."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Obnovování výchozích nastavení (APN)"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Obnovit výchozí"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Obnovení výchozích nastavení APN bylo dokončeno."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Bez názvu"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Názvy přístupových bodů"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Názvy přístupových bodů (APN)"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nový název přístupového bodu (APN)"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Nastavení názvu přístupového budu pro tohoto uživatele není dostupné."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopírovat do schránky?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopírovat"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"na SIM kartu <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Obecné"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Rozšířená nastavení"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Obecná nastavení"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Rozšířená nastavení"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM karta <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Odesílat všem příjemcům jednotlivé zprávy SMS. Odpovědi dostanete pouze vy."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Odeslat všem příjemcům jednu zprávu MMS"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Neznámé číslo"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nová zpráva"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nová zpráva"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Výběr SIM karty"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Výběr SIM karty (vybrána SIM karta <xliff:g id="SIM_0">%1$s</xliff:g>)"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Upravit předmět"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Vybrat SIM kartu nebo upravit předmět"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Zvuk zaznamenáte klepnutím a podržením"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Zahájit novou konverzaci"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS a MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Seznam SMS a MMS"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS a MMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nová zpráva"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Seznam konverzací"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Načítání konverzací"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Načítání zpráv"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Zobrazit další konverzace"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Zobrazit další zprávy"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Konverzace byla smazána"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Konverzace byla smazána. Klepnutím zobrazíte jinou konverzaci v aplikaci SMS a MMS."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Zablokováno"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Odblokováno"</string>
+ <string name="db_full" msgid="8459265782521418031">"V úložišti je málo místa. Některá data mohou být ztracena."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Vybrat přílohy"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Potvrďte výběr"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Vybráno: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Odstraňte prosím alespoň jednu přílohu a zkuste to znovu."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Zprávu můžete zkusit odeslat, avšak může se stát, že nebude doručena, dokud neodstraníte alespoň jednu přílohu."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Ve zprávě lze odeslat pouze jedno video. Odstraňte ostatní videa a zkuste to znovu."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Načtení přílohy v aplikaci SMS a MMS se nezdařilo."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Přesto odeslat"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Konverzaci nelze zahájit"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Vybráno: <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-da/arrays.xml b/res/values-da/arrays.xml
new file mode 100644
index 0000000..d40336a
--- /dev/null
+++ b/res/values-da/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"intet emne"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ja"</item>
+ <item msgid="6049132459802288033">"Nej"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Haha"</item>
+ <item msgid="2611328818571146775">"Tak"</item>
+ <item msgid="4881335087096496747">"Jeg er enig"</item>
+ <item msgid="2422296858597420738">"Sådan!"</item>
+ <item msgid="4805581752819452687">"Er på vej"</item>
+ <item msgid="4746700499431366214">"Jeg vender tilbage til dig lidt senere"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..340c28f
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Beskeder"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Beskeder"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Vælg en samtale"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Indstillinger"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Send besked"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Tilføj en vedhæftet fil"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Hjælp"</string>
+ <string name="welcome" msgid="2857560951820802321">"Velkommen"</string>
+ <string name="skip" msgid="7238879696319945853">"Spring over"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Næste &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Næste"</string>
+ <string name="exit" msgid="1905187380359981199">"Afslut"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Indstillinger &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Indstillinger"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Beskeder skal have tilladelse for at kunne anvende Sms, Telefon og Kontaktpersoner."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Du kan ændre tilladelser i Indstillinger &gt; Apps &gt; Beskeder &gt; Tilladelser."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Du kan ændre tilladelser under Indstillinger, Apps, Beskeder, Tilladelser."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Mest brugte"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Alle kontaktpersoner"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Send til <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Optag billeder eller video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Vælg billeder fra denne enhed"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Optag lyd"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Vælg billede"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Medierne er valgt."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Medierne er fravalgt."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Der er valgt <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"billede <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" msgid="8142175029053370662">"billede"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Optag lyd"</string>
+ <string name="action_share" msgid="2143483844803153871">"Del"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Lige nu"</string>
+ <string name="posted_now" msgid="867560789350406701">"Nu"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> timer</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> timer</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dage</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dage</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> uger</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> uger</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> måneder</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> måneder</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> år</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> år</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Klasse 0-besked"</string>
+ <string name="save" msgid="5081141452059463572">"Gem"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Enheden er ved at løbe tør for plads. Ældre beskeder bliver automatisk slettet for at frigøre plads."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Der er snart ikke mere lagerplads"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Beskeder sender eller modtager muligvis ikke beskeder, før der er mere ledig plads på din enhed."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Begrænset lagerplads til sms. Det kan være nødvendigt at slette beskeder."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Bekræft dit telefonnummer"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Dette engangstrin sikrer, at Beskeder leverer dine gruppebeskeder på korrekt vis."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonnummer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Slet alle beskeder med medier"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Slet beskeder, der er mere end <xliff:g id="DURATION">%s</xliff:g> gamle"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Slet automatisk beskeder, der er mere end <xliff:g id="DURATION">%s</xliff:g> gamle"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorer"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Slet alle beskeder, der indeholder medier?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Vil du slette beskeder, der er mere end <xliff:g id="DURATION">%s</xliff:g> gamle?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Vil du slette beskeder, der er ældre end <xliff:g id="DURATION">%s</xliff:g>, og aktiverr automatisk sletning?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> sagde"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Du sagde"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Besked fra <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Du sendte en besked"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Sender…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ikke sendt. Tryk for at prøve igen."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ikke sendt. Prøver igen…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Send igen eller slet"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Ring op til alarmcentralen. Din sms-besked kan ikke leveres på nuværende tidspunkt."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Mislykkedes"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Ny MMS-besked til download"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Ny MMS-besked"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Download mislykkedes"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tryk for at prøve igen"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Tryk for at downloade"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Download eller slet"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Downloader…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Beskeden er udløbet eller ikke tilgængelig"</string>
+ <string name="mms_info" msgid="3402311750134118165">"størrelse: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, udløb: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Beskeden kan ikke sendes. Ugyldig modtager."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Tjenesten er ikke aktiveret på netværket"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Et netværksproblem er skyld i, at afsendelsen mislykkedes"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Beskeden er udløbet eller ikke tilgængelig"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Intet emne)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Ukendt afsender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Leveret"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Beskeden <xliff:g id="SUBJECT">%1$s</xliff:g> blev ikke downloadet fra <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Databasehandlingen blev ikke udført, fordi hukommelsen er for lille"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Beskeden blev ikke sendt"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Nogle beskeder blev ikke sendt i Beskeder"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> beskeder i <xliff:g id="CONVERSATIONS">%d</xliff:g> samtaler</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> beskeder i <xliff:g id="CONVERSATIONS">%d</xliff:g> samtaler</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Beskeden blev ikke downloadet"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Nogle beskeder blev ikke downloadet i Beskeder"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> beskeder i <xliff:g id="CONVERSATIONS">%d</xliff:g> samtaler</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> beskeder i <xliff:g id="CONVERSATIONS">%d</xliff:g> samtaler</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Beskeden til <xliff:g id="NUMBER">%1$s</xliff:g> blev ikke sendt"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Ring op til alarmcentralen. Din sms-besked til <xliff:g id="NUMBER">%1$s</xliff:g> kan ikke leveres på nuværende tidspunkt."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> nye beskeder</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nye beskeder</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Start"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kameraet er ikke tilgængeligt"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kameraet er ikke tilgængeligt"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Videooptagelse er ikke tilgængelig"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Medie kan ikke gemmes"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Der kan ikke tages billeder"</string>
+ <string name="back" msgid="1477626055115561645">"Tilbage"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arkiveret"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Slet"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arkivér"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Fjern fra arkiv"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Deaktiver underretninger"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Aktivér underretninger"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Tilføj kontaktperson"</string>
+ <string name="action_download" msgid="7786338136368564146">"Download"</string>
+ <string name="action_send" msgid="377635240181672039">"Send"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Slet"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Vil du slette denne besked?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Denne handling kan ikke fortrydes."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Slet"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Vil du slette disse samtaler?</item>
+ <item quantity="other">Vil du slette disse samtaler?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Slet"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Annuller"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Til"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Vælg flere billeder"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Bekræft valg"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Der kan ikke optages lyd. Prøv igen."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Lyden kan ikke afspilles. Prøv igen."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Lyden kunne ikke gemmes. Prøv igen."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Tryk og hold nede"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Billede"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Lydklip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Visitkort"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Download"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Svar via sms"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Svar via mms"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Besvar"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> deltagere</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> deltagere</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Mig"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakten er blokeret og arkiveret"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontaktpersonen er ikke længere blokeret og er fjernet fra arkivet"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> blev arkiveret"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> er blevet fjernet fra arkivet."</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Underretninger er slået fra"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Underretninger er slået til"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Alt er klart. Tryk på Send igen."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Beskeder blev angivet som standardappen til sms-beskeder."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Kassér vedhæftede filer</item>
+ <item quantity="other">Kassér vedhæftede filer</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Vedhæftet lydfil"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Afspil vedhæftet lydfil"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Sæt på pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Besked fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mislykket besked fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Besked fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Ikke-afsendt besked til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sender besked til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mislykket besked til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Besked til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mislykket besked fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Besked fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Ikke-afsendt besked til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sender besked til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mislykket besked til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Besked til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mislykket besked. Tryk for at prøve igen."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Samtale med <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Slet emne"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Optag video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Tag et stillbillede"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Tag et billede"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Start videooptagelse"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Skift til kamera i fuld skærm"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Skift mellem front- og bagudvendt kamera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stop optagelsen, og vedhæft video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Stands optagelse af video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Billeder i Beskeder"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> billeder blev gemt i albummet \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> billeder blev gemt i albummet \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> videoer blev gemt i albummet \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videoer blev gemt i albummet \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> vedhæftede filer blev gemt i albummet \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vedhæftede filer blev gemt i albummet \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> vedhæftede filer blev gemt under \"Downloads\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> vedhæftede filer blev gemt under \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> vedhæftede filer blev gemt</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> vedhæftede filer blev gemt</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> vedhæftede filer kunne ikke gemmes</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> vedhæftede filer kunne ikke gemmes</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Den vedhæftede MMS blev gemt"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Indstillinger"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arkiveret"</string>
+ <string name="action_close" msgid="1840519376200478419">"Luk"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"Mms"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avanceret"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Fejlretning"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Underretninger"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Lyd"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Lydløs"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrer"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokeret"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-leveringsrapporter"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Anmod om leveringsrapporter for alle de sms-beskeder, du sender"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatisk hentning"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Hent automatisk mms-beskeder"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automatisk hentning under roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Hent automatisk mms under roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Gruppebeskeder"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Brug mms til at sende en enkelt besked, når der er flere modtagere"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Standardapp til sms-beskeder"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Standardapp til sms-beskeder"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Dit telefonnummer"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Ukendt"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Lyde for udgående besked"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Gem sms"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Gem rådata fra modtagne sms-beskeder i en fil på et eksternt lager"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Gem mms"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Gem rådata fra modtagne mms-beskeder i en fil på et eksternt lager"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Trådløse alarmer"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Indstillinger for beskeder"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopiér tekst"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Se info"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Slet"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Videresend"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Beskedinfo"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Sms"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mms"</string>
+ <string name="from_label" msgid="1947831848146564875">"Fra: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Til: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Sendt: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Modtaget: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Emne: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Størrelse: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-kort: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Høj"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Lav"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM-kort <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Skjult afsenderadresse"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Beskeden kan ikke sendes, mens der indlæses vedhæftede filer."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Den vedhæftede fil kunne ikke indlæses. Prøv igen."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Netværket er ikke klar. Prøv igen."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Slet tekst"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Skift mellem indtastning af tekst og tal"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Tilføj flere deltagere"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Bekræft deltagere"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Start en ny samtale"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Vælg dette element"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Afspil video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Personer og valgmuligheder"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Fejlretning"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Personer og valgmuligheder"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Generelt"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Personer i denne samtale"</string>
+ <string name="action_call" msgid="6596167921517350362">"Foretag et opkald"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Send besked"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Send besked&lt;br/&gt;&lt;small&gt;fra <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Send billeder</item>
+ <item quantity="other">Send billeder</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Send lydoptagelser</item>
+ <item quantity="other">Send lydoptagelser</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Send videoer</item>
+ <item quantity="other">Send videoer</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Send visitkort</item>
+ <item quantity="other">Send visitkort</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Send vedhæftede filer</item>
+ <item quantity="other">Send vedhæftede filer</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> vedhæftede filer er klar til at blive sendt</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> vedhæftede filer er klar til at blive sendt</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Send feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Se i Google Play Butik"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Oplysninger om versionen"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Open source-licenser"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Underretninger"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Grænsen for vedhæftede filer er nået"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Den vedhæftede fil kunne ikke indlæses."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Vil du føje dette nummer til Kontaktpersoner?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Tilføj kontakt"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Emne"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Emne: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kontaktkortet indlæses"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kontaktkortet kunne ikke indlæses"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Se visitkortet"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kontaktpersoner</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktpersoner</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktkort"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Fødselsdag"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Noter"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Videresend besked"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Svar"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Sms er slået fra"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Angiv Beskeder som standardappen til sms-beskeder for at sende beskeder"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Angiv Beskeder som standardappen til sms-beskeder"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Skift"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Angiv Beskeder som standardappen til sms-beskeder for at modtage beskeder"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Der er ikke valgt et foretrukket simkort til afsendelse af sms-beskeder"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Denne app er ikke tilladt af enhedens ejer."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Der er for mange deltagere i en samtale"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Ugyldige kontaktpersoner</item>
+ <item quantity="other">Ugyldige kontaktpersoner</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kamerabilledet kunne ikke indlæses"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Dig: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Kladde"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Når du starter en ny samtale, vises den her"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arkiverede samtaler vises her"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Indlæser samtaler ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Billede"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Lydklip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Visitkort"</string>
+ <string name="mms_text" msgid="1528791558806015806">"Mms"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Fortryd"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Prøv igen"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Indtast navnet på en kontaktperson eller et telefonnummer for at starte på en ny besked"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloker"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloker <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Ophæv blokering af <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Vil du blokere <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Du vil fortsat modtage beskeder fra dette nummer, men du bliver ikke længere underrettet. Denne samtale arkiveres."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokerede kontakter"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"OPHÆV BLOKERING"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokerede kontakter"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Vælg billede fra dokumentbiblioteket"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sender besked"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Beskeden er sendt"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobildata er slået fra. Kontrollér dine indstillinger."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Der kan ikke sendes beskeder i flytilstand"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Beskeden blev ikke sendt"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Beskeden blev downloadet"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobildata er slået fra. Kontrollér dine indstillinger."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Beskeder kan ikke downloades i flytilstand"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Beskeden kunne ikke downloades"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nul"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Et"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"To"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tre"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Fire"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Fem"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seks"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Syv"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Otte"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Ni"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Der kan ikke sendes en besked med <xliff:g id="CARRIERNAME">%1$s</xliff:g>, fejl <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Der kan ikke sendes en besked med ukendt mobilselskab, fejl <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Vs: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Beskeden blev ikke sendt: Tjenesten er ikke aktiveret på netværket"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Beskeden blev ikke sendt: Destinationsadressen er ugyldig"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Beskeden blev ikke sendt: Beskeden er ugyldig"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Beskeden blev ikke sendt: Indholdet understøttes ikke"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Beskeden blev ikke sendt: Beskeden understøttes ikke"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Beskeden blev ikke sendt, da den er for stor"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Ny besked"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Vis"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Billede"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Vi kunne ikke finde en passende applikation"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Fjern modtager"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Ny besked"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Annuller"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Rediger adgangspunkt"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ikke angivet"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Navn"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Adgangspunkt"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Adgangspunktstype"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Slet adgangspunkt"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nyt adgangspunkt"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Gem"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Kassér"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Feltet Navn skal udfyldes."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Du skal angive et adgangspunkt."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Feltet MCC skal indeholde tre cifre."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Feltet MNC skal indeholde to eller tre cifre."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Gendanner standardindstillingerne for adgangspunktet."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Nulstil til standard"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Standardindstillingerne for adgangspunktet er nulstillet."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Ikke-navngivet"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Adgangspunkter"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Adgangspunkter"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nyt adgangspunkt"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Indstillingerne for adgangspunkt (APN) er ikke tilgængelige for denne bruger"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Vil du kopiere til udklipsholder?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiér"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"til <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Generelt"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avanceret"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Generelle indstillinger"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Avancerede indstillinger"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Indstillinger for SIM-kortet \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Send individuelle sms-beskeder til alle modtagere. Der kan kun sendes svar til dig"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Send en enkelt mms-besked til alle modtagere"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Ukendt nummer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Ny besked"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Ny besked."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-vælger"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> er valgt. SIM-vælger"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Rediger emne"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Vælg SIM-kort, eller rediger emne"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Tryk og hold nede for at optage lyd"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Start en ny samtale"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Beskeder"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Liste i Beskeder"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Beskeder"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Ny besked"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Samtaleliste"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Indlæser samtaler"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Indlæser beskeder"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Se flere samtaler"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Se flere beskeder"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Samtalen er slettet"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Samtalen blev slettet. Tryk for at se en anden samtale i Beskeder."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blokeret"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Blokering fjernet"</string>
+ <string name="db_full" msgid="8459265782521418031">"Der er for lidt lagerplads. Nogle data gemmes muligvis ikke."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Vælg vedhæftede filer"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Bekræft valg"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Der er valgt <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Fjern en eller flere vedhæftede filer, og prøv igen."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Du kan prøve at sende din besked, men den leveres muligvis ikke, medmindre du fjerner en eller flere vedhæftede filer."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Du kan kun sende én video pr besked. Fjern flere videoer, og prøv igen."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Beskeder kunne ikke indlæse den vedhæftede fil."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Send alligevel"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Samtalen kunne ikke startes"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> er valgt"</string>
+</resources>
diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml
new file mode 100644
index 0000000..c3e971f
--- /dev/null
+++ b/res/values-de/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"kein Betreff"</item>
+ <item msgid="272485471009191934">"Kein Betreff"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ja"</item>
+ <item msgid="6049132459802288033">"Nein"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Vielen Dank!"</item>
+ <item msgid="4881335087096496747">"Ich stimme zu."</item>
+ <item msgid="2422296858597420738">"Wunderbar"</item>
+ <item msgid="4805581752819452687">"Bin auf dem Weg."</item>
+ <item msgid="4746700499431366214">"OK, ich melde mich später."</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..88b95c3
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"SMS/MMS"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"SMS/MMS"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Konversation auswählen"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Einstellungen"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Nachricht senden"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Anhang hinzufügen"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Hilfe"</string>
+ <string name="welcome" msgid="2857560951820802321">"Willkommen"</string>
+ <string name="skip" msgid="7238879696319945853">"Überspringen"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Weiter &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Weiter"</string>
+ <string name="exit" msgid="1905187380359981199">"Beenden"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Einstellungen &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Einstellungen"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Die SMS/MMS App benötigt Berechtigungen für SMS, Telefon und Kontakte."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Sie können die Berechtigungen unter \"Einstellungen\" &gt; \"Apps\" &gt; \"SMS/MMS\" &gt; \"Berechtigungen\" ändern."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Sie können die Berechtigungen unter \"Einstellungen\" &gt; \"Apps\" &gt; \"SMS/MMS\" &gt; \"Berechtigungen\" ändern."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Häufig genutzte Kontakte"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Alle Kontakte"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"An <xliff:g id="DESTINATION">%s</xliff:g> senden"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Bilder oder Videos aufnehmen"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Bilder von diesem Gerät auswählen"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Audio aufzeichnen"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Foto auswählen"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Die Medien sind ausgewählt."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Die Auswahl der Medien ist aufgehoben."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ausgewählt"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"Bild: <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" msgid="8142175029053370662">"Bild"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Audio aufzeichnen"</string>
+ <string name="action_share" msgid="2143483844803153871">"Teilen"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Gerade eben"</string>
+ <string name="posted_now" msgid="867560789350406701">"Jetzt"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> Stunden</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> Stunde</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> Tage</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> Tag</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> Wochen</item>
+ <item quantity="one">eine Woche</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> Monate</item>
+ <item quantity="one">ein Monat</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> Jahre</item>
+ <item quantity="one">ein Jahr</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Nachricht Klasse 0"</string>
+ <string name="save" msgid="5081141452059463572">"Speichern"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Das Gerät verfügt über wenig Speicherplatz. Die SMS/MMS App löscht ältere Nachrichten automatisch, um Speicherplatz freizugeben."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Der Speicherplatz wird knapp"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Die SMS/MMS App versendet oder empfängt möglicherweise keine Nachrichten, bis mehr Speicherplatz auf Ihrem Gerät zur Verfügung steht."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Geringer SMS-Speicherplatz. Sie müssen eventuell Nachrichten löschen."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Telefonnummer bestätigen"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Mit dieser einmaligen Eingabe stellen Sie sicher, dass die SMS/MMS App Ihre Gruppennachrichten korrekt sendet."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonnummer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Alle Nachrichten mit Medien löschen"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Nachrichten löschen, die älter als <xliff:g id="DURATION">%s</xliff:g> sind"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Nachrichten, die älter als <xliff:g id="DURATION">%s</xliff:g> sind, automatisch löschen"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorieren"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Alle Nachrichten mit Medien löschen?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Nachrichten löschen, die älter als <xliff:g id="DURATION">%s</xliff:g> sind?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Nachrichten löschen, die älter als <xliff:g id="DURATION">%s</xliff:g> sind, und automatisches Löschen aktivieren?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> hat Folgendes gesagt:"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Sie haben Folgendes gesagt:"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Nachricht von <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Sie haben eine Nachricht gesendet."</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Nachricht wird gesendet…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nachricht wurde nicht gesendet. Zum erneuten Senden tippen."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nachricht wurde nicht gesendet. Neuer Versuch…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Erneut senden oder löschen"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Bitte kontaktieren Sie die Notfalldienste per Sprachanruf. Ihre SMS konnte dieses Mal nicht zugestellt werden."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Fehler"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Neue MMS zum Herunterladen"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Neue MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Fehler beim Download"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Für neuen Versuch hier tippen"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Zum Herunterladen tippen"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Herunterladen oder löschen"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Nachricht wird heruntergeladen…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Nachricht abgelaufen oder nicht verfügbar"</string>
+ <string name="mms_info" msgid="3402311750134118165">"Größe: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, läuft ab: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nachricht kann nicht gesendet werden. Der Empfänger ist ungültig."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Dienst im Netzwerk nicht aktiviert"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Nachricht kann aufgrund eines Netzwerkproblems nicht gesendet werden."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Nachricht abgelaufen oder nicht verfügbar"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Kein Betreff)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Unbekannter Absender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Zugestellt"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nachricht \"<xliff:g id="SUBJECT">%1$s</xliff:g>\" von <xliff:g id="FROM">%2$s</xliff:g> konnte nicht heruntergeladen werden."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Datenbankoperation konnte aufgrund unzureichenden Speichers nicht abgeschlossen werden."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Nachricht wurde nicht gesendet."</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Einige Nachrichten in der SMS/MMS App wurden nicht gesendet."</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> Nachrichten in <xliff:g id="CONVERSATIONS">%d</xliff:g> Unterhaltungen</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> Nachricht in einer Unterhaltung</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Nachricht nicht heruntergeladen"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Einige Nachrichten in der SMS/MMS App wurden nicht heruntergeladen."</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> Nachrichten in <xliff:g id="CONVERSATIONS">%d</xliff:g> Unterhaltungen</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> Nachricht in einer Unterhaltung</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"SMS an <xliff:g id="NUMBER">%1$s</xliff:g> nicht gesendet"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Bitte kontaktieren Sie die Notfalldienste per Sprachanruf. Ihre SMS an <xliff:g id="NUMBER">%1$s</xliff:g> konnte dieses Mal nicht zugestellt werden."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> neue Nachrichten</item>
+ <item quantity="one">Neue Nachricht</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Starten"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera nicht verfügbar"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera nicht verfügbar"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Videoaufnahme nicht verfügbar"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Medien können nicht gespeichert werden."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Keine Bildaufnahme möglich"</string>
+ <string name="back" msgid="1477626055115561645">"Zurück"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archiviert"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Löschen"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archivieren"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Aus Archiv entfernen"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Benachrichtigungen deaktivieren"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Benachrichtigungen aktivieren"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Kontakt hinzufügen"</string>
+ <string name="action_download" msgid="7786338136368564146">"Herunterladen"</string>
+ <string name="action_send" msgid="377635240181672039">"Senden"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Löschen"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Diese Nachricht löschen?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Diese Aktion kann nicht rückgängig gemacht werden."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Löschen"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Diese Unterhaltungen löschen?</item>
+ <item quantity="one">Diese Unterhaltung löschen?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Löschen"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Abbrechen"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"An"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Mehrere Bilder auswählen"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Auswahl bestätigen"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Die Audioaufzeichnung ist nicht möglich. Bitte versuchen Sie es erneut."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Die Audiowiedergabe ist nicht möglich. Bitte versuchen Sie es erneut."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Die Audionachricht konnte nicht gespeichert werden. Bitte versuchen Sie es erneut."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Berühren und halten"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Bild"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audioclip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontaktkarte"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Herunterladen"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Per SMS antworten"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Antwort per MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Antworten"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> Teilnehmer</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> Teilnehmer</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ich"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt blockiert und archiviert"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Blockierung des Kontakts aufgehoben &amp; Kontakt aus dem Archiv entfernt"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archiviert"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> aus dem Archiv entfernt"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Benachrichtigungen deaktiviert"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Benachrichtigungen aktiviert"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Bereit. Zum erneuten Senden tippen."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Die SMS/MMS App wurde als Standard-SMS-App festgelegt."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Anhänge verwerfen</item>
+ <item quantity="one">Anhang verwerfen</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audioanhang"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Audioanhang wiedergeben"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausieren"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Nachricht von <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Fehler bei Nachricht von <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Nachricht von <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Nicht gesendete Nachricht an <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Nachricht wird gesendet an <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Fehler bei Nachricht an <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Nachricht an <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Fehler bei Nachricht von <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Nachricht von <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Nicht gesendete Nachricht an <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Nachricht wird gesendet an <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Fehler bei Nachricht an <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Nachricht an <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Uhrzeit: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Fehler bei Nachricht. Zum Wiederholen tippen."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Unterhaltung mit <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Betreff löschen"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Video aufnehmen"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Standbild aufnehmen"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Bild aufnehmen"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Videoaufnahme starten"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Zur Vollbildkamera wechseln"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Zwischen Kamera auf Vorder- und Rückseite wechseln"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Aufnahme beenden und Video anhängen"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Videoaufzeichnung stoppen"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos in der SMS/MMS App"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> Fotos im Album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" gespeichert</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> Foto im Album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" gespeichert</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> Videos im Album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" gespeichert</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> Video im Album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" gespeichert</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> Anhänge im Album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" gespeichert</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> Anhang im Album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" gespeichert</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> Anhänge in \"Downloads\" gespeichert</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> Anhang in \"Downloads\" gespeichert</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> Anhänge wurden gespeichert.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> Anhang wurde gespeichert.</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> Anhänge konnten nicht gespeichert werden.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> Anhang konnte nicht gespeichert werden.</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS-Anhang gespeichert"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Einstellungen"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archiviert"</string>
+ <string name="action_close" msgid="1840519376200478419">"Schließen"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Erweitert"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Fehlersuche"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Benachrichtigungen"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ton"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Lautlos"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrieren"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blockiert"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-Zustellberichte"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Zustellbestätigung für jede gesendete SMS anfordern"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatisch abrufen"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS-Nachrichten automatisch abrufen"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automatischer Roaming-Abruf"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"MMS beim Roaming automatisch abrufen"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"SMS/MMS an Gruppe senden"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Verwenden Sie MMS, um eine einzelne Nachricht an mehrere Empfänger zu versenden."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Standard-SMS-App"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Standard-SMS-App"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Meine Telefonnummer"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Unbekannt"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Töne für ausgehende Nachrichten"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS sichern"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Erhaltene SMS-Rohdaten in externer Speicherdatei sichern"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS sichern"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Erhaltene MMS-Rohdaten in externer Speicherdatei sichern"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Cell Broadcast-Warnungen"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Nachrichtenoptionen"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Text kopieren"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Details ansehen"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Löschen"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Weiterleiten"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Nachrichtendetails"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Typ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"MMS"</string>
+ <string name="from_label" msgid="1947831848146564875">"Von: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"An: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Gesendet: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Empfangen: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Betreff: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Größe: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorität: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Hoch"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Niedrig"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Ausgeblendete Senderadresse"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Die Nachricht kann nicht gesendet werden, weil die Anhänge noch nicht geladen wurden."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Anhang kann nicht geladen werden. Bitte versuchen Sie es erneut."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Netzwerk ist nicht bereit. Bitte versuchen Sie es erneut."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Text löschen"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Zwischen Text- und Zifferneingabe wechseln"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Weitere Teilnehmer hinzufügen"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Teilnehmer bestätigen"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Neue Unterhaltung starten"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Dieses Element auswählen"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Video abspielen"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Personen &amp; Optionen"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debuggen"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Personen &amp; Optionen"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Allgemein"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Personen in dieser Konversation"</string>
+ <string name="action_call" msgid="6596167921517350362">"Anrufen"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Nachricht senden"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Nachricht senden&lt;br/&gt;&lt;small&gt;von <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Fotos senden</item>
+ <item quantity="one">Foto senden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Audioaufzeichnungen senden</item>
+ <item quantity="one">Audioaufzeichnung senden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Videos senden</item>
+ <item quantity="one">Video senden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Kontaktkarten senden</item>
+ <item quantity="one">Kontaktkarte senden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Anhänge senden</item>
+ <item quantity="one">Anhang senden</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> Anhänge zum Senden bereit</item>
+ <item quantity="one">Ein Anhang zum Senden bereit</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Feedback geben"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Im Google Play Store ansehen"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versionsinformationen"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version: %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Open-Source-Lizenzen"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Benachrichtigungen"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Anhanglimit erreicht"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Anhang konnte nicht geladen werden"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Zu Kontakten hinzufügen?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Kontakt hinzufügen"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Betreff"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Betreff: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kontaktkarte wird geladen."</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kontaktkarte kann nicht geladen werden."</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Kontaktkarte ansehen"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> Kontakte</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> Kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktkarten"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Geburtstag"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notizen"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Nachricht weiterleiten"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Antworten"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS deaktiviert"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Um Nachrichten zu senden, legen Sie die SMS/MMS App als Standard-SMS-App fest."</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Legen Sie die SMS/MMS App als Standard-SMS-App fest."</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Ändern"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Um Nachrichten zu erhalten, legen Sie die SMS/MMS App als Standard-SMS-App fest."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Keine bevorzugte SIM für das Senden von SMS ausgewählt"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Diese App wird vom Geräteeigentümer nicht zugelassen."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Zu viele Teilnehmer in der Unterhaltung"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Ungültige Kontakte</item>
+ <item quantity="one">Ungültiger Kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kamerabild kann nicht geladen werden."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Sie: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Entwurf"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Wenn Sie eine neue Unterhaltung beginnen, wird diese hier angezeigt"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Archivierte Unterhaltungen erscheinen hier."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Unterhaltungen werden geladen…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Bild"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audioclip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontaktkarte"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Rückgängig"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Erneut versuchen"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Geben Sie einen Kontaktnamen oder eine Telefonnummer ein, um eine neue Nachricht zu beginnen."</string>
+ <string name="action_block" msgid="9032076625645190136">"Blockieren"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> blockieren"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Blockierung aufheben für <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> blockieren?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Sie erhalten weiterhin Nachrichten von dieser Nummer, werden jedoch nicht mehr benachrichtigt. Diese Unterhaltung wird archiviert."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blockierte Kontakte"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"Blockierung aufheben"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blockierte Kontakte"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Bild aus der Dokumentenbibliothek auswählen"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Nachricht wird gesendet"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Nachricht gesendet"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobilfunkdaten sind deaktiviert. Bitte überprüfen Sie Ihre Einstellungen."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Im Flugmodus können keine Nachrichten gesendet werden."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Die Nachricht konnte nicht gesendet werden."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Nachricht heruntergeladen"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobilfunkdaten sind deaktiviert. Bitte überprüfen Sie Ihre Einstellungen."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Im Flugmodus können keine Nachrichten heruntergeladen werden."</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Die Nachricht konnte nicht heruntergeladen werden."</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Null"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Eins"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Zwei"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Drei"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Vier"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Fünf"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sechs"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sieben"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Acht"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Neun"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Nachricht kann nicht mit <xliff:g id="CARRIERNAME">%1$s</xliff:g> gesendet werden. Fehler <xliff:g id="ERRORCODE">%2$d</xliff:g>."</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Nachricht kann nicht mit einem unbekannten Mobilfunkanbieter gesendet werden. Fehler <xliff:g id="ERRORCODE">%1$d</xliff:g>."</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Nachricht wurde nicht gesendet: Der Dienst ist im Netzwerk nicht aktiviert."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Nachricht nicht gesendet: ungültige Zieladresse"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Nachricht nicht gesendet: ungültige Nachricht"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Nachricht nicht gesendet: nicht unterstützter Inhalt"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Nachricht nicht gesendet: nicht unterstützte Nachricht"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Nachricht nicht gesendet: zu groß"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Neue Nachricht"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ansehen"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Bild"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Keine geeignete App gefunden"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Empfänger entfernen"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Neue Nachricht"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Abbrechen"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Zugangspunkt bearbeiten"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nicht definiert"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Name"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-Proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-Port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-Typ"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN löschen"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Neuer APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Speichern"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Verwerfen"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Das Namensfeld darf nicht leer sein."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN darf nicht leer sein."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Das MCC-Feld muss 3 Zeichen enthalten."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Das MNC-Feld muss 2 oder 3 Zeichen enthalten."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Standard-APN-Einstellungen werden wiederhergestellt"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Auf Standard zurücksetzen"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Zurücksetzen auf Standard-APN-Einstellungen abgeschlossen"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Unbenannt"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Zugriffspunktnamen"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Neuer APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Die Einstellungen für den Zugangspunkt sind für diesen Nutzer nicht verfügbar."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"In Zwischenablage kopieren?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopieren"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"an <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Allgemein"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Erweitert"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Allgemeine Einstellungen"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Erweiterte Einstellungen"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Einzelne SMS-Nachrichten an alle Empfänger senden. Die Antworten erhalten nur Sie."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Einzelne MMS an alle Empfänger senden"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Unbekannte Nummer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Neue Nachricht"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Neue Nachricht"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-Auswahl"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ausgewählt, SIM-Auswahl"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Betreff bearbeiten"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM auswählen oder Betreff bearbeiten"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Für Audioaufnahme berühren und halten"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Neue Unterhaltung starten"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS/MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Liste der Unterhaltungen"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Nachricht"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Neue Nachricht"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Konversationsliste"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Unterhaltungen werden geladen"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Nachrichten werden geladen"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Weitere Unterhaltungen anzeigen"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Weitere Nachrichten ansehen"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Konversation gelöscht"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Unterhaltung gelöscht. Tippen Sie hier, um eine andere Unterhaltung zu sehen."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blockiert"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Blockierung aufgehoben"</string>
+ <string name="db_full" msgid="8459265782521418031">"Wenig freier Speicherplatz. Einige Daten gehen möglicherweise verloren."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Anhänge auswählen"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Auswahl bestätigen"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ausgewählt"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Bitte entfernen Sie mindestens einen Anhang und versuchen Sie es erneut."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Sie können versuchen, die Nachricht zu senden, sie wird jedoch möglicherweise nicht zugestellt, wenn Sie nicht mindestens einen Anhang entfernen."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Pro Nachricht kann nur ein Video gesendet werden. Bitte entfernen Sie überzählige Videos und versuchen Sie es erneut."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Die SMS/MMS App konnte den Anhang nicht laden."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Trotzdem senden"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Die Unterhaltung konnte nicht gestartet werden."</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ausgewählt"</string>
+</resources>
diff --git a/res/values-el/arrays.xml b/res/values-el/arrays.xml
new file mode 100644
index 0000000..280d8e9
--- /dev/null
+++ b/res/values-el/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"χωρίς θέμα"</item>
+ <item msgid="272485471009191934">"χωρίς θέμα"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ναι"</item>
+ <item msgid="6049132459802288033">"Όχι"</item>
+ <item msgid="3084376867445867895">"ΟΚ"</item>
+ <item msgid="3155097332660174689">"Χαχα"</item>
+ <item msgid="2611328818571146775">"Σας ευχαριστούμε"</item>
+ <item msgid="4881335087096496747">"Συμφωνώ"</item>
+ <item msgid="2422296858597420738">"Ωραία"</item>
+ <item msgid="4805581752819452687">"Έρχομαι"</item>
+ <item msgid="4746700499431366214">"Εντάξει, θα επικοινωνήσω μαζί σου αργότερα"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..feade7b
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Ανταλλαγή μηνυμάτων"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Ανταλλαγή μηνυμάτων"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Επιλογή συνομιλίας"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Ρυθμίσεις"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Αποστολή μηνύματος"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Προσθήκη συνημμένου αρχείου"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Βοήθεια"</string>
+ <string name="welcome" msgid="2857560951820802321">"Καλώς ήρθατε"</string>
+ <string name="skip" msgid="7238879696319945853">"Παράλειψη"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Επόμενο &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Επόμενο"</string>
+ <string name="exit" msgid="1905187380359981199">"Έξοδος"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Ρυθμίσεις &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Ρυθμίσεις"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Η Ανταλλαγή μηνυμάτων χρειάζεται άδεια για SMS, Τηλέφωνο και Επαφές."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Μπορείτε να αλλάξετε τα δικαιώματα στις Ρυθμίσεις &gt; Εφαρμογές &gt; Ανταλλαγή μηνυμάτων &gt; Δικαιώματα."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Μπορείτε να αλλάξετε τα δικαιώματα στις Ρυθμίσεις, Εφαρμογές, Μηνύματα, Δικαιώματα."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Συχνή επικοινωνία"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Όλες οι επαφές"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Αποστολή σε <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Λήψη εικόνων ή βίντεο"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Επιλέξτε εικόνες από αυτήν τη συσκευή"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Ηχογράφηση"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Επιλογή φωτογραφίας"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Το μέσο είναι επιλεγμένο."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Το μέσο δεν είναι επιλεγμένο."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> επιλεγμένα"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"εικόνα <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" msgid="8142175029053370662">"εικόνα"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Ηχογράφηση"</string>
+ <string name="action_share" msgid="2143483844803153871">"Κοινή χρήση"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Μόλις τώρα"</string>
+ <string name="posted_now" msgid="867560789350406701">"Τώρα"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> λεπτά</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> λεπτό</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ώρες</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ώρα</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ημέρες</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ημέρα</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> εβδομάδες</item>
+ <item quantity="one">μία εβδομάδα</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> μήνες</item>
+ <item quantity="one">ένας μήνας</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> έτη</item>
+ <item quantity="one">ένα έτος</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Μήνυμα κλάσης 0"</string>
+ <string name="save" msgid="5081141452059463572">"Αποθήκευση"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Ο αποθηκευτικός χώρος της συσκευής είναι περιορισμένος. Η Ανταλλαγή μηνυμάτων θα διαγράψει αυτόματα τα παλαιότερα μηνύματα για να απελευθερώσει χώρο."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Ο χώρος αποθήκευσης εξαντλείται"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Η Ανταλλαγή μηνυμάτων μπορεί να μην κάνει αποστολή ή λήψη μηνυμάτων μέχρι να ελευθερώσετε περισσότερο χώρο στη συσκευή σας."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Περιορισμένος αποθηκευτικός χώρος SMS. Μπορεί να χρειαστεί να διαγράψετε μηνύματα."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Επιβεβαιώστε τον αριθμό τηλεφώνου σας"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Αυτό το μοναδικό βήμα θα εξασφαλίσει ότι η Ανταλλαγή μηνυμάτων θα παραδώσει σωστά τα ομαδικά μηνύματά σας."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Αριθμός τηλεφώνου"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Διαγραφή όλων των μηνυμάτων με πολυμέσα"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Διαγραφή μηνυμάτων πριν από τις <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Αυτόματη διαγραφή των μηνυμάτων πριν από <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Παράβλεψη"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Να γίνει διαγραφή όλων των μηνυμάτων που περιέχουν πολυμέσα;"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Να γίνει διαγραφή των μηνυμάτων πριν από τις <xliff:g id="DURATION">%s</xliff:g>;"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Να γίνει αυτόματη διαγραφή των μηνυμάτων πριν από <xliff:g id="DURATION">%s</xliff:g> και ενεργοποίηση της αυτόματης διαγραφής;"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"Ο χρήστης <xliff:g id="SENDER">%s</xliff:g> είπε"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Είπατε"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Μήνυμα από το χρήστη <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Στείλατε ένα μήνυμα"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Αποστολή…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Δεν εστάλη. Αγγίξτε για να δοκιμάσετε ξανά."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Δεν εστάλη. Επανάληψη…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Εκ νέου αποστολή ή διαγραφή"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Κάντε μια φωνητική κλήση στις υπηρεσίες έκτακτης ανάγκης. Δεν ήταν δυνατή η παράδοση του μηνύματος κειμένου σας αυτήν τη φορά."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Αποτυχία"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Νέο μήνυμα MMS για λήψη"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Νέο μήνυμα MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Δεν ήταν δυνατή η λήψη"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Αγγίξτε για να δοκιμάσετε ξανά"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Αγγίξτε για λήψη"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Λήψη ή διαγραφή"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Λήψη…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Το μήνυμα έληξε ή δεν είναι διαθέσιμο"</string>
+ <string name="mms_info" msgid="3402311750134118165">"μέγεθος: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, λήξη: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Δεν είναι δυνατή η αποστολή. Ο παραλήπτης δεν είναι έγκυρος."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Η υπηρεσία δεν έχει ενεργοποιηθεί στο δίκτυο"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Δεν ήταν δυνατή η αποστολή λόγω προβλήματος στο δίκτυο"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Το μήνυμα έληξε ή δεν είναι διαθέσιμο."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Χωρίς θέμα)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Άγνωστος αποστολέας"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Παραδόθηκε"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Δεν ήταν δυνατή η λήψη του μηνύματος <xliff:g id="SUBJECT">%1$s</xliff:g> από <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Δεν ήταν δυνατή η ολοκλήρωση της λειτουργίας της βάσης δεδομένων λόγω ανεπαρκούς μνήμης"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Το μήνυμα δεν εστάλη"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Ορισμένα μηνύματα δεν εστάλησαν στην Ανταλλαγή μηνυμάτων"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> μηνύματα σε <xliff:g id="CONVERSATIONS">%d</xliff:g> συνομιλίες</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> μηνύματα σε μία συνομιλία</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Δεν έγινε λήψη του μηνύματος"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Δεν έγινε λήψη ορισμένων μηνυμάτων στην Ανταλλαγή μηνυμάτων"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> μηνύματα σε <xliff:g id="CONVERSATIONS">%d</xliff:g> συνομιλίες</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> μηνύματα σε μία συνομιλία</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Το μήνυμα στον αριθμό <xliff:g id="NUMBER">%1$s</xliff:g> δεν εστάλη"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Κάντε μια φωνητική κλήση στις υπηρεσίες έκτακτης ανάγκης. Δεν ήταν δυνατή η παράδοση του μηνύματος κειμένου σας στον αριθμό <xliff:g id="NUMBER">%1$s</xliff:g> αυτήν τη φορά."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> νέα μηνύματα</item>
+ <item quantity="one">Νέο μήνυμα</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Έναρξη"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Η φωτογραφική μηχανή δεν είναι διαθέσιμη"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Η φωτογραφική μηχανή δεν είναι διαθέσιμη"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Η εγγραφή βίντεο δεν είναι διαθέσιμη"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Δεν είναι δυνατή η αποθήκευση πολυμέσων"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Δεν είναι δυνατή η λήψη φωτογραφίας"</string>
+ <string name="back" msgid="1477626055115561645">"Πίσω"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Αρχειοθετημένο"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Διαγραφή"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Αρχειοθέτηση"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Κατάργηση αρχειοθέτησης"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Απενεργοποίηση ειδοποιήσεων"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Ενεργοποίηση ειδοποιήσεων"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Προσθήκη επαφής"</string>
+ <string name="action_download" msgid="7786338136368564146">"Λήψη"</string>
+ <string name="action_send" msgid="377635240181672039">"Αποστολή"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Διαγραφή"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Να διαγραφεί αυτό το μήνυμα;"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Διαγραφή"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Διαγραφή αυτών των συνομιλιών;</item>
+ <item quantity="one">Διαγραφή αυτής της συνομιλίας;</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Διαγραφή"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Ακύρωση"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Προς"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Επιλογή πολλαπλών εικόνων"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Επιβεβαίωση επιλογής"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Δεν είναι δυνατή η αποθήκευση ήχου. Δοκιμάστε ξανά."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Δεν είναι δυνατή η αναπαραγωγή ήχου. Δοκιμάστε ξανά."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Δεν ήταν δυνατή η αποθήκευση ήχου. Δοκιμάστε ξανά."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Άγγιγμα &amp; κράτημα"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Εικόνα"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Κλιπ ήχου"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Βίντεο"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Κάρτα επικοινωνίας"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Λήψη"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Απάντηση μέσω SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Απάντηση μέσω MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Απάντηση"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> συμμετέχοντες</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> συμμετέχων</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Εγώ"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Η επαφή αποκλείστηκε και αρχειοθετήθηκε"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Έγινε κατάργηση αποκλεισμού και αρχειοθέτησης της επαφής"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Έχουν αρχειοθετηθεί <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> αφαιρέθηκαν από το αρχείο"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Οι ειδοποιήσεις απενεργοποιήθηκαν"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Οι ειδοποιήσεις ενεργοποιήθηκαν"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Έχουν ρυθμιστεί όλα. Αγγίξτε ξανά την επιλογή \"Αποστολή\"."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Η Ανταλλαγή μηνυμάτων ορίστηκε επιτυχώς ως η προεπιλεγμένη εφαρμογή SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Απόρριψη συνημμένων</item>
+ <item quantity="one">Απόρριψη συνημμένου</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Συνημμένο ήχου"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Αναπαραγωγή συνημμένου ήχου"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Παύση"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Μήνυμα από <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Αποτυχία μηνύματος από <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Μήνυμα από <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Μη απεσταλμένο μήνυμα προς <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Αποστολή μηνύματος προς <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Αποτυχία μηνύματος προς <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Μήνυμα προς <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Αποτυχία μηνύματος από <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Μήνυμα από <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Μη απεσταλμένο μήνυμα προς <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Αποστολή μηνύματος σε <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Αποτυχία μηνύματος προς <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Μήνυμα προς <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ώρα: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Αποτυχία μηνύματος. Αγγίξτε για επανάληψη."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Συνομιλία με <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Διαγραφή θέματος"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Λήψη βίντεο"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Τραβήξτε μια στατική εικόνα"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Τραβήξτε μια φωτογραφία"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Έναρξη εγγραφής βίντεο"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Εναλλαγή σε κάμερα πλήρους οθόνης"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Εναλλαγή μεταξύ μπροστινής και της πίσω φωτογραφικής μηχανής"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Διακοπή εγγραφής και επισύναψη βίντεο"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Διακοπή εγγραφής βίντεο"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Φωτογραφίες Ανταλλαγής μηνυμάτων"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> φωτογραφίες αποθηκεύτηκαν στο λεύκωμα \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> φωτογραφία αποθηκεύτηκε στο λεύκωμα \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> βίντεο αποθηκεύτηκαν στο λεύκωμα \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> βίντεο αποθηκεύτηκε στο λεύκωμα \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> συνημμένα αποθηκεύτηκαν στο λεύκωμα \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> συνημμένο αποθηκεύτηκε στο λεύκωμα \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> συνημμένα αποθηκεύτηκαν στις \"Λήψεις\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> συνημμένο αποθηκεύτηκε στις \"Λήψεις\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">Αποθηκεύτηκε <xliff:g id="QUANTITY_1">%d</xliff:g> συνημμένων αρχείων</item>
+ <item quantity="one">Αποθηκεύτηκε <xliff:g id="QUANTITY_0">%d</xliff:g> συνημμένο αρχείο</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Δεν ήταν δυνατή η αποθήκευση <xliff:g id="QUANTITY_1">%d</xliff:g> συνημμένων αρχείων</item>
+ <item quantity="one">Δεν ήταν δυνατή η αποθήκευση <xliff:g id="QUANTITY_0">%d</xliff:g> συνημμένου αρχείου</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Αποθηκευμένο συνημμένο MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Ρυθμίσεις"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Αρχειοθετημένο"</string>
+ <string name="action_close" msgid="1840519376200478419">"Κλείσιμο"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Σύνθετες ρυθμίσεις"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Εντοπισμός σφαλμάτων"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Ειδοποιήσεις"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ήχος"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Σίγαση"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Δόνηση"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Αποκλεισμένο"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Αναφορές παράδοσης SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Αίτηση αναφοράς παράδοσης για κάθε SMS που στέλνετε"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Αυτόματη ανάκτηση"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Αυτόματη ανάκτηση μηνυμάτων MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Αυ/τη ανάκτηση περιαγ."</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Αυτόματη ανάκτηση MMS κατά την περιαγωγή"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Ομαδική ανταλλαγή μηνυμάτων"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Χρησιμοποιήστε το MMS για να στείλετε ένα μήνυμα σε πολλούς παραλήπτες"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Προεπιλεγμένη εφαρμογή SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Προεπιλεγμένη εφαρμογή SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Ο αριθμός τηλεφώνου σας"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Άγνωστo"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Ήχοε εξερχόμενου μηνύματος"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Απόρριψη SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Απόρριψη μη επεξεργασμένων δεδομένων ληφθέντων SMS σε αρχείο εξωτερικού αποθηκευτικού χώρου"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Απόρριψη MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Απόρριψη μη επεξεργασμένων δεδομένων ληφθέντων MMS σε αρχείο εξωτερικού αποθηκευτικού χώρου"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Ασύρματες ειδοποιήσεις"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Επιλογές μηνύματος"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Αντιγραφή κειμένου"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Προβολή λεπτομερειών"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Διαγραφή"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Προώθηση"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Λεπτομέρειες μηνύματος"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Τύπος: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Μήνυμα κειμένου"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Μήνυμα πολυμέσων"</string>
+ <string name="from_label" msgid="1947831848146564875">"Από: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Προς: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Εστάλη: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Ελήφθη: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Θέμα: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Μέγεθος: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Προτεραιότητα: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Υψηλή"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Κανονική"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Χαμηλή"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Διεύθυνση κρυφού αποστολέα"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Δεν είναι δυνατή η αποστολή του μηνύματος ενώ φορτώνουν τα συνημμένα."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Δεν είναι δυνατή η φόρτωση της επισύναψης. Δοκιμάστε ξανά."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Το δίκτυο δεν είναι έτοιμο. Δοκιμάστε ξανά."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Διαγραφή κειμένου"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Εναλλαγή μεταξύ εισαγωγής κειμένου και αριθμών"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Προσθήκη περισσότερων συμμετεχόντων"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Επιβεβαίωση συμμετεχόντων"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Έναρξη νέας συνομιλίας"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Επιλέξτε αυτό το στοιχείο"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Αναπαραγωγή βίντεο"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Άτομα και επιλογές"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Εντοπισμός σφαλμάτων"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Άτομα και επιλογές"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Γενικά"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Άτομα σε αυτήν τη συνομιλία"</string>
+ <string name="action_call" msgid="6596167921517350362">"Πραγματοποίηση κλήσης"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Αποστολή μηνύματος"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Αποστολή μηνύματος&lt;br/&gt;&lt;small&gt;από <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Αποστολή φωτογραφιών</item>
+ <item quantity="one">Αποστολή φωτογραφίας</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Αποστολή αρχείων ήχου</item>
+ <item quantity="one">Αποστολή αρχείου ήχου</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Αποστολή αρχείων βίντεο</item>
+ <item quantity="one">Αποστολή αρχείου βίντεο</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Αποστολή καρτών επαφών</item>
+ <item quantity="one">Αποστολή κάρτας επαφής</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Αποστολή συνημμένων</item>
+ <item quantity="one">Αποστολή συνημμένου</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> συνημμένα έτοιμα για αποστολή</item>
+ <item quantity="one">Ένα συνημμένο έτοιμο για αποστολή</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Αποστολή σχολίων"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Προβολή στο Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Πληροφορίες έκδοσης"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Έκδοση %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Άδειες λογισμικού ανοικτού κώδικα"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Ειδοποιήσεις"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Συμπλήρωση ορίου συνημμένων"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Αποτυχία φόρτωσης συνημμένου."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Προσθήκη στις επαφές;"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Προσθήκη επαφής"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Θέμα"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Θέμα: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Φόρτωση κάρτας επαφής"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Δεν ήταν δυνατή η φόρτωση της κάρτας επαφής"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Προβολή κάρτας επαφής"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> επαφές</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> επαφή</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Κάρτες επαφών"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Γενέθλια"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Σημειώσεις"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Προώθηση μηνύματος"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Απάντηση"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Τα SMS απενεργοποιήθηκαν"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Για αποστολή, ορίστε την Ανταλλαγή μηνυμάτων ως προεπιλεγμένη εφαρμογή SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Ορισμός Ανταλλαγής μηνυμάτων ως προεπιλεγμένης εφαρμογής SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Αλλαγή"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Για να λαμβάνετε μηνύματα, ορίστε την Ανταλλαγή μηνυμάτων ως προεπιλεγμένη εφαρμογή SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Δεν έχει επιλεγεί προτιμώμενη SIM για την αποστολή μηνυμάτων SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Αυτή η εφαρμογή δεν επιτρέπεται από τον κάτοχο της συσκευής."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ΟΚ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Πάρα πολλοί συμμετέχοντες σε μια συνομιλία"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Μη έγκυρες επαφές</item>
+ <item quantity="one">Μη έγκυρη επαφή</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Δεν ήταν δυνατή η φόρτωση της εικόνας φωτογραφικής μηχανής"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Εσείς: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Πρόχειρο"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Αφού ξεκινήσετε μια νέα συνομιλία, θα εμφανίζεται εδώ"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Οι αρχειοθετημένες συνομιλίες εμφανίζονται εδώ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Φόρτωση συνομιλιών…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Εικόνα"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Κλιπ ήχου"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Βίντεο"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Κάρτα επαφής"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Αναίρεση"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Επανάληψη"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Εισαγάγετε ένα όνομα επαφής ή έναν αριθμό τηλεφώνου για να ξεκινήσετε ένα νέο μήνυμα"</string>
+ <string name="action_block" msgid="9032076625645190136">"Αποκλεισμός"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Αποκλεισμός επαφής <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Κατάργηση αποκλεισμού της επαφής <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Αποκλεισμός <xliff:g id="DESTINATION">%s</xliff:g>;"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Θα εξακολουθήσετε να λαμβάνετε μηνύματα από αυτόν τον αριθμό, αλλά δεν θα ειδοποιείστε πια. Αυτή η συνομιλία θα αρχειοθετηθεί."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Αποκλεισμένες επαφές"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ΚΑΤΑΡΓΗΣΗ ΑΠΟΚΛΕΙΣΜΟΥ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Αποκλεισμένες επαφές"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Επιλέξτε μια εικόνα από τη βιβλιοθήκη εγγράφων"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Αποστολή μηνύματος"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Το μήνυμα εστάλη"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Τα δεδομένα κινητής τηλεφωνίας έχουν απενεργοποιηθεί. Ελέγξτε τις ρυθμίσεις σας."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Δεν είναι δυνατή η αποστολή μηνυμάτων σε Λειτουργία πτήσης"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Αδυναμία αποστολής μηνύματος"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Έγινε λήψη του μηνύματος"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Τα δεδομένα κινητής τηλεφωνίας απενεργοποιήθηκαν. Ελέγξτε τις ρυθμίσεις σας."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Δεν είναι δυνατή η λήψη μηνυμάτων σε λειτουργία πτήσης"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Δεν ήταν δυνατή η λήψη του μηνύματος"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Μηδέν"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Ένα"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Δύο"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Τρία"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Τέσσερα"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Πέντε"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Έξι"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Επτά"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Οκτώ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Εννέα"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Δεν είναι δυνατή η αποστολή του μηνύματος με <xliff:g id="CARRIERNAME">%1$s</xliff:g>, σφάλμα <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Δεν είναι δυνατή η αποστολή του μηνύματος με άγνωστη εταιρεία κινητής τηλεφωνίας, σφάλμα <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Προώθ.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Το μήνυμα δεν εστάλη: Η υπηρεσία δεν ενεργοποιήθηκε στο δίκτυο"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Το μήνυμα δεν εστάλη: μη έγκυρη διεύθυνση προορισμού"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Το μήνυμα δεν εστάλη: μη έγκυρο μήνυμα"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Το μήνυμα δεν εστάλη: μη υποστηριζόμενο περιεχόμενο"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Το μήνυμα δεν εστάλη: μη υποστηριζόμενο μήνυμα"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Το μήνυμα δεν εστάλη: πάρα πολύ μεγάλο"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Νέο μήνυμα"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Προβολή"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Εικόνα"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Δεν ήταν δυνατή η εύρεση της κατάλληλης εφαρμογής"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Κατάργηση παραλήπτη"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Νέο μήνυμα"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Ακύρωση"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Επεξ/σία σημ. πρόσβ."</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Δεν έχει ρυθμιστεί"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Όνομα"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Διακομιστής μεσολάβησης MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Θύρα MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Τύπος APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Διαγραφή APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Νέο APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Αποθήκευση"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Απόρριψη"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Το πεδίο Όνομα δεν μπορεί να είναι κενό."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Το APN δεν μπορεί να είναι κενό."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Το πεδίο MCC πρέπει να αποτελείται από 3 ψηφία."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Το πεδίο MNC πρέπει να αποτελείται από 2 ή 3 ψηφία."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Επαναφορά προεπιλεγμένων ρυθμίσεων APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Επαναφορά προεπιλογών"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Η επαναφορά των εργοστασιακών ρυθμίσεων APN ολοκληρώθηκε."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Χωρίς τίτλο"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Ονόματα σημείου πρόσβασης"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Νέο APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Οι ρυθμίσεις ονόματος σημείου πρόσβασης δεν είναι διαθέσιμες γι\' αυτόν το χρήστη"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Αντιγραφή στο πρόχειρο;"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Αντιγραφή"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"σε <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Γενικά"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Σύνθετες ρυθμίσεις"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Γενικές ρυθμίσεις"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Σύνθετες ρυθμίσεις"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Αποστολή μεμονωμένων μηνυμάτων SMS σε όλους τους παραλήπτες. Μόνο εσείς θα λαμβάνετε τις απαντήσεις"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Αποστολή μεμονωμένου MMS σε όλους τους παραλήπτες"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Άγνωστος αριθμός"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Νέο μήνυμα"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Νέο μήνυμα."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Επιλογέας SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Επιλέχτηκε <xliff:g id="SIM_0">%1$s</xliff:g>, επιλογέας SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Επεξεργασία θέματος"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Επιλέξτε SIM ή επεξεργαστείτε το θέμα"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Πατήστε παρατεταμένα για εγγραφή ήχου"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Έναρξη νέας συνομιλίας"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Ανταλλαγή μηνυμάτων"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Λίστα Ανταλλαγής μηνυμάτων"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Ανταλλαγή μηνυμάτων"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Νέο μήνυμα"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Λίστα συνομιλιών"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Φόρτωση συνομιλιών"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Φόρτωση μηνυμάτων"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Προβολή περισσότερων συνομιλιών"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Εμφάνιση περισσότερων μηνυμάτων"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Η συνομιλία διαγράφηκε"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Διαγραφή συνομιλίας. Αγγίξτε για να εμφανίσετε μια διαφορετική συνομιλία στην Ανταλλαγή μηνυμάτων"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Αποκλείστηκε"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Ο αποκλεισμός καταργήθηκε"</string>
+ <string name="db_full" msgid="8459265782521418031">"Ο αποθηκευτικός χώρος είναι περιορισμένος. Ορισμένα στοιχεία ενδέχεται να χαθούν."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Επιλέξτε συνημμένα"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Επιβεβαίωση επιλογής"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> επιλ/μένα"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Καταργήστε ένα ή περισσότερα συνημμένα και δοκιμάστε ξανά."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Μπορείτε να δοκιμάσετε να στείλετε το μήνυμά σας, αλλά ενδέχεται να μην παραδοθεί αν δεν καταργήσετε ένα ή περισσότερα συνημμένα."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Μπορείτε να στείλετε μόνο ένα βίντεο ανά μήνυμα. Καταργήστε τα επιπλέον βίντεο και δοκιμάστε ξανά."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Αποτυχία φόρτωσης συνημμένου από την Ανταλλαγή μηνυμάτων."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Αποστολή ούτως ή άλλως"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Δεν ήταν δυνατή η έναρξη της συνομιλίας"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Επιλέχτηκε <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-en-rAU/arrays.xml b/res/values-en-rAU/arrays.xml
new file mode 100644
index 0000000..94d5aff
--- /dev/null
+++ b/res/values-en-rAU/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"no subject"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Yes"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Thanks"</item>
+ <item msgid="4881335087096496747">"I agree"</item>
+ <item msgid="2422296858597420738">"Nice"</item>
+ <item msgid="4805581752819452687">"On my way"</item>
+ <item msgid="4746700499431366214">"OK, let me get back to you later"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..11048a5
--- /dev/null
+++ b/res/values-en-rAU/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Messaging"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Messaging"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Select conversation"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Settings"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Send Message"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Add an attachment"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Help"</string>
+ <string name="welcome" msgid="2857560951820802321">"Welcome"</string>
+ <string name="skip" msgid="7238879696319945853">"Skip"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Next &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Next"</string>
+ <string name="exit" msgid="1905187380359981199">"Exit"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Settings &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Settings"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaging needs permission to SMS, Phone and Contacts."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"You can change permissions in Settings &gt; Apps &gt; Messaging &gt; Permissions."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"You can change permissions in Settings, Apps, Messaging, Permissions."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frequents"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"All contacts"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Send to <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capture pictures or video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Choose images from this device"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Record audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Choose photo"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"The media is selected."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"The media is unselected."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> selected"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"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" msgid="8142175029053370662">"image"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Record audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Share"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Just now"</string>
+ <string name="posted_now" msgid="867560789350406701">"Now"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> mins</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hours</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hour</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> days</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> day</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> weeks</item>
+ <item quantity="one">a week</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> months</item>
+ <item quantity="one">a month</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> years</item>
+ <item quantity="one">a year</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Class 0 message"</string>
+ <string name="save" msgid="5081141452059463572">"Save"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Device is low on space. Messaging will automatically delete older messages to free up space."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Storage space running out"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Messaging might not send or receive messages until more space is available on your device."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Low SMS storage. You may need to delete messages."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirm your phone number"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"This one-off step will ensure Messaging will deliver your group messages properly."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Phone number"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Delete all messages with media"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Auto-delete messages older than <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignore"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Delete all messages with media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g> and turn on auto-delete?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> said"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"You said"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Message from <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"You sent a message"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Sending…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Not sent. Touch to try again."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Not sent. Trying again…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Resend or delete"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Please make a voice call to emergency services. Your text message could not be delivered at this time."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Failed"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"New MMS message to download"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"New MMS message"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Couldn\'t download"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Touch to try again"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Touch to download"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Download or delete"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Downloading…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Message expired or not available"</string>
+ <string name="mms_info" msgid="3402311750134118165">"size: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Can\'t send. Recipient not valid."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Service not activated on network"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Couldn\'t send due to network problem"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Message expired or not available"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(No subject)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Unknown sender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Delivered"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Couldn\'t download message <xliff:g id="SUBJECT">%1$s</xliff:g> from <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Couldn\'t complete database operation due to low memory"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Message not sent"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Some messages not sent in Messaging"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messages in one conversation</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Message not downloaded"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Some messages not downloaded in Messaging"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messages in one conversation</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Message to <xliff:g id="NUMBER">%1$s</xliff:g> not sent"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Please make a voice call to emergency services. Your text message to <xliff:g id="NUMBER">%1$s</xliff:g> could not be delivered at this time."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> new messages</item>
+ <item quantity="one">New message</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Start"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Camera not available"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Camera not available"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video capture not available"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Can\'t save media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Can\'t take picture"</string>
+ <string name="back" msgid="1477626055115561645">"Back"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archived"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Delete"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archive"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Unarchive"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Turn off notifications"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Turn on notifications"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Add contact"</string>
+ <string name="action_download" msgid="7786338136368564146">"Download"</string>
+ <string name="action_send" msgid="377635240181672039">"Send"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Delete"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Delete this message"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"This action cannot be undone."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Delete"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Delete these conversations?</item>
+ <item quantity="one">Delete this conversation?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Delete"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancel"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"To"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Select multiple images"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirm selection"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Can\'t record audio. Try again."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Can\'t play audio. Try again."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Couldn\'t save audio. Try again."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Touch &amp; hold"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Picture"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio clip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Contact card"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Download"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Reply via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Reply via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Reply"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participants</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participant</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Me"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contact blocked &amp; archived"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contact unblocked &amp; unarchived"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archived"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> unarchived"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notifications turned off"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notifications turned on"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"All set. Touch Send again."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Messaging successfully set as the default SMS app."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Discard attachments</item>
+ <item quantity="one">Discard attachment</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audio attachment"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Play audio attachment"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Failed message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Unsent message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sending message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Failed message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Failed message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Unsent message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sending message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Failed message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Failed message. Touch to retry."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversation with <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Delete subject"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capture video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capture a still image"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Take picture"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Start recording video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Switch to full screen camera"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Switch between front and back camera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stop recording and attach video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Stop recording video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging photos"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> photos saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> photo saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videos saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> attachments saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> attachments saved to \"Downloads\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved to \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> attachments saved</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Couldn\'t save <xliff:g id="QUANTITY_1">%d</xliff:g> attachments</item>
+ <item quantity="one">Couldn\'t save <xliff:g id="QUANTITY_0">%d</xliff:g> attachment</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Saved MMS attachment"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Settings"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archived"</string>
+ <string name="action_close" msgid="1840519376200478419">"Close"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Advanced"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Debug"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifications"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Sound"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silent"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrate"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blocked"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS delivery reports"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Request a delivery report for each SMS that you send"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Auto-retrieve"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automatically retrieve MMS messages"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Roaming auto-retrieve"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatically retrieve MMS when roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Group messaging"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Use MMS to send a single message when there are multiple recipients"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Default SMS app"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Default SMS app"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Your phone number"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Unknown"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Outgoing message sounds"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Dump SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Dump received SMS raw data into external storage file"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Dump MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Dump received MMS raw data into external storage file"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Wireless alerts"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Message options"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copy text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"View details"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Delete"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Forward"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Message details"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Text message"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedia message"</string>
+ <string name="from_label" msgid="1947831848146564875">"From: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"To: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Sent: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Received: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subject: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Size: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priority: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"High"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Low"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Hidden sender address"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Can\'t send message while loading attachments."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Can\'t load attachment. Try again."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Network is not ready. Try again."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Delete text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Switch between entering text and numbers"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Add more participants"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirm participants"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Start new conversation"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Select this item"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Play video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"People &amp; Options"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debug"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"People &amp; Options"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"General"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"People in this conversation"</string>
+ <string name="action_call" msgid="6596167921517350362">"Make a call"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Send message"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Send message&lt;br/&gt;&lt;small&gt;from <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Send photos</item>
+ <item quantity="one">Send photo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Send audios</item>
+ <item quantity="one">Send audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Send videos</item>
+ <item quantity="one">Send video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Send contact cards</item>
+ <item quantity="one">Send contact card</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Send attachments</item>
+ <item quantity="one">Send attachment</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> attachments ready to send</item>
+ <item quantity="one">One attachment ready to send</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Send feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"View in Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Version info"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Open-source licences"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifications"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Attachment limit reached"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Failed to load attachment."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Add to Contacts?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Add Contact"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subject"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subject: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Loading contact card"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Couldn\'t load contact card"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"View contact card"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contacts</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contact</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Contact cards"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Birthday"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notes"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Forward message"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Reply"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS disabled"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"To send, set Messaging as the default SMS app"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Set Messaging as the default SMS app"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Change"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"To receive messages, set Messaging as the default SMS app"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"No preferred SIM selected for sending SMS messages"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"This app isn\'t allowed by the device owner."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Too many participants in a conversation"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Invalid contacts</item>
+ <item quantity="one">Invalid contact</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Couldn\'t load camera image"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"You: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draft"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Once you start a new conversation, you’ll see it listed here"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Archived conversations appear here"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Loading conversations…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Picture"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio clip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"In-stream video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Contact card"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Undo"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Retry"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Enter a contact name or phone number to start a new message"</string>
+ <string name="action_block" msgid="9032076625645190136">"Block"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Block <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Unblock <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Block <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blocked contacts"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"UNBLOCK"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blocked contacts"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Choose image from document library"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sending message"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Message sent"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobile data is turned off. Check your settings."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Can\'t send messages in Aeroplane mode"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Message could not be sent"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Message downloaded"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobile data is turned off. Check your settings."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Can\'t download messages in Aeroplane mode"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Message could not be downloaded"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"One"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Two"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Three"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Four"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Five"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Six"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Seven"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Eight"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nine"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Can\'t send message with <xliff:g id="CARRIERNAME">%1$s</xliff:g>, error <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Can\'t send message with unknown operator, error <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Message not sent: service not activated on network"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Message not sent: invalid destination address"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Message not sent: invalid message"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Message not sent: unsupported content"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Message not sent: unsupported message"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Message not sent: too large"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"New message"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"View"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Image"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Could not find an appropriate application"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Remove recipient"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"New message"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancel"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edit access point"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Not set"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Named"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN type"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Delete APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"New APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Save"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Discard"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"The Name field can\'t be empty."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"The APN cannot be empty."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC field must be 3 digits."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC field must be 2 or 3 digits."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restoring default APN settings."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Reset to default"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Reset default APN settings completed"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Untitled"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Access point names"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"New APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Access Point Name settings are not available for this user"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copy to clipboard?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copy"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"to <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"General"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Advanced"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"General settings"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Advanced settings"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Send individual SMS messages to all recipients. Only you will get any replies"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Send a single MMS to all recipients"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Unknown number"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"New message"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"New message"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM selector"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> selected, SIM selector"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edit subject"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Select SIM or edit subject"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Touch and hold to record audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Start new conversation"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Messaging List"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"New message"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Conversation list"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Loading conversations"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Loading messages"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"View more conversations"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"View more messages"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversation deleted"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversation deleted. Touch to show a different Messaging conversation"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blocked"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Unblocked"</string>
+ <string name="db_full" msgid="8459265782521418031">"Storage space is low. Some data may be lost."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Select attachments"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirm selection"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> selected"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Please remove one or more attachments and try again."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"You can try sending your message, but it might not be delivered unless you remove one or more attachments."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"You can only send one video per message. Please remove additional videos and try again."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging failed to load attachment."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Send anyway"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Could not start conversation"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> selected"</string>
+</resources>
diff --git a/res/values-en-rGB/arrays.xml b/res/values-en-rGB/arrays.xml
new file mode 100644
index 0000000..94d5aff
--- /dev/null
+++ b/res/values-en-rGB/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"no subject"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Yes"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Thanks"</item>
+ <item msgid="4881335087096496747">"I agree"</item>
+ <item msgid="2422296858597420738">"Nice"</item>
+ <item msgid="4805581752819452687">"On my way"</item>
+ <item msgid="4746700499431366214">"OK, let me get back to you later"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..11048a5
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Messaging"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Messaging"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Select conversation"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Settings"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Send Message"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Add an attachment"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Help"</string>
+ <string name="welcome" msgid="2857560951820802321">"Welcome"</string>
+ <string name="skip" msgid="7238879696319945853">"Skip"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Next &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Next"</string>
+ <string name="exit" msgid="1905187380359981199">"Exit"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Settings &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Settings"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaging needs permission to SMS, Phone and Contacts."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"You can change permissions in Settings &gt; Apps &gt; Messaging &gt; Permissions."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"You can change permissions in Settings, Apps, Messaging, Permissions."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frequents"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"All contacts"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Send to <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capture pictures or video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Choose images from this device"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Record audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Choose photo"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"The media is selected."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"The media is unselected."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> selected"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"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" msgid="8142175029053370662">"image"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Record audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Share"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Just now"</string>
+ <string name="posted_now" msgid="867560789350406701">"Now"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> mins</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hours</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hour</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> days</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> day</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> weeks</item>
+ <item quantity="one">a week</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> months</item>
+ <item quantity="one">a month</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> years</item>
+ <item quantity="one">a year</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Class 0 message"</string>
+ <string name="save" msgid="5081141452059463572">"Save"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Device is low on space. Messaging will automatically delete older messages to free up space."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Storage space running out"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Messaging might not send or receive messages until more space is available on your device."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Low SMS storage. You may need to delete messages."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirm your phone number"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"This one-off step will ensure Messaging will deliver your group messages properly."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Phone number"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Delete all messages with media"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Auto-delete messages older than <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignore"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Delete all messages with media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g> and turn on auto-delete?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> said"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"You said"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Message from <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"You sent a message"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Sending…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Not sent. Touch to try again."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Not sent. Trying again…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Resend or delete"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Please make a voice call to emergency services. Your text message could not be delivered at this time."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Failed"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"New MMS message to download"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"New MMS message"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Couldn\'t download"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Touch to try again"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Touch to download"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Download or delete"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Downloading…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Message expired or not available"</string>
+ <string name="mms_info" msgid="3402311750134118165">"size: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Can\'t send. Recipient not valid."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Service not activated on network"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Couldn\'t send due to network problem"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Message expired or not available"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(No subject)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Unknown sender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Delivered"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Couldn\'t download message <xliff:g id="SUBJECT">%1$s</xliff:g> from <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Couldn\'t complete database operation due to low memory"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Message not sent"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Some messages not sent in Messaging"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messages in one conversation</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Message not downloaded"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Some messages not downloaded in Messaging"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messages in one conversation</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Message to <xliff:g id="NUMBER">%1$s</xliff:g> not sent"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Please make a voice call to emergency services. Your text message to <xliff:g id="NUMBER">%1$s</xliff:g> could not be delivered at this time."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> new messages</item>
+ <item quantity="one">New message</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Start"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Camera not available"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Camera not available"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video capture not available"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Can\'t save media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Can\'t take picture"</string>
+ <string name="back" msgid="1477626055115561645">"Back"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archived"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Delete"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archive"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Unarchive"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Turn off notifications"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Turn on notifications"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Add contact"</string>
+ <string name="action_download" msgid="7786338136368564146">"Download"</string>
+ <string name="action_send" msgid="377635240181672039">"Send"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Delete"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Delete this message"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"This action cannot be undone."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Delete"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Delete these conversations?</item>
+ <item quantity="one">Delete this conversation?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Delete"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancel"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"To"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Select multiple images"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirm selection"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Can\'t record audio. Try again."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Can\'t play audio. Try again."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Couldn\'t save audio. Try again."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Touch &amp; hold"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Picture"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio clip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Contact card"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Download"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Reply via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Reply via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Reply"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participants</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participant</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Me"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contact blocked &amp; archived"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contact unblocked &amp; unarchived"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archived"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> unarchived"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notifications turned off"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notifications turned on"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"All set. Touch Send again."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Messaging successfully set as the default SMS app."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Discard attachments</item>
+ <item quantity="one">Discard attachment</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audio attachment"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Play audio attachment"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Failed message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Unsent message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sending message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Failed message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Failed message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Unsent message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sending message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Failed message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Failed message. Touch to retry."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversation with <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Delete subject"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capture video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capture a still image"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Take picture"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Start recording video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Switch to full screen camera"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Switch between front and back camera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stop recording and attach video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Stop recording video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging photos"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> photos saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> photo saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videos saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> attachments saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> attachments saved to \"Downloads\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved to \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> attachments saved</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Couldn\'t save <xliff:g id="QUANTITY_1">%d</xliff:g> attachments</item>
+ <item quantity="one">Couldn\'t save <xliff:g id="QUANTITY_0">%d</xliff:g> attachment</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Saved MMS attachment"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Settings"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archived"</string>
+ <string name="action_close" msgid="1840519376200478419">"Close"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Advanced"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Debug"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifications"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Sound"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silent"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrate"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blocked"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS delivery reports"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Request a delivery report for each SMS that you send"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Auto-retrieve"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automatically retrieve MMS messages"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Roaming auto-retrieve"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatically retrieve MMS when roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Group messaging"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Use MMS to send a single message when there are multiple recipients"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Default SMS app"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Default SMS app"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Your phone number"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Unknown"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Outgoing message sounds"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Dump SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Dump received SMS raw data into external storage file"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Dump MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Dump received MMS raw data into external storage file"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Wireless alerts"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Message options"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copy text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"View details"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Delete"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Forward"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Message details"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Text message"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedia message"</string>
+ <string name="from_label" msgid="1947831848146564875">"From: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"To: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Sent: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Received: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subject: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Size: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priority: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"High"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Low"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Hidden sender address"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Can\'t send message while loading attachments."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Can\'t load attachment. Try again."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Network is not ready. Try again."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Delete text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Switch between entering text and numbers"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Add more participants"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirm participants"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Start new conversation"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Select this item"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Play video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"People &amp; Options"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debug"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"People &amp; Options"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"General"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"People in this conversation"</string>
+ <string name="action_call" msgid="6596167921517350362">"Make a call"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Send message"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Send message&lt;br/&gt;&lt;small&gt;from <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Send photos</item>
+ <item quantity="one">Send photo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Send audios</item>
+ <item quantity="one">Send audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Send videos</item>
+ <item quantity="one">Send video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Send contact cards</item>
+ <item quantity="one">Send contact card</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Send attachments</item>
+ <item quantity="one">Send attachment</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> attachments ready to send</item>
+ <item quantity="one">One attachment ready to send</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Send feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"View in Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Version info"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Open-source licences"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifications"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Attachment limit reached"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Failed to load attachment."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Add to Contacts?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Add Contact"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subject"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subject: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Loading contact card"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Couldn\'t load contact card"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"View contact card"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contacts</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contact</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Contact cards"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Birthday"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notes"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Forward message"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Reply"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS disabled"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"To send, set Messaging as the default SMS app"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Set Messaging as the default SMS app"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Change"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"To receive messages, set Messaging as the default SMS app"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"No preferred SIM selected for sending SMS messages"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"This app isn\'t allowed by the device owner."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Too many participants in a conversation"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Invalid contacts</item>
+ <item quantity="one">Invalid contact</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Couldn\'t load camera image"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"You: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draft"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Once you start a new conversation, you’ll see it listed here"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Archived conversations appear here"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Loading conversations…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Picture"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio clip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"In-stream video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Contact card"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Undo"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Retry"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Enter a contact name or phone number to start a new message"</string>
+ <string name="action_block" msgid="9032076625645190136">"Block"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Block <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Unblock <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Block <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blocked contacts"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"UNBLOCK"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blocked contacts"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Choose image from document library"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sending message"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Message sent"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobile data is turned off. Check your settings."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Can\'t send messages in Aeroplane mode"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Message could not be sent"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Message downloaded"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobile data is turned off. Check your settings."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Can\'t download messages in Aeroplane mode"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Message could not be downloaded"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"One"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Two"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Three"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Four"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Five"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Six"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Seven"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Eight"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nine"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Can\'t send message with <xliff:g id="CARRIERNAME">%1$s</xliff:g>, error <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Can\'t send message with unknown operator, error <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Message not sent: service not activated on network"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Message not sent: invalid destination address"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Message not sent: invalid message"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Message not sent: unsupported content"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Message not sent: unsupported message"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Message not sent: too large"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"New message"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"View"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Image"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Could not find an appropriate application"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Remove recipient"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"New message"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancel"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edit access point"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Not set"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Named"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN type"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Delete APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"New APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Save"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Discard"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"The Name field can\'t be empty."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"The APN cannot be empty."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC field must be 3 digits."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC field must be 2 or 3 digits."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restoring default APN settings."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Reset to default"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Reset default APN settings completed"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Untitled"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Access point names"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"New APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Access Point Name settings are not available for this user"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copy to clipboard?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copy"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"to <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"General"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Advanced"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"General settings"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Advanced settings"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Send individual SMS messages to all recipients. Only you will get any replies"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Send a single MMS to all recipients"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Unknown number"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"New message"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"New message"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM selector"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> selected, SIM selector"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edit subject"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Select SIM or edit subject"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Touch and hold to record audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Start new conversation"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Messaging List"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"New message"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Conversation list"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Loading conversations"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Loading messages"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"View more conversations"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"View more messages"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversation deleted"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversation deleted. Touch to show a different Messaging conversation"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blocked"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Unblocked"</string>
+ <string name="db_full" msgid="8459265782521418031">"Storage space is low. Some data may be lost."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Select attachments"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirm selection"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> selected"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Please remove one or more attachments and try again."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"You can try sending your message, but it might not be delivered unless you remove one or more attachments."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"You can only send one video per message. Please remove additional videos and try again."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging failed to load attachment."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Send anyway"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Could not start conversation"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> selected"</string>
+</resources>
diff --git a/res/values-en-rIN/arrays.xml b/res/values-en-rIN/arrays.xml
new file mode 100644
index 0000000..94d5aff
--- /dev/null
+++ b/res/values-en-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"no subject"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Yes"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Thanks"</item>
+ <item msgid="4881335087096496747">"I agree"</item>
+ <item msgid="2422296858597420738">"Nice"</item>
+ <item msgid="4805581752819452687">"On my way"</item>
+ <item msgid="4746700499431366214">"OK, let me get back to you later"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..11048a5
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Messaging"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Messaging"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Select conversation"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Settings"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Send Message"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Add an attachment"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Help"</string>
+ <string name="welcome" msgid="2857560951820802321">"Welcome"</string>
+ <string name="skip" msgid="7238879696319945853">"Skip"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Next &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Next"</string>
+ <string name="exit" msgid="1905187380359981199">"Exit"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Settings &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Settings"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaging needs permission to SMS, Phone and Contacts."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"You can change permissions in Settings &gt; Apps &gt; Messaging &gt; Permissions."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"You can change permissions in Settings, Apps, Messaging, Permissions."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frequents"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"All contacts"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Send to <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capture pictures or video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Choose images from this device"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Record audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Choose photo"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"The media is selected."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"The media is unselected."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> selected"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"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" msgid="8142175029053370662">"image"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Record audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Share"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Just now"</string>
+ <string name="posted_now" msgid="867560789350406701">"Now"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> mins</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hours</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hour</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> days</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> day</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> weeks</item>
+ <item quantity="one">a week</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> months</item>
+ <item quantity="one">a month</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> years</item>
+ <item quantity="one">a year</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Class 0 message"</string>
+ <string name="save" msgid="5081141452059463572">"Save"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Device is low on space. Messaging will automatically delete older messages to free up space."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Storage space running out"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Messaging might not send or receive messages until more space is available on your device."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Low SMS storage. You may need to delete messages."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirm your phone number"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"This one-off step will ensure Messaging will deliver your group messages properly."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Phone number"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Delete all messages with media"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Auto-delete messages older than <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignore"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Delete all messages with media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Delete messages older than <xliff:g id="DURATION">%s</xliff:g> and turn on auto-delete?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> said"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"You said"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Message from <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"You sent a message"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Sending…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Not sent. Touch to try again."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Not sent. Trying again…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Resend or delete"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Please make a voice call to emergency services. Your text message could not be delivered at this time."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Failed"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"New MMS message to download"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"New MMS message"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Couldn\'t download"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Touch to try again"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Touch to download"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Download or delete"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Downloading…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Message expired or not available"</string>
+ <string name="mms_info" msgid="3402311750134118165">"size: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Can\'t send. Recipient not valid."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Service not activated on network"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Couldn\'t send due to network problem"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Message expired or not available"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(No subject)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Unknown sender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Delivered"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Couldn\'t download message <xliff:g id="SUBJECT">%1$s</xliff:g> from <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Couldn\'t complete database operation due to low memory"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Message not sent"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Some messages not sent in Messaging"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messages in one conversation</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Message not downloaded"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Some messages not downloaded in Messaging"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messages in one conversation</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Message to <xliff:g id="NUMBER">%1$s</xliff:g> not sent"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Please make a voice call to emergency services. Your text message to <xliff:g id="NUMBER">%1$s</xliff:g> could not be delivered at this time."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> new messages</item>
+ <item quantity="one">New message</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Start"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Camera not available"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Camera not available"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video capture not available"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Can\'t save media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Can\'t take picture"</string>
+ <string name="back" msgid="1477626055115561645">"Back"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archived"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Delete"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archive"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Unarchive"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Turn off notifications"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Turn on notifications"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Add contact"</string>
+ <string name="action_download" msgid="7786338136368564146">"Download"</string>
+ <string name="action_send" msgid="377635240181672039">"Send"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Delete"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Delete this message"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"This action cannot be undone."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Delete"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Delete these conversations?</item>
+ <item quantity="one">Delete this conversation?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Delete"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancel"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"To"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Select multiple images"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirm selection"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Can\'t record audio. Try again."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Can\'t play audio. Try again."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Couldn\'t save audio. Try again."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Touch &amp; hold"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Picture"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio clip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Contact card"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Download"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Reply via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Reply via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Reply"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participants</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participant</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Me"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contact blocked &amp; archived"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contact unblocked &amp; unarchived"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archived"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> unarchived"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notifications turned off"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notifications turned on"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"All set. Touch Send again."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Messaging successfully set as the default SMS app."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Discard attachments</item>
+ <item quantity="one">Discard attachment</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audio attachment"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Play audio attachment"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Failed message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Unsent message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sending message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Failed message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Message to <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Failed message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Message from <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Unsent message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sending message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Failed message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Message to <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Failed message. Touch to retry."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversation with <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Delete subject"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capture video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capture a still image"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Take picture"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Start recording video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Switch to full screen camera"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Switch between front and back camera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stop recording and attach video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Stop recording video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging photos"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> photos saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> photo saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videos saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> attachments saved to \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" album</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved to \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> attachments saved to \"Downloads\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved to \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> attachments saved</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> attachment saved</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Couldn\'t save <xliff:g id="QUANTITY_1">%d</xliff:g> attachments</item>
+ <item quantity="one">Couldn\'t save <xliff:g id="QUANTITY_0">%d</xliff:g> attachment</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Saved MMS attachment"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Settings"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archived"</string>
+ <string name="action_close" msgid="1840519376200478419">"Close"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Advanced"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Debug"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifications"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Sound"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silent"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrate"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blocked"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS delivery reports"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Request a delivery report for each SMS that you send"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Auto-retrieve"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automatically retrieve MMS messages"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Roaming auto-retrieve"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatically retrieve MMS when roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Group messaging"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Use MMS to send a single message when there are multiple recipients"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Default SMS app"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Default SMS app"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Your phone number"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Unknown"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Outgoing message sounds"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Dump SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Dump received SMS raw data into external storage file"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Dump MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Dump received MMS raw data into external storage file"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Wireless alerts"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Message options"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copy text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"View details"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Delete"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Forward"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Message details"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Text message"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedia message"</string>
+ <string name="from_label" msgid="1947831848146564875">"From: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"To: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Sent: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Received: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subject: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Size: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priority: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"High"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Low"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Hidden sender address"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Can\'t send message while loading attachments."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Can\'t load attachment. Try again."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Network is not ready. Try again."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Delete text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Switch between entering text and numbers"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Add more participants"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirm participants"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Start new conversation"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Select this item"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Play video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"People &amp; Options"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debug"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"People &amp; Options"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"General"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"People in this conversation"</string>
+ <string name="action_call" msgid="6596167921517350362">"Make a call"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Send message"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Send message&lt;br/&gt;&lt;small&gt;from <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Send photos</item>
+ <item quantity="one">Send photo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Send audios</item>
+ <item quantity="one">Send audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Send videos</item>
+ <item quantity="one">Send video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Send contact cards</item>
+ <item quantity="one">Send contact card</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Send attachments</item>
+ <item quantity="one">Send attachment</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> attachments ready to send</item>
+ <item quantity="one">One attachment ready to send</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Send feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"View in Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Version info"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Open-source licences"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifications"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Attachment limit reached"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Failed to load attachment."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Add to Contacts?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Add Contact"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subject"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subject: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Loading contact card"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Couldn\'t load contact card"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"View contact card"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contacts</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contact</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Contact cards"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Birthday"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notes"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Forward message"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Reply"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS disabled"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"To send, set Messaging as the default SMS app"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Set Messaging as the default SMS app"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Change"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"To receive messages, set Messaging as the default SMS app"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"No preferred SIM selected for sending SMS messages"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"This app isn\'t allowed by the device owner."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Too many participants in a conversation"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Invalid contacts</item>
+ <item quantity="one">Invalid contact</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Couldn\'t load camera image"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"You: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draft"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Once you start a new conversation, you’ll see it listed here"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Archived conversations appear here"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Loading conversations…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Picture"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio clip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"In-stream video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Contact card"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Undo"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Retry"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Enter a contact name or phone number to start a new message"</string>
+ <string name="action_block" msgid="9032076625645190136">"Block"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Block <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Unblock <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Block <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blocked contacts"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"UNBLOCK"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blocked contacts"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Choose image from document library"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sending message"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Message sent"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobile data is turned off. Check your settings."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Can\'t send messages in Aeroplane mode"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Message could not be sent"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Message downloaded"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobile data is turned off. Check your settings."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Can\'t download messages in Aeroplane mode"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Message could not be downloaded"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"One"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Two"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Three"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Four"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Five"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Six"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Seven"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Eight"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nine"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Can\'t send message with <xliff:g id="CARRIERNAME">%1$s</xliff:g>, error <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Can\'t send message with unknown operator, error <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Message not sent: service not activated on network"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Message not sent: invalid destination address"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Message not sent: invalid message"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Message not sent: unsupported content"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Message not sent: unsupported message"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Message not sent: too large"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"New message"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"View"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Image"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Could not find an appropriate application"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Remove recipient"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"New message"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancel"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edit access point"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Not set"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Named"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN type"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Delete APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"New APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Save"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Discard"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"The Name field can\'t be empty."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"The APN cannot be empty."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC field must be 3 digits."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC field must be 2 or 3 digits."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restoring default APN settings."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Reset to default"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Reset default APN settings completed"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Untitled"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Access point names"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"New APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Access Point Name settings are not available for this user"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copy to clipboard?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copy"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"to <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"General"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Advanced"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"General settings"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Advanced settings"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Send individual SMS messages to all recipients. Only you will get any replies"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Send a single MMS to all recipients"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Unknown number"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"New message"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"New message"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM selector"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> selected, SIM selector"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edit subject"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Select SIM or edit subject"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Touch and hold to record audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Start new conversation"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Messaging List"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"New message"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Conversation list"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Loading conversations"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Loading messages"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"View more conversations"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"View more messages"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversation deleted"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversation deleted. Touch to show a different Messaging conversation"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blocked"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Unblocked"</string>
+ <string name="db_full" msgid="8459265782521418031">"Storage space is low. Some data may be lost."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Select attachments"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirm selection"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> selected"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Please remove one or more attachments and try again."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"You can try sending your message, but it might not be delivered unless you remove one or more attachments."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"You can only send one video per message. Please remove additional videos and try again."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging failed to load attachment."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Send anyway"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Could not start conversation"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> selected"</string>
+</resources>
diff --git a/res/values-es-rUS/arrays.xml b/res/values-es-rUS/arrays.xml
new file mode 100644
index 0000000..4a3fbb7
--- /dev/null
+++ b/res/values-es-rUS/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sin asunto"</item>
+ <item msgid="272485471009191934">"sinasunto"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Sí"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"Aceptar"</item>
+ <item msgid="3155097332660174689">"Je, je"</item>
+ <item msgid="2611328818571146775">"Gracias"</item>
+ <item msgid="4881335087096496747">"Acepto"</item>
+ <item msgid="2422296858597420738">"Genial"</item>
+ <item msgid="4805581752819452687">"Estoy en camino."</item>
+ <item msgid="4746700499431366214">"De acuerdo, te llamo más tarde."</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..1da2400
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Centro de mensajes"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Centro de mensajes"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Seleccionar conversación"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Configuración"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Enviar mensaje"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Agregar un archivo adjunto"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ayuda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Te damos la bienvenida"</string>
+ <string name="skip" msgid="7238879696319945853">"Omitir"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Siguiente &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Siguiente"</string>
+ <string name="exit" msgid="1905187380359981199">"Salir"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Configuración &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Configuración"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Centro de Mensajes necesita permiso para acceder a SMS, Teléfono y Contactos."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Puedes cambiar los permisos en Configuración &gt; Aplicaciones &gt; Centro de Mensajes &gt; Permisos."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Puedes cambiar los permisos en Configuración, Aplicaciones, Centro de Mensajes, Permisos."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frecuentes"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Todos los contactos"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Enviar a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capturar imágenes o video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Seleccionar imágenes de este dispositivo"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Grabar audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Elegir foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"El medio está seleccionado."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Los medios no están seleccionados."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Seleccionado: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imagen <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" msgid="8142175029053370662">"imagen"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Grabar audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Compartir"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Recién"</string>
+ <string name="posted_now" msgid="867560789350406701">"Recién"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> minutos</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> minuto</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> días</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> día</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semanas</item>
+ <item quantity="one">una semana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> meses</item>
+ <item quantity="one">un mes</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> años</item>
+ <item quantity="one">un año</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mensaje clase 0"</string>
+ <string name="save" msgid="5081141452059463572">"Guardar"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"El dispositivo tiene poco espacio de almacenamiento. El Centro de Mensajes borrará automáticamente los mensajes antiguos para liberar espacio."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Queda poco espacio de almacenamiento"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Es posible que Centro de Mensajes no envíe ni reciba mensajes hasta que se libere más espacio en el dispositivo."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Como queda poco espacio para SMS, tal vez debas eliminar mensajes."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirmar tu número de teléfono"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Este paso, que se realiza por única vez, asegurará que Centro de Mensajes envíe los mensajes grupales de forma correcta."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Número de teléfono"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Eliminar todos los mensajes con contenido multimedia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Eliminar mensajes de más de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Eliminar automáticamente mensajes de más de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorar"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"¿Eliminar todos los mensajes con contenido multimedia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"¿Eliminar mensajes de más de <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"¿Eliminar mensajes de más de <xliff:g id="DURATION">%s</xliff:g> y activar la eliminación automática?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> dijo"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Tú dijiste"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Enviaste un mensaje"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Enviando…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"El mensaje no se envió. Toca para volver a intentarlo."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"El mensaje no se envió. Reintentando…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Volver a enviar o eliminar"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Realiza una llamada de voz a los servicios de emergencia. No se pudo enviar el mensaje de texto esta vez."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Error"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nuevo mensaje MMS para descargar"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nuevo mensaje MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"El mensaje no se pudo descargar."</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Toca para volver a intentarlo."</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Toca para descargar."</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Descargar o eliminar"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Descargando…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"El mensaje venció o no está disponible."</string>
+ <string name="mms_info" msgid="3402311750134118165">"tamaño: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>; vencimiento: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"No se puede enviar porque el destinatario no es válido."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"No se activó el servicio en la red."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"No se pudo enviar debido a un problema de la red."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"El mensaje venció o no está disponible."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Sin asunto)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Remitente desconocido"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Entregado"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"No se pudo descargar el mensaje <xliff:g id="SUBJECT">%1$s</xliff:g> de <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"No se pudo completar la operación de la base de datos por falta de memoria."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mensaje no enviado"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"No se pudieron enviar algunos mensajes en Centro de Mensajes"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensajes en <xliff:g id="CONVERSATIONS">%d</xliff:g> conversaciones</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensajes en una conversación</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mensaje no descargado"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"No se pudieron descargar algunos mensajes en Centro de Mensajes"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensajes en <xliff:g id="CONVERSATIONS">%d</xliff:g> conversaciones</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensaje en una conversación</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mensaje al <xliff:g id="NUMBER">%1$s</xliff:g> no enviado"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Realiza una llamada de voz a los servicios de emergencia. No se pudo enviar el mensaje de texto a <xliff:g id="NUMBER">%1$s</xliff:g> esta vez."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> mensajes nuevos</item>
+ <item quantity="one">Un mensaje nuevo</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Iniciar"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Cámara no disponible"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Cámara no disponible"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Captura de video no disponible"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"No se puede guardar el contenido multimedia."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"No se puede tomar la foto."</string>
+ <string name="back" msgid="1477626055115561645">"Atrás"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archivadas"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Eliminar"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archivo"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Eliminar del archivo"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desactivar notificaciones"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activar notificaciones"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Agregar contacto"</string>
+ <string name="action_download" msgid="7786338136368564146">"Descargar"</string>
+ <string name="action_send" msgid="377635240181672039">"Enviar"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Eliminar"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"¿Deseas eliminar este mensaje?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Esta acción no se puede deshacer."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Eliminar"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">¿Deseas borrar estas conversaciones?</item>
+ <item quantity="one">¿Deseas borrar esta conversación?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Eliminar"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancelar"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Para"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Seleccionar varias imágenes"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmar selección"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"<xliff:g id="COUNT">%d</xliff:g> más"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"No se puede grabar el audio. Vuelve a intentarlo."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"No se puede reproducir el audio. Vuelve a intentarlo."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"No se pudo guardar el audio. Vuelve a intentarlo."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Mantener presionado"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imagen"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clip de audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Tarjeta de contacto"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Descargar"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Responder con SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Responder (MMS)"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Responder"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participantes</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participante</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Yo"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contacto bloqueado y archivado"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contacto desbloqueado y sin archivar"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archivada(s)"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> no archivada(s)"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificaciones desactivadas"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificaciones activadas"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Todo listo. Vuelve a tocar Enviar."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Se estableció de forma correcta Centro de Mensajes como tu aplicación de SMS predeterminada."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Descartar archivos adjuntos</item>
+ <item quantity="one">Descartar archivo adjunto</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Archivo adjunto de audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reproducir archivo adjunto de audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausa"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Error del mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mensaje no enviado a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Enviando mensaje a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Error de mensaje a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mensaje a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Error del mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g> <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g> <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mensaje no enviado a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Enviando mensaje a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Error del mensaje a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mensaje a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Se produjo un error en el mensaje. Toca para volver a intentarlo."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversación con <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Eliminar asunto"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capturar video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturar una imagen fija"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Tomar fotografía"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Iniciar grabación de video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Cambiar a cámara en pantalla completa"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Alternar entre cámara frontal y trasera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Detener la grabación y adjuntar video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Detener la grabación de video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos de Centro de Mensajes"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">Se guardaron <xliff:g id="QUANTITY_2">%d</xliff:g> fotos en el álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Se guardó <xliff:g id="QUANTITY_0">%d</xliff:g> foto en el álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">Se guardaron <xliff:g id="QUANTITY_2">%d</xliff:g> videos en el álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Se guardó <xliff:g id="QUANTITY_0">%d</xliff:g> video en el álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">Se guardaron <xliff:g id="QUANTITY_2">%d</xliff:g> archivos adjuntos en el álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Se guardó <xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto en el álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">Se guardaron <xliff:g id="QUANTITY_1">%d</xliff:g> archivos adjuntos en \"Descargas\"</item>
+ <item quantity="one">Se guardó <xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto en \"Descargas\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">Se guardaron <xliff:g id="QUANTITY_1">%d</xliff:g> archivos adjuntos</item>
+ <item quantity="one">Se guardó <xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">No se pudieron guardar <xliff:g id="QUANTITY_1">%d</xliff:g> archivos adjuntos</item>
+ <item quantity="one">No se pudo guardar <xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Archivo adjunto de MMS guardado"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Configuración"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archivadas"</string>
+ <string name="action_close" msgid="1840519376200478419">"Cerrar"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avanzada"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Depurar"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificaciones"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Sonido"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silencio"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqueado"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Informes de entrega de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Solicitar un informe de entrega al enviar un SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recuperación automática"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recuperar mensajes MMS automáticamente"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Recuperación automática en roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Recuperar MMS automáticamente en roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mensajes grupales"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utiliza MMS para enviar un mismo mensaje a varios destinatarios."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplicación de SMS predeterminada"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplicación de SMS predeterminada"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Tu número de teléfono"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Desconocido"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sonidos de mensaje saliente"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Volcar SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Volcar datos sin procesar de SMS en el archivo de almacenamiento externo"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Volcar MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Volcar datos sin procesar de MMS recibidos en un archivo de almacenamiento externo"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertas inalámbricas"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opciones de mensaje"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copiar el texto"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ver detalles"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Eliminar"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Reenviar"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalles del mensaje"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mensaje de texto"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mensaje multimedia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Para: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Enviado: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Fecha de recepción: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Asunto: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Tamaño: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioridad: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Baja"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Dirección de remitente oculta"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"No se pueden enviar mensajes mientras se cargan adjuntos."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"No se puede cargar el archivo adjunto. Vuelve a intentarlo."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"La red no está lista. Vuelve a intentarlo."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Suprimir texto"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Cambiar entre ingresar texto y números"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Agregar más participantes"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmar participantes"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Comenzar una nueva conversación"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Seleccionar este elemento"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reproducir video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Contactos y opciones"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Depurar"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Contactos y opciones"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"General"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Contactos en esta conversación"</string>
+ <string name="action_call" msgid="6596167921517350362">"Hacer una llamada"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Enviar mensaje"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Enviar mensaje&lt;br/&gt;&lt;small&gt;de <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Enviar fotos</item>
+ <item quantity="one">Enviar foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Enviar audios</item>
+ <item quantity="one">Enviar audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Enviar videos</item>
+ <item quantity="one">Enviar video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Enviar tarjetas de contacto</item>
+ <item quantity="one">Enviar tarjeta de contacto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Enviar archivos adjuntos</item>
+ <item quantity="one">Enviar archivo adjunto</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> archivos adjuntos listos para enviar</item>
+ <item quantity="one">Un archivo adjunto listo para enviar</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Enviar comentarios"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ver en Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Información de la versión"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versión %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licencias de código abierto"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificaciones"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Se alcanzó el límite de archivos adjuntos."</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Se produjo un error al cargar el archivo adjunto."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"¿Agregar a contactos?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Agregar contacto"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Asunto"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Asunto: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Cargando tarjeta de contacto"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"No se pudo cargar la tarjeta de contacto."</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ver tarjeta de contacto"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contactos</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contacto</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Tarjetas de contactos"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Fecha de nacimiento"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notas"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Reenviar mensaje"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Responder"</string>
+ <string name="plus_one" msgid="9010288417554932581">"Hacer +1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS inhabilitados"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Establecer Centro de mensajes como la aplicación de SMS predeterminada para enviar mensajes"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Establecer Centro de Mensajes como la aplicación de SMS predeterminada"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Cambiar"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Establecer Centro de mensajes como la aplicación de SMS predeterminada para recibir mensajes"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"No se seleccionó ninguna SIM preferida para el envío de mensajes SMS."</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"El propietario del dispositivo no permite esta aplicación."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Aceptar"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Demasiados participantes en una conversación"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Contactos no válidos</item>
+ <item quantity="one">Contacto no válido</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"No se pudo cargar la imagen de la cámara."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Tú: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Borrador"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Cuando inicies una conversación nueva, aparecerá aquí."</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Las conversaciones archivadas aparecen aquí."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Cargando conversaciones…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imagen"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clip de audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Tarjeta de contacto"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Deshacer"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Volver a intentar"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Ingresa un nombre de contacto o número de teléfono para crear un nuevo mensaje."</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquear"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquear a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desbloquear a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"¿Bloquear a <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Seguirás recibiendo mensajes de este número, pero no verás notificaciones. Esta conversación se archivará."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contactos bloqueados"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOQUEAR"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contactos bloqueados"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Seleccionar imagen de la galería de documentos"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Enviando el mensaje"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Se envió el mensaje."</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Los datos móviles están desactivados. Comprueba la configuración."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"No se pueden enviar mensajes en modo de avión."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"No se pudo enviar el mensaje."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Se descargó el mensaje."</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Los datos móviles están desactivados. Comprueba la configuración."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"No se pueden descargar mensajes en modo de avión."</string>
+ <string name="download_message_failure" msgid="635370887537738004">"No se pudo descargar el mensaje."</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Cero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Uno"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dos"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tres"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Cuatro"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinco"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seis"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Siete"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Ocho"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nueve"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Error <xliff:g id="ERRORCODE">%2$d</xliff:g>. No se puede enviar el mensaje con <xliff:g id="CARRIERNAME">%1$s</xliff:g>."</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Error <xliff:g id="ERRORCODE">%1$d</xliff:g>. No se puede enviar el mensaje con un proveedor desconocido."</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Rv: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"No se envió el mensaje porque el servicio no está activado en la red."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mensaje no enviado: dirección de destino no válida"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mensaje no enviado: mensaje no válido"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mensaje no enviado: contenido no admitido"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mensaje no enviado: mensaje no admitido"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mensaje no enviado: demasiado largo"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mensaje nuevo"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ver"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imagen"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"No se pudo encontrar una aplicación adecuada."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Eliminar destinatario"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mensaje nuevo"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancelar"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Editar el punto de acceso"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Sin establecer"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nombre"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy de MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Puerto MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipo de APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Eliminar APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN nuevo"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Guardar"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Descartar"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"El campo Nombre no puede estar vacío."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"El APN no puede estar vacío."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"El campo de MCC debe contener tres dígitos."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"El campo MNC debe contener dos o tres dígitos."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restaurando la configuración APN predeterminada"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Restablecer parámetros predeterminados"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Se restableció la configuración predeterminada de APN."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sin título"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nombres de puntos de acceso"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN nuevo"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"La configuración del nombre de punto de acceso no está disponible para este usuario."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"¿Copiar en el portapapeles?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copiar"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"a <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"General"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avanzada"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Configuración general"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Configuración avanzada"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Enviar mensajes de SMS individuales a todos los destinatarios (solo tú recibirás las respuestas)."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Enviar un único MMS a todos los destinatarios"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Número desconocido"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mensaje nuevo"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mensaje nuevo"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Selector de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Se seleccionó <xliff:g id="SIM_0">%1$s</xliff:g>, selector de SIM."</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editar asunto"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Seleccionar SIM o editar asunto"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Mantén presionado para grabar audio."</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Comenzar una nueva conversación"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Centro de mensajes"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista de Centro de Mensajes"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Centro de mensajes"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mensaje nuevo"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista de conversaciones"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Cargando conversaciones"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Cargando mensajes…"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ver más conversaciones"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Ver más mensajes"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Se eliminó la conversación."</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Se borró la conversación. Toca para mostrar otra conversación de Centro de Mensajes"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloqueado"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Desbloqueado"</string>
+ <string name="db_full" msgid="8459265782521418031">"Hay poco espacio de almacenamiento. Podrían perderse algunos datos."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Seleccionar archivos adjuntos"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmar selección"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Seleccionado: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Elimina uno o más archivos adjuntos y vuelve a intentarlo."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Puedes intentar enviar el mensaje, pero podría no enviarse a menos que elimines uno más archivos adjuntos."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Solo puedes enviar un video por mensaje. Quita los videos adicionales y vuelve a intentarlo."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Centro de Mensajes no pudo cargar el archivo adjunto."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Enviar de todas formas"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"No se pudo iniciar la conversación."</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Se seleccionó <xliff:g id="SELECTED_SIM">%s</xliff:g>."</string>
+</resources>
diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml
new file mode 100644
index 0000000..ca8e06b
--- /dev/null
+++ b/res/values-es/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sin asunto"</item>
+ <item msgid="272485471009191934">"sinasunto"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Sí"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"Ok"</item>
+ <item msgid="3155097332660174689">"Je, je"</item>
+ <item msgid="2611328818571146775">"Gracias"</item>
+ <item msgid="4881335087096496747">"Acepto"</item>
+ <item msgid="2422296858597420738">"Genial"</item>
+ <item msgid="4805581752819452687">"Voy de camino"</item>
+ <item msgid="4746700499431366214">"Vale, te llamo más tarde"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..78ffe68
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mensajes"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mensajes"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Seleccionar conversación"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Ajustes"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Enviar mensaje"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Adjuntar un archivo"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ayuda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Te damos la bienvenida"</string>
+ <string name="skip" msgid="7238879696319945853">"Saltar"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Siguiente &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Siguiente"</string>
+ <string name="exit" msgid="1905187380359981199">"Salir"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Ajustes &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Ajustes"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mensajes necesita permiso para acceder a los SMS, al teléfono y a los contactos."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Puedes cambiar los permisos en Ajustes &gt; Aplicaciones &gt; Mensajes &gt; Permisos."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Puedes cambiar los permisos en Ajustes, Aplicaciones, Mensajes, Permisos."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frecuentes"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Todos los contactos"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Enviar a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capturar imágenes o vídeo"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Seleccionar imágenes de este dispositivo"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Grabar audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Elegir foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Contenido multimedia seleccionado."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Contenido multimedia no seleccionado."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Seleccionado: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imagen del <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" msgid="8142175029053370662">"imagen"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Grabar audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Compartir"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Ahora mismo"</string>
+ <string name="posted_now" msgid="867560789350406701">"Ahora"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> días</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> día</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semanas</item>
+ <item quantity="one">una semana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> meses</item>
+ <item quantity="one">un mes</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> años</item>
+ <item quantity="one">un año</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mensaje de clase 0"</string>
+ <string name="save" msgid="5081141452059463572">"Guardar"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Casi no queda espacio en el dispositivo. La aplicación Mensajes eliminará automáticamente los mensajes más antiguos para liberar espacio."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Queda poco espacio"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Es posible que Mensajes no envíe ni reciba mensajes hasta que haya más espacio disponible en el dispositivo."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Queda poco espacio para recibir SMS. Es posible que tengas que eliminar mensajes."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirma tu número de teléfono"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Este paso único garantiza que Mensajes enviará tus mensajes de grupo correctamente."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Número de teléfono"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Eliminar todos los mensajes con archivos multimedia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Eliminar mensajes anteriores a <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Eliminar automáticamente mensajes anteriores a <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorar"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"¿Eliminar todos los mensajes con archivos multimedia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"¿Eliminar mensajes anteriores a <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"¿Eliminar mensajes anteriores a <xliff:g id="DURATION">%s</xliff:g> y activar eliminación automática?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ha dicho"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Has dicho"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Has enviado un mensaje"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Enviando..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"No enviado. Toca para volver a intentarlo."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"No enviado. Volviendo a intentar…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Volver a enviar o eliminar"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Llama a los servicios de emergencias. Tu mensaje de texto no se puede entregar en estos momentos."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Error"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nuevo mensaje MMS que descargar"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nuevo mensaje MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Error al descargar"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Toca para volver a intentarlo"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Toca para descargar"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Descargar o eliminar"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Descargando..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"El mensaje ha caducado o no está disponible"</string>
+ <string name="mms_info" msgid="3402311750134118165">"tamaño: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, vencimiento: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"No se puede enviar porque el destinatario no es válido."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"No se ha activado el servicio en la red"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"No se ha podido enviar por un problema de red"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"El mensaje ha caducado o no está disponible"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Sin asunto)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Destinatario desconocido"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Entregado"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"No se ha podido descargar el mensaje <xliff:g id="SUBJECT">%1$s</xliff:g> de <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"No se ha podido completar la operación de la base de datos por falta de memoria"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mensaje no enviado"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Hay algunos mensajes no enviados en Mensajes"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensajes en <xliff:g id="CONVERSATIONS">%d</xliff:g> conversaciones</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensajes en una conversación</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mensaje no descargado"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Algunos mensajes no se han descargado en Mensajes"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensajes en <xliff:g id="CONVERSATIONS">%d</xliff:g> conversaciones</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensajes en una conversación</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mensaje para <xliff:g id="NUMBER">%1$s</xliff:g> no enviado"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Llama a los servicios de emergencias. Tu mensaje de texto enviado al número <xliff:g id="NUMBER">%1$s</xliff:g> no se puede entregar en estos momentos."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> mensajes nuevos</item>
+ <item quantity="one">Mensaje nuevo</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Iniciar"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Cámara no disponible"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Cámara no disponible"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Captura de vídeo no disponible"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"No se puede guardar el archivo multimedia"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"No se pueden hacer fotos"</string>
+ <string name="back" msgid="1477626055115561645">"Atrás"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archivado"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Eliminar"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archivar"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"No archivar"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desactivar notificaciones"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activar notificaciones"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Añadir contacto"</string>
+ <string name="action_download" msgid="7786338136368564146">"Descargar"</string>
+ <string name="action_send" msgid="377635240181672039">"Enviar"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Eliminar"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"¿Eliminar este mensaje?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Esta acción no se puede deshacer."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Eliminar"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">¿Eliminar estas conversaciones?</item>
+ <item quantity="one">¿Eliminar esta conversación?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Eliminar"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancelar"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Para"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Seleccionar varias imágenes"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmar selección"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"No se puede grabar el audio. Vuelve a intentarlo."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"No se puede reproducir el audio. Vuelve a intentarlo."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"No se ha podido guardar el audio. Vuelve a intentarlo."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Mantener pulsado"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imagen"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Fragmento de audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vídeo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Tarjeta de contacto"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Descargar"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Responder con SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Responder por MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Responder"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participantes</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participante</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Yo"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contacto bloqueado y archivado"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contacto desbloqueado y no archivado"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archivadas"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> no archivadas"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificaciones desactivadas"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificaciones activadas"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Listo. Toca Enviar de nuevo."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Mensajes establecida como aplicación de SMS predeterminada."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Descartar archivos adjuntos</item>
+ <item quantity="one">Descartar archivo adjunto</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Archivo de audio adjunto"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reproducir archivo de audio adjunto"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausar"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Error del mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mensaje no enviado a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Enviando mensaje a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Error del mensaje para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mensaje para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Error del mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mensaje de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mensaje no enviado a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Enviando mensaje a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Error del mensaje para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mensaje para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Error del mensaje. Toca para volver a intentarlo."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversación con <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Eliminar asunto"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capturar vídeo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturar una imagen fija"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Hacer foto"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Iniciar grabación de vídeo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Cambiar a cámara en pantalla completa"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Cambiar de la cámara frontal a la trasera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Dejar de grabar y adjuntar vídeo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Detener grabación de vídeo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos de Mensajes"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotos guardadas en el álbum <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto guardada en el álbum <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vídeos guardados en el álbum <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> vídeo guardado en el álbum <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> archivos adjuntos guardados en el álbum <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto guardado en el álbum <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> archivos adjuntos guardados en Descargas</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto guardado en Descargas</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> archivos adjuntos guardados</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto guardado</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">No se han podido guardar <xliff:g id="QUANTITY_1">%d</xliff:g> archivos adjuntos</item>
+ <item quantity="one">No se ha podido guardar <xliff:g id="QUANTITY_0">%d</xliff:g> archivo adjunto</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Adjunto de MMS guardado"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Ajustes"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archivado"</string>
+ <string name="action_close" msgid="1840519376200478419">"Cerrar"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Opciones avanzadas"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Depurar"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificaciones"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Sonido"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silencio"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqueado"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Informes de entrega de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Solicitar un informe de entrega al enviar un SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recuperar automáticamente"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recuperar automáticamente mensajes MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Autorrecuperar en itinerancia"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Recuperar MMS automáticamente en itinerancia"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mensaje de grupo"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Usar MMS para enviar un único mensaje a varios destinatarios"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplicación de SMS predeterminada"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplicación de SMS predeterminada"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Tu número de teléfono"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Desconocido"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sonidos de mensajes enviados"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Volcar SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Volcar datos sin procesar de SMS recibidos en un archivo del almacenamiento externo"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Volcar MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Volcar datos sin procesar de MMS recibidos en un archivo del almacenamiento externo"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertas inalámbricas"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opciones de mensaje"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copiar texto"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ver detalles"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Eliminar"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Reenviar"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalles de mensaje"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mensaje de texto"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mensaje multimedia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"A: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Enviado: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Recibido: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Asunto: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Tamaño: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioridad: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Baja"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Dirección de remitente oculta"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"No se pueden enviar mensajes mientras se cargan adjuntos."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"No se ha podido cargar el archivo adjunto. Vuelve a intentarlo."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"La red no está lista. Inténtalo de nuevo."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Eliminar texto"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Alternar entre introducir texto y números"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Añadir más participantes"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmar participantes"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Iniciar nueva conversación"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Seleccionar este elemento"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reproducir vídeo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Contactos y opciones"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Depurar"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Contactos y opciones"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Generales"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Contactos en esta conversación"</string>
+ <string name="action_call" msgid="6596167921517350362">"Hacer una llamada"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Enviar mensaje"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Enviar mensaje&lt;br/&gt;&lt;small&gt;de <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Enviar fotos</item>
+ <item quantity="one">Enviar foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Enviar archivos de audio</item>
+ <item quantity="one">Enviar archivo de audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Enviar vídeos</item>
+ <item quantity="one">Enviar vídeo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Enviar tarjetas de contacto</item>
+ <item quantity="one">Enviar tarjeta de contacto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Enviar archivos adjuntos</item>
+ <item quantity="one">Enviar archivo adjunto</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> archivos adjuntos listos para enviar</item>
+ <item quantity="one">Un archivo adjunto listo para enviar</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Danos tu opinión"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ver en Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Información de la versión"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versión %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licencias código abierto"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificaciones"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Límite de archivos adjuntos alcanzado"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Error al cargar archivo adjunto."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"¿Añadir a contactos?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Añadir contacto"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Asunto"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Asunto: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Cargando tarjeta de contacto"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"No se ha podido cargar la tarjeta de contacto"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ver tarjeta de contacto"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contactos</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contacto</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Tarjetas de contacto"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Cumpleaños"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notas"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Reenviar mensaje"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Responder"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS inhabilitados"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Para enviar mensajes, establece Mensajes como aplicación de SMS predeterminada"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Establecer Mensajes como aplicación de SMS predeterminada"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Cambiar"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Para recibir mensajes, establece Mensajes como aplicación de SMS predeterminada"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"No se ha seleccionado la SIM preferida para enviar mensajes SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"El propietario del dispositivo no permite esta aplicación."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Aceptar"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Demasiados participantes en una conversación"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Contactos no válidos</item>
+ <item quantity="one">Contacto no válido</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"No se ha podido cargar la imagen de la cámara"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Tú: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Borrador"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Cuando inicies una conversación nueva, aparecerá aquí"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Aquí aparecen las conversaciones archivadas"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Cargando conversaciones..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imagen"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Fragmento de audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vídeo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Tarjeta de contacto"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Deshacer"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Volver a intentar"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Escribe el nombre de un contacto o un número de teléfono para iniciar un mensaje nuevo"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquear"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquear a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desbloquear a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"¿Bloquear a <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Seguirás recibiendo mensajes de este número pero no volverás a recibir notificaciones. Esta conversación se archivará."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contactos bloqueados"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOQUEAR"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contactos bloqueados"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Selecciona imagen de la biblioteca de documentos"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Enviando mensaje"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mensaje enviado"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Los datos móviles están desactivados. Comprueba los ajustes."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"No se puede enviar mensajes en modo avión"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Error al enviar mensaje"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mensaje descargado"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Los datos móviles están desactivados. Comprueba los ajustes."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"No se pueden descargar mensajes en modo avión"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"No se ha podido descargar el mensaje"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Cero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Uno"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dos"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tres"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Cuatro"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinco"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seis"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Siete"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Ocho"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nueve"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"No se puede enviar el mensaje con <xliff:g id="CARRIERNAME">%1$s</xliff:g> (error <xliff:g id="ERRORCODE">%2$d</xliff:g>)"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"No se puede enviar el mensaje con un operador desconocido (error <xliff:g id="ERRORCODE">%1$d</xliff:g>)"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Rv: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mensaje no enviado porque el servicio no se ha activado en la red"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mensaje no enviado: dirección de destino no válida"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mensaje no enviado: mensaje no válido"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mensaje no enviado: contenido no admitido"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mensaje no enviado: mensaje no admitido"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mensaje no enviado: demasiado largo"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mensaje nuevo"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ver"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imagen"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"No se ha podido encontrar una aplicación adecuada"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Eliminar destinatario"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mensaje nuevo"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancelar"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Editar punto de acceso"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"No definida"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nombre"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy para MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Puerto para MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipo de APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Eliminar APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nuevo APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Guardar"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Descartar"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"El campo Nombre no puede estar vacío."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"El campo APN no puede estar vacío."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"El campo MCC debe contener 3 dígitos."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"El campo MNC debe contener 2 o 3 dígitos."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restaurando la configuración de APN predeterminada"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Restablecer ajustes"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Se ha restablecido la configuración de APN predeterminada."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sin título"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nombre de puntos de acceso"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nuevo APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Los ajustes del nombre de punto de acceso no están disponibles para este usuario"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"¿Copiar al portapapeles?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copiar"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"para <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"General"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Opciones avanzadas"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Ajustes generales"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Ajustes avanzados"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Enviar mensajes SMS individuales. Solo tú recibirás las respuestas"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Enviar un único MMS a todos los destinatarios"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Número desconocido"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mensaje nuevo"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mensaje nuevo."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Selector de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> seleccionada, selector de SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editar asunto"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Seleccionar SIM o editar asunto"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Mantener pulsado para grabar audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Iniciar nueva conversación"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mensajes"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista de mensajes"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mensajes"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mensaje nuevo"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista de conversaciones"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Cargando conversaciones"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Cargando mensajes"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ver más conversaciones"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Ver más mensajes"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversación eliminada"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversación eliminada. Toca para ver otra conversación de Mensajes"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloqueado"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Desbloqueado"</string>
+ <string name="db_full" msgid="8459265782521418031">"Queda poco espacio de almacenamiento. Es posible que se pierdan algunos datos."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Seleccionar adjuntos"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmar selección"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Seleccionado: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Elimina uno o varios archivos adjuntos y vuelve a intentarlo."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Puedes probar a enviar el mensaje, pero quizás no se envíe hasta que elimines uno o varios archivos adjuntos."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Solo puedes enviar un vídeo por mensaje. Elimina más vídeos y vuelve a intentarlo."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"El mensaje no ha podido cargar el archivo adjunto."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Enviar de todos modos"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"No se ha podido iniciar la conversación"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> seleccionada"</string>
+</resources>
diff --git a/res/values-et-rEE/arrays.xml b/res/values-et-rEE/arrays.xml
new file mode 100644
index 0000000..16e1744
--- /dev/null
+++ b/res/values-et-rEE/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"teema puudub"</item>
+ <item msgid="272485471009191934">"teema puudub"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Jah"</item>
+ <item msgid="6049132459802288033">"Ei"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Aitäh"</item>
+ <item msgid="4881335087096496747">"Nõustun"</item>
+ <item msgid="2422296858597420738">"Tore"</item>
+ <item msgid="4805581752819452687">"Olen teel"</item>
+ <item msgid="4746700499431366214">"OK, vastan sulle hiljem"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..79097fe
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Sõnumside"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Sõnumside"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Vestluse valimine"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Seaded"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Saada sõnum"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Manuse lisamine"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Abi"</string>
+ <string name="welcome" msgid="2857560951820802321">"Tere tulemast"</string>
+ <string name="skip" msgid="7238879696319945853">"Vahelejätmine"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Järgmine &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Järgmine"</string>
+ <string name="exit" msgid="1905187380359981199">"Väljumine"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Seaded &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Seaded"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Sõnumside vajab luba SMS-idele, telefonile ja kontaktidele juurdepääsemiseks."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Saate muuta lube jaotises Seaded &gt; Rakendused &gt; Sõnumside &gt; Load."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Lubade muutmiseks tehke valikud Seaded, Rakendused, Sõnumside, Load."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Sagedased"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Kõik kontaktid"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Saada numbrile <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Jäädvustage pilte või videoid"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Kujutiste valimine sellest seadmest"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Heli salvestamine"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Foto valimine"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Meedia on valitud."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Meediat pole valitud."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> on valitud"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"pilt kuupäevaga <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" msgid="8142175029053370662">"pilt"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Heli salvestamine"</string>
+ <string name="action_share" msgid="2143483844803153871">"Jagamine"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Hetk tagasi"</string>
+ <string name="posted_now" msgid="867560789350406701">"Praegu"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> tundi</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> tund</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> päeva</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> päev</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> nädalat</item>
+ <item quantity="one">nädal</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> kuud</item>
+ <item quantity="one">kuu</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> aastat</item>
+ <item quantity="one">aasta</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0-klassi sõnum"</string>
+ <string name="save" msgid="5081141452059463572">"Salvesta"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Seadmes on vähe ruumi. Sõnumside kustutab ruumi vabastamiseks automaatselt vanemad sõnumid."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Talletusruum hakkab täis saama"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Sõnumside ei pruugi sõnumeid saata ega vastu võtta seni, kuni seadmes on rohkem vaba ruumi."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS-ide jaoks on vähe salvestusruumi. Peate võib-olla mõned sõnumid kustutama."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Kinnitage oma telefoninumber"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"See ühekordne toiming tagab, et rakendus Sõnumside edastab teie grupisõnumid õigesti."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefoninumber"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Kustuta kõik meediumifaile sisaldavad sõnumid"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Kustuta sõnumid, mis on vanemad kui <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Kustuta automaatselt sõnumid, mis on vanemad kui <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Eira"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Kas kustutada kõik meediaga sõnumid?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Kas kustutada sõnumid, mis on vanemad kui <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Kas kustutada automaatselt sõnumid, mis on vanemad kui <xliff:g id="DURATION">%s</xliff:g>, ja lülitada sisse automaatne kustutamine?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ütles:"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Ütlesite:"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Sõnum saatjalt <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Saatsite sõnumi"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Saatmine ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ei saadetud. Puudutage uuesti proovimiseks."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ei saadetud. Proovitakse uuesti ..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Saada uuesti või kustuta"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Helistage hädaabiteenusele. Teie tekstsõnumit ei õnnestunud praegu edastada."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Ebaõnnestus"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Uus MMS-sõnum allalaadimiseks"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Uus MMS-sõnum"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Ei saanud alla laadida"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Puudutage uuesti proovimiseks"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Puudutage allalaadimiseks"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Laadi alla või kustuta"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Allalaadimine ..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Sõnum on aegunud või pole saadaval"</string>
+ <string name="mms_info" msgid="3402311750134118165">"suurus: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, aegumine: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Ei õnnestu saata. Saaja ei ole õige."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Teenus pole võrgus aktiveeritud"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Võrguprobleemi tõttu ei õnnestunud saata"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Sõnum on aegunud või pole saadaval"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Teema puudub.)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Tundmatu saatja"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Kohale toimetatud"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Ei saa laadida alla sõnumit <xliff:g id="SUBJECT">%1$s</xliff:g>, mille saatis <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Vähese mälu tõttu ei õnnestunud andmebaasi toimingut lõpule viia"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Sõnumit ei saadetud"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Mõnda sõnumit ei saadetud rakenduses Sõnumside"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> sõnumit <xliff:g id="CONVERSATIONS">%d</xliff:g> vestluses</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> sõnumit ühes vestluses</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Sõnumit ei laaditud alla"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Mõnda sõnumit ei laaditud alla rakenduses Sõnumside"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> sõnumit <xliff:g id="CONVERSATIONS">%d</xliff:g> vestluses</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> sõnumit ühes vestluses</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Sõnumit numbrile <xliff:g id="NUMBER">%1$s</xliff:g> ei saadetud"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Helistage hädaabiteenusele. Teie tekstsõnumit numbrile <xliff:g id="NUMBER">%1$s</xliff:g> ei õnnestunud praegu edastada."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> uut sõnumit</item>
+ <item quantity="one">Uus sõnum</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Käivita"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kaamera pole saadaval"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kaamera pole saadaval"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video jäädvustamine pole saadaval"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Meediat ei saa salvestada"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Pilti ei saa jäädvustada"</string>
+ <string name="back" msgid="1477626055115561645">"Tagasi"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arhiivitud"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Kustutamine"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arhiivimine"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Arhiivimise tühistamine"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Märguannete väljalülitamine"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Märguannete sisselülitamine"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Kontakti lisamine"</string>
+ <string name="action_download" msgid="7786338136368564146">"Laadi alla"</string>
+ <string name="action_send" msgid="377635240181672039">"Saada"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Kustuta"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Kas kustutada see sõnum?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Seda toimingut ei saa tagasi võtta."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Kustuta"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Kas kustutada need vestlused?</item>
+ <item quantity="one">Kas kustutada see vestlus?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Kustuta"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Tühista"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Sihtkohta"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Mitme kujutise valimine"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Valiku kinnitamine"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"ja veel <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Heli ei õnnestu salvestada. Proovige uuesti."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Heli ei saa esitada. Proovige uuesti."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Heli ei õnnestunud salvestada. Proovige uuesti."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Puudutage pikalt"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Pilt"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Heliklipp"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontaktikaart"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Allalaadimine"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Vasta SMS-iga"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Vastam. MMS-iga"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Vasta"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> osalejat</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> osaleja</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Mina"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt on blokeeritud ja arhiivitud"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt deblokeeriti ja eemaldati arhiivist"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> on arhiivitud"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> üksuse arhiivimine on tühistatud"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Märguanded on välja lülitatud"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Märguanded on sisse lülitatud"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Kõik on valmis. Puudutage uuesti käsku Saada."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Sõnumside määramine SMS-i vaikerakenduseks õnnestus."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Manustest loobumine</item>
+ <item quantity="one">Manusest loobumine</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Helimanus"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Helimanuste esitamine"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Peata"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Sõnum saatjalt <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Sõnumi saamine kontaktilt <xliff:g id="SENDER">%s</xliff:g> ebaõnnestus: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Sõnum kontaktilt <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Saatmata sõnum kontaktile <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sõnumi saatmine kontaktile <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Sõnumi saatmine kontaktile <xliff:g id="CONTACT">%s</xliff:g> ebaõnnestus: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Sõnum kontaktile <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Sõnumi saamine kontaktilt <xliff:g id="SENDER">%s</xliff:g> ebaõnnestus: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Sõnum kontaktilt <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Saatmata sõnum kontaktile <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sõnumi saatmine kontaktile <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Sõnumi saatmine kontaktile <xliff:g id="GROUP">%s</xliff:g> ebaõnnestus: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Sõnum kontaktile <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aeg: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Sõnumi saatmine ebaõnnestus. Puudutage uuesti proovimiseks."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Vestlus osalejatega <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Teema kustutamine"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Video jäädvustamine"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Foto jäädvustamine"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Pildistamine"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Video salvestamise alustamine"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Lülitamine täisekraaniga kaamerale"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Esi- ja tagakaamera vahetamine"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Salvestamise peatamine ja video lisamine"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Lõpeta video salvestamine"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Sõnumside fotod"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotot salvestati albumisse „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto salvestati albumisse „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videot salvestati albumisse „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video salvestati albumisse „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> manust salvestati albumisse „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> manus salvestati albumisse „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> manust salvestati kausta „Allalaadimised”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> manus salvestati kausta „Allalaadimised”</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> manust salvestati</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> manus salvestati</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> manust ei saanud salvestada</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> manust ei saanud salvestada</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Salvestati MMS-manus"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Seaded"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arhiivitud"</string>
+ <string name="action_close" msgid="1840519376200478419">"Sule"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Täpsem"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Silumine"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Märguanded"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Heli"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Hääletu"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibratsioon"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokeeritud"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-i edastamisaruanded"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Edastamisaruande taotlus iga saadetud SMS-i kohta"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automaatne toomine"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS-sõnumite automaatne vastuvõtmine"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automaatne vastuvõtmine rändlusel"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"MMS-ide automaatne toomine rändluse ajal"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Rühma sõnumside"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"MMS-i kasutamine, kui mitmele kontaktile tuleb saata üks sõnum"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"SMS-i vaikerakendus"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"SMS-i vaikerakendus"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Teie telefoninumber"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Teadmata"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Väljuva sõnumi helid"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS-i tõmmistamine"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Saadud SMS-ide lähteandmete tõmmistamine välisesse talletusfaili"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS-i tõmmistamine"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Saadud MMS-ide lähteandmete tõmmistamine välisesse talletusfaili"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Raadiosidehoiatused"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Sõnumi valikud"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopeeri tekst"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Kuva üksikasjad"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Kustuta"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Edastamine"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Sõnumi üksikasjad"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tüüp: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Tekstisõnum"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimeediumsõnum"</string>
+ <string name="from_label" msgid="1947831848146564875">"Saatja: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Saaja: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Saadetud: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Vastu võetud: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Teema: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Suurus: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioriteet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Suur"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Tavaline"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Madal"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Varjatud saatja aadress"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Sõnumit ei saa manuste laadimise ajal saata."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Manust ei õnnestu laadida. Proovige uuesti."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Võrk pole valmis. Proovige uuesti."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Kustuta tekst"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Teksti ja numbrite vaheldumisi sisestamine"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Lisa rohkem osalejaid"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Osalejate kinnitamine"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Uue vestluse alustamine"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Selle üksuse valimine"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Video esitamine"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Inimesed ja valikud"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Silu"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Inimesed ja valikud"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Üldine"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Selles vestluses osalevad inimesed"</string>
+ <string name="action_call" msgid="6596167921517350362">"Helista"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Saatke sõnum"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Sõnumi saatmine&lt;br/&gt;&lt;small&gt;SIM-kaardilt <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Saada fotod</item>
+ <item quantity="one">Saada foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Saada helifailid</item>
+ <item quantity="one">Saada helifail</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Saada videod</item>
+ <item quantity="one">Saada video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Saada kontaktikaardid</item>
+ <item quantity="one">Saada kontaktikaart</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Saada manused</item>
+ <item quantity="one">Saada manus</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> manust on saatmiseks valmis</item>
+ <item quantity="one">Üks manus on saatmiseks valmis</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Tagasiside saatmine"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Kuvamine Google Play poes"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versiooni teave"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versioon %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Avatud lähtekoodi litsentsid"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Märguanded"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Manuselimiit on täis"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Manuse laadimine ebaõnnestus."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Kas lisada kontaktidesse?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Kontaktisiku lisamine"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Teema"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Teema: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kontakti kaardi laadimine"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kontaktkaarti ei õnnestunud laadida"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Kontakti kaardi kuvamine"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakti</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktkaardid"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Sünnipäev"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Märkmed"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Sõnumi edastamine"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Vastamine"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS on keelatud"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Saatmiseks määrake rakendus Sõnumside SMS-i vaikerakenduseks"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Määrake rakendus Sõnumside SMS-i vaikerakenduseks"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Muuda"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Sõnumite vastuvõtmiseks määrake rakendus Sõnumside SMS-i vaikerakenduseks"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS-sõnumite saatmiseks pole eelistatud SIM-kaarti valitud"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Seadme omanik pole seda rakendust lubanud."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Vestluses on liiga palju osalejaid"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Sobimatud kontaktid</item>
+ <item quantity="one">Sobimatu kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kaamera kujutist ei õnnestunud laadida"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Teie: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Mustand"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Kui alustate uut vestlust, loetletakse see siin"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arhiivitud vestlused kuvatakse siin"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Vestluste laadimine …"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Pilt"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Heliklipp"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontaktikaart"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Tagasivõtmine"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Proovi uuesti"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Uue sõnumi alustamiseks sisestage kontakti nimi või telefoninumber"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokeerimine"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokeeri <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Deblokeeri <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Kas blokeerida <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Saate sellelt numbrilt ikka sõnumeid, kuid te ei saa enam märguandeid. Vestlus arhiivitakse."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokeeritud kontaktid"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DEBLOKEERIMINE"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokeeritud kontaktid"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Dokumendikogust kujutise valimine"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sõnumi saatmine"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Sõnum on saadetud"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobiilne andmeside on välja lülitatud. Kontrollige seadeid."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Lennukirežiimis ei saa sõnumeid saata"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Sõnumit ei saanud saata"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Sõnum on alla laaditud"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobiilne andmeside on välja lülitatud. Kontrollige seadeid."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Lennukireżiimis ei saa sõnumeid alla laadida"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Sõnumit ei saanud alla laadida"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Null"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Üks"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Kaks"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Kolm"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Neli"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Viis"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Kuus"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Seitse"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Kaheksa"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Üheksa"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Operaatori <xliff:g id="CARRIERNAME">%1$s</xliff:g> kaudu ei õnnestu sõnumit saata, viga <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Tundmatu operaatori kaudu ei õnnestu sõnumit saata, viga <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Edastamine: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Sõnumit ei saadetud: teenus pole võrgus aktiveeritud"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Sõnumit ei saadetud: kehtetu sihtaadress"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Sõnumit ei saadetud: vigane sõnum"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Sõnumit ei saadetud: toetuseta sisu"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Sõnumit ei saadetud: toetuseta sõnum"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Sõnumit ei saadetud: liiga suur"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Uus sõnum"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Kuvamine"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Kujutis"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Sobivat rakendust ei õnnestunud leida"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Saaja eemaldamine"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Uus sõnum"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Tühista"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Pääsupunkti muutmine"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Määramata"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nimi"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-i puhverserver"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-i port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-i tüüp"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Kustuta APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Uus APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Salvesta"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Tühista"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Nimeväli ei saa olla tühi."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ei saa olla tühi."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-väli peab olema kolme numbriga."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC-väli peab olema kahe või kolme numbriga."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"APN-i vaikeseadete taastamine."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Lähtesta vaikeseadetele"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"APN-i vaikeseadete lähtestamine on lõpule viidud."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Pealkirjata"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Pääsupunktide nimed"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-id"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Uus APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Pääsupunkti nime seaded pole selle kasutaja jaoks saadaval"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kas kopeerida lõikelauale?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopeeri"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"SIM-kaardile <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Üldised"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Täpsemad"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Üldseaded"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Täpsemad seaded"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM-kaart „<xliff:g id="SIM_NAME">%s</xliff:g>”"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Individuaalsed SMS-id saadetakse kõikidele adressaatidele. Ainult teie saate vastuseid"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Üks MMS saadetakse kõikidele adressaatidele"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Tundmatu number"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Uus sõnum"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Uus sõnum."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-kaardi valija"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> on valitud, SIM-kaardi valija"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Teema muutmine"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM-kaardi valimine või objekti valimine"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Puudutage pikalt heli salvestamiseks"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Uue vestluse alustamine"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Sõnumside"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Sõnumside loend"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Sõnumside"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Uus sõnum"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Vestlusloend"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Vestluste laadimine …"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Sõnumite laadimine ..."</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Kuva rohkem vestlusi"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Kuva rohkem sõnumeid"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Vestlus on kustutatud"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Vestlus kustutati. Rakenduse Sõnumside eri vestluste kuvamiseks puudutage"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blokeeritud"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Deblokeeritud"</string>
+ <string name="db_full" msgid="8459265782521418031">"Talletusruumi on vähe. Osa andmetest võib kaduma minna."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Manuste valimine"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Valiku kinnitamine"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> on valitud"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Eemaldage üks või mitu manust ja proovige uuesti."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Võite proovida sõnumit saata, aga selle edastamine võib ebaõnnestuda, kui te ei eemalda üht või mitut manust."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Saate sõnumis saata vaid ühe video. Eemaldage täiendavad videod ja proovige uuesti."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Rakendusel Sõnumside ei õnnestunud manust laadida."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Saada ikkagi"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Vestlust ei õnnestunud alustada"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> on valitud"</string>
+</resources>
diff --git a/res/values-eu-rES/arrays.xml b/res/values-eu-rES/arrays.xml
new file mode 100644
index 0000000..5ea97f0
--- /dev/null
+++ b/res/values-eu-rES/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"gairik ez"</item>
+ <item msgid="272485471009191934">"gairik ez"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Bai"</item>
+ <item msgid="6049132459802288033">"Ez"</item>
+ <item msgid="3084376867445867895">"Ados"</item>
+ <item msgid="3155097332660174689">"Kar-kar"</item>
+ <item msgid="2611328818571146775">"Eskerrik asko"</item>
+ <item msgid="4881335087096496747">"Ados"</item>
+ <item msgid="2422296858597420738">"Ederki"</item>
+ <item msgid="4805581752819452687">"Banoa"</item>
+ <item msgid="4746700499431366214">"Ados, geroago erantzungo dizut"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
new file mode 100644
index 0000000..24813fe
--- /dev/null
+++ b/res/values-eu-rES/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mezuak"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mezuak"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Hautatu elkarrizketa"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Ezarpenak"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Bidali mezua"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Gehitu eranskina"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Laguntza"</string>
+ <string name="welcome" msgid="2857560951820802321">"Ongi etorri"</string>
+ <string name="skip" msgid="7238879696319945853">"Saltatu"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Hurrengoa &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Hurrengoa"</string>
+ <string name="exit" msgid="1905187380359981199">"Irten"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Ezarpenak &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Ezarpenak"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mezuak aplikazioak baimena behar du SMS mezuak bidaltzeko eta jasotzeko, deiak egiteko eta kontaktuak atzitzeko."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Baimenak aldatzeko, zoaz Ezarpenak &gt; Aplikazioak &gt; Mezuak &gt; Baimenak atalera."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Baimenak aldatzeko, zoaz Ezarpenak, Aplikazioak, Mezuak, Baimenak atalera."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Maiz erabilitakoak"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Kontaktu guztiak"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Bidali hona: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Atera irudiak edo grabatu bideoa"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Aukeratu gailu honetako irudiak"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Grabatu audioa"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Aukeratu argazkia"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Euskarria hautatu da."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Euskarria desautatu da."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> hautatuta"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"Irudiaren data: <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" msgid="8142175029053370662">"irudia"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Grabatu audioa"</string>
+ <string name="action_share" msgid="2143483844803153871">"Partekatu"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Oraintxe"</string>
+ <string name="posted_now" msgid="867560789350406701">"Orain"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ordu</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ordu</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> egun</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> egun</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> aste</item>
+ <item quantity="one">Astebete</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> hilabete</item>
+ <item quantity="one">Hilabete</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> urte</item>
+ <item quantity="one">Urtebete</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0 klaseko mezua"</string>
+ <string name="save" msgid="5081141452059463572">"Gorde"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Toki gutxi dago gailuan. Tokia egiteko, Mezuak aplikazioak automatikoki ezabatuko ditu mezu zaharragoak."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Memoria betetzen ari da"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Gailuan toki gehiago izan arte, baliteke Mezuak aplikazioak mezurik ez bidaltzea edo jasotzea."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS mezuetarako memoria gutxi dago. Baliteke mezuak ezabatu behar izatea."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Berretsi telefono-zenbakia"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Behin bakarrik egin beharreko urrats honi esker, Mezuak aplikazioak taldeko mezuak behar bezala bidaliko dituela ziurtatuko duzu."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefono-zenbakia"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Ezabatu multimedia-elementuak dituzten mezu guztiak"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Ezabatu <xliff:g id="DURATION">%s</xliff:g> baino zaharragoak diren mezuak"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Ezabatu automatikoki <xliff:g id="DURATION">%s</xliff:g> baino zaharragoak diren mezuak"</string>
+ <string name="ignore" msgid="7063392681130898793">"Egin ez ikusi"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Multimedia-elementuak dauzkaten mezu guztiak ezabatu nahi dituzu?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> baino zaharragoak diren mezuak ezabatu nahi dituzu?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> baino zaharragoak diren mezuak ezabatu eta ezabaketa automatikoa aktibatu nahi duzu?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> erabiltzaileak esan du"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Zuk esan duzu"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> erabiltzailearen mezua:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Mezu bat bidali duzu"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Bidaltzen…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ez da bidali. Berriro saiatzeko, ukitu hau."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ez da bidali. Berriro saiatzen…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Birbidali edo ezabatu"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Deitu larrialdi-zerbitzuetara. Ezin izan dugu entregatu testu-mezua."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Huts egin du"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"MMS mezu berria deskargatzeko prest"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"MMS mezu berria"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Ezin izan da deskargatu"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Berriro saiatzeko, ukitu hau"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Deskargatzeko, ukitu hau"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Deskargatu edo ezabatu"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Deskargatzen…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Mezua iraungi da edo ez dago erabilgarri"</string>
+ <string name="mms_info" msgid="3402311750134118165">"Tamaina: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>. Iraungitze-data: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>."</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Ezin da bidali. Hartzaileak ez du balio."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Zerbitzua ez dago sare honetan aktibatuta"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Ezin izan da bidali sareko arazo baten ondorioz"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Mezua iraungi da edo ez dago erabilgarri"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Gairik ez)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Igorle ezezaguna"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Bidalita"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Ezin izan da <xliff:g id="FROM">%2$s</xliff:g> erabiltzailearen <xliff:g id="SUBJECT">%1$s</xliff:g> mezua deskargatu."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Ezin izan da datu-basearen eragiketa osatu memoria gutxi dagoelako"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Ez da bidali mezua"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Ez dira bidali Mezuak aplikazioko mezu batzuk"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mezu daude <xliff:g id="CONVERSATIONS">%d</xliff:g> elkarrizketatan</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mezu daude elkarrizketa batean</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Ez da deskargatu mezua"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Ez dira deskargatu Mezuak aplikazioko mezu batzuk"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mezu daude <xliff:g id="CONVERSATIONS">%d</xliff:g> elkarrizketatan</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mezu daude elkarrizketa batean</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mezua ez da bidali <xliff:g id="NUMBER">%1$s</xliff:g> zenbakira"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Deitu larrialdi-zerbitzuetara. Ezin izan dugu entregatu <xliff:g id="NUMBER">%1$s</xliff:g> zenbakira bidalitako testu-mezua."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> mezu berri</item>
+ <item quantity="one">Mezu berria</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Hasi"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera ez dago erabilgarri"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera ez dago erabilgarri"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Bideo-grabaketa ez dago erabilgarri"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Ezin da gorde multimedia-elementua"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Ezin da atera argazkia"</string>
+ <string name="back" msgid="1477626055115561645">"Atzera"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Artxibatutakoak"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Ezabatu"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Artxibatu"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Kendu artxibotik"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desaktibatu jakinarazpenak"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Aktibatu jakinarazpenak"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Gehitu kontaktua"</string>
+ <string name="action_download" msgid="7786338136368564146">"Deskargatu"</string>
+ <string name="action_send" msgid="377635240181672039">"Bidali"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Ezabatu"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Mezua ezabatu nahi duzu?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Ezin da ekintza hori desegin."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Ezabatu"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Elkarrizketa hauek ezabatu nahi dituzu?</item>
+ <item quantity="one">Elkarrizketa ezabatu nahi duzu?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Ezabatu"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Utzi"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Hartzailea"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Hautatu hainbat irudi"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Berretsi hautapena"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Ezin da grabatu audioa. Saiatu berriro."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Ezin da erreproduzitu audioa. Saiatu berriro."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Ezin izan da gorde audioa. Saiatu berriro."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Eduki ukituta"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Argazkia"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio-klipa"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Bideoa"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontaktu-txartela"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Deskargatu"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Erantzun SMS bidez"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Erantzun MMS bidez"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Erantzun"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> parte-hartzaile</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> parte-hartzaile</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ni"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontaktua blokeatu eta artxibatu egin da"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontaktua desblokeatu da eta artxibotik kendu da"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> artxibatuta"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> artxibotik kenduta"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Jakinarazpenak desaktibatuta daude"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Jakinarazpenak aktibatuta daude"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Dena ezarri da. Ukitu berriro Bidali."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Mezuak aplikazioa behar bezala ezarri da SMS mezuetarako aplikazio lehenetsi gisa."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Baztertu eranskinak</item>
+ <item quantity="one">Baztertu eranskina</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audio-eranskina"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Erreproduzitu audio-eranskina"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausatu"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> erabiltzailearen mezua: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> igorlearen mezuak huts egin du: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> igorlearen mezua: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Ez zaio bidali mezua <xliff:g id="CONTACT">%s</xliff:g> erabiltzaileari: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> erabiltzaileari mezua bidaltzen: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Ezin izan zaio bidali mezua <xliff:g id="CONTACT">%s</xliff:g> erabiltzaileari: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> erabiltzailearentzako mezua: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> igorlearen mezuak huts egin du: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> igorlearen mezua: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Ez da bidali mezua <xliff:g id="GROUP">%s</xliff:g> taldera: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> taldera mezua bidaltzen: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Ezin izan da bidali mezua <xliff:g id="GROUP">%s</xliff:g> taldera: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> taldearentzako mezua: <xliff:g id="MESSAGE">%s</xliff:g>. Ordua: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mezuak huts egin du. Ukitu berriro saiatzeko."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Elkarrizketa erabiltzaile hauekin (<xliff:g id="PARTICIPANTS">%s</xliff:g>)"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Ezabatu gaia"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Grabatu bideoa"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Atera irudi finkoa"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Atera argazkia"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Hasi bideoa grabatzen"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Aldatu pantaila osoko kamerara"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Aldatu aurreko eta atzeko kameren artean"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Utzi grabatzeari eta erantsi bideoa"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Utzi bideoa grabatzeari"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Mezuak aplikazioko argazkiak"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> argazki gorde dira \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albumean</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> argazki gorde da \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albumean</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> bideo gorde dira \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albumean</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bideo gorde da \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albumean</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> eranskin gorde dira \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albumean</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> eranskin gorde da \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albumean</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> eranskin gorde dira \"Deskargak\" atalean</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> eranskin gorde da \"Deskargak\" atalean</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">Gorde dira <xliff:g id="QUANTITY_1">%d</xliff:g> eranskin</item>
+ <item quantity="one">Gorde da <xliff:g id="QUANTITY_0">%d</xliff:g> eranskin</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Ezin izan dira gorde <xliff:g id="QUANTITY_1">%d</xliff:g> eranskin</item>
+ <item quantity="one">Ezin izan da gorde <xliff:g id="QUANTITY_0">%d</xliff:g> eranskin</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMSko eranskina gordeta dago"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Ezarpenak"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Artxibatuta"</string>
+ <string name="action_close" msgid="1840519376200478419">"Itxi"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Ezarpen aurreratuak"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Araztu"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Jakinarazpenak"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Soinua"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Isilik"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Egin dar-dar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokeatuta"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMSak entregatzearen txostenak"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Eskatu bidaltzen duzun SMS bakoitzaren entrega-txostena"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Eskuratu automatikoki"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Eskuratu MMS mezuak automatikoki"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Eskuratu automatikoki ibiltaritzan"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Eskuratu MMS mezuak automatikoki ibiltaritzan"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Talde-mezularitza"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Erabili MMSak mezu bakarra bidaltzeko hartzaile ugari daudenean"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"SMS mezuetarako aplikazio lehenetsia"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"SMS mezuetarako aplikazio lehenetsia"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Zure telefono-zenbakia"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Ezezaguna"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Irteerako mezuen soinuak"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Irauli SMSak"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Irauli jasotako SMS mezuen prozesatu gabeko datuak kanpoko memoriako fitxategira"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Irauli MMSak"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Irauli jasotako MMS mezuen prozesatu gabeko datuak kanpoko memoriako fitxategira"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Hari gabeko alertak"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Mezuaren aukerak"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopiatu testua"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ikusi xehetasunak"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Ezabatu"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Birbidali"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Mezuaren xehetasunak"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Mota: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Testu-mezua"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedia-mezua"</string>
+ <string name="from_label" msgid="1947831848146564875">"Igorlea: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Hartzailea: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Bidaltze-data: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Jasotze-data: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Gaia: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Tamaina: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Lehentasuna: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM txartela: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Handia"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normala"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Txikia"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"<xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>. SIM txartela"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Igorlearen helbidea ezkutatuta badago"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Ezin da mezua bidali eranskinak kargatzen ari diren bitartean."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Ezin da kargatu eranskina. Saiatu berriro."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Sarea ez dago prest. Saiatu berriro."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Ezabatu testua"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Aldatu testu eta zenbaki moduen artean"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Gehitu parte-hartzaile gehiago"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Berretsi parte-hartzaileak"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Hasi elkarrizketa bat"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Hautatu elementu hau"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Erreproduzitu bideoa"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Jendea eta aukerak"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Araztu"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Jendea eta aukerak"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Orokorra"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Elkarrizketa honetako jendea"</string>
+ <string name="action_call" msgid="6596167921517350362">"Deitu"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Bidali mezua"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Bidali mezua &lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g> txarteletik&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Bidali argazkiak</item>
+ <item quantity="one">Bidali argazkia</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Bidali audioak</item>
+ <item quantity="one">Bidali audioa</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Bidali bideoak</item>
+ <item quantity="one">Bidali bideoa</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Bidali kontaktu-txartelak</item>
+ <item quantity="one">Bidali kontaktu-txartela</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Bidali eranskinak</item>
+ <item quantity="one">Bidali eranskina</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> eranskin bidaltzeko prest daude</item>
+ <item quantity="one">Eranskin bat bidaltzeko prest dago</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Bidali iritzia"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ikusi Google Play Store dendan"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Bertsioari buruzko informazioa"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"%1$s bertsioa"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Kode irekiko lizentziak"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Jakinarazpenak"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Eranskinen mugara iritsi zara"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Ezin izan da kargatu eranskina."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Kontaktuetan gehitu nahi duzu?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Gehitu kontaktua"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Gaia"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Gaia: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kontaktu-txartela kargatzen"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Ezin izan da kargatu kontaktu-txartela"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ikusi kontaktu-txartela"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktu</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontaktu</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktu-txartelak"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Urtebetetzea"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Oharrak"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Birbidali mezua"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Erantzun"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMSak desgaituta"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Mezuak bidaltzeko, ezarri Mezuak aplikazioa SMS mezuetarako aplikazio lehenetsi gisa"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Ezarri Mezuak aplikazioa SMS mezuetarako aplikazio lehenetsi gisa"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Aldatu"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Mezuak jasotzeko, ezarri Mezuak aplikazioa SMS mezuetarako aplikazio lehenetsi gisa"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Ez da hautatu SIM txartel hobetsirik SMS mezuak bidaltzeko"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Gailuaren jabeak ez du aplikazio hau erabiltzea baimendu."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Ados"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Parte-hartzaile gehiegi daude elkarrizketan"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Kontaktuek ez dute balio</item>
+ <item quantity="one">Kontaktuak ez du balio</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Ezin izan da kargatu kamerako irudia"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Zu: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Zirriborroa"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Elkarrizketak hasten dituzunean, hemen agertuko dira"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Artxibatutako elkarrizketak hemen ageri dira"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Elkarrizketak kargatzen…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Argazkia"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio-klipa"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Bideoa"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontaktu-txartela"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Desegin"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Saiatu berriro"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Mezu berria hasteko, idatzi kontaktu baten izena edo telefono-zenbakia."</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokeatu"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokeatu <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desblokeatu <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> blokeatu nahi duzu?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Zenbaki horren mezuak jasotzen jarraituko duzu, baina ez duzu horren jakinarazpenik jasoko aurrerantzean. Elkarrizketa artxibatu egingo da."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokeatutako kontaktuak"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOKEATU"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokeatutako kontaktuak"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Aukeratu irudia dokumentuen liburutegitik"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Mezua bidaltzen"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mezua bidali da"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mugikorreko datuak desaktibatuta daude. Egiaztatu ezarpenak."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Ezin dira bidali mezuak hegaldi moduan"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Ezin izan da bidali mezua"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mezua deskargatu da"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mugikorreko datuak desaktibatuta daude. Egiaztatu ezarpenak."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Ezin da deskargatu mezua hegaldi moduan"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Ezin izan da deskargatu mezua"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Bat"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Bi"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Hiru"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Lau"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Bost"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sei"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Zazpi"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Zortzi"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Bederatzi"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Ezin da bidali mezua <xliff:g id="CARRIERNAME">%1$s</xliff:g> operadorearekin. Errorea: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Ezin da bidali mezua operadore ezezagunarekin. Errorea: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Ez da bidali mezua: zerbitzua ez dago aktibatuta sarean"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mezua ez da bidali. Helmugaren helbideak ez du balio."</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mezua ez da bidali. Ez du balio."</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mezua ez da bidali. Edukia ez da onartzen."</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mezua ez da bidali. Ez da onartzen."</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Ez da bidali mezua: handiegia da"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mezu berria"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ikusi"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Irudia"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Ezin izan da aurkitu aplikazio egokia"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Kendu hartzailea"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mezu berria"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Utzi"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Editatu sarbide-puntua"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ezarri gabe"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Izena"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxya"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS ataka"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN mota"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Ezabatu APNa"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN berria"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Gorde"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Baztertu"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"\"Izena\" eremua ezin da hutsik utzi."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"\"APN\" eremua ezin da hutsik utzi."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"\"MCC\" eremuak 3 digitu izan behar ditu."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"\"MNC\" eremuak 2 edo 3 digitu izan behar ditu."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"APN ezarpen lehenetsiak leheneratzen."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Berrezarri ezarpen lehenetsiak"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"APN ezarpen lehenetsiak berrezarri dira."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Izengabea"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Sarbide-puntuen izenak"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNak"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN berria"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Erabiltzaile honek ezin ditu APN ezarpenak atzitu"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Arbelean kopiatu nahi duzu?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiatu"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> txartelean"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Orokorra"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Ezarpen aurreratuak"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Ezarpen orokorrak"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Ezarpen aurreratuak"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM txartela"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Bidali SMS mezu bana hartzaile guztiei. Zuk bakarrik jasoko dituzu erantzunak"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Bidali MMS mezu bakarra hartzaile guztiei"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Zenbaki ezezaguna"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mezu berria"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mezu berria."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM hautatzailea"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> hautatuta, SIM hautatzailea"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editatu gaia"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Hautatu SIM txartela edo editatu gaia"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Eduki sakatuta audioa grabatzeko"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Hasi elkarrizketa bat"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mezuak"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Mezuak aplikazioko zerrenda"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mezuak"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mezu berria"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Elkarrizketa-zerrenda"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Elkarrizketak kargatzen"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Mezuak kargatzen"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ikusi elkarrizketa gehiago"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Ikusi mezu gehiago"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Elkarrizketa ezabatu egin da"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Ezabatu egin da elkarrizketa. Ukitu hau Mezuak aplikazioko beste elkarrizketa bat erakusteko."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blokeatu da"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Desblokeatu da"</string>
+ <string name="db_full" msgid="8459265782521418031">"Toki gutxi geratzen da. Agian ez dira gordeko datu batzuk."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Hautatu eranskinak"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Berretsi hautapena"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> hautatuta"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Kendu eranskin bat edo gehiago, eta saiatu berriro."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Hala eta guztiz ere, mezua bidaltzen saia zaitezke, baina agian ez da entregatuko, eranskinetako batzuk kendu ezean."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Bideo bakarra bidal dezakezu mezu bakoitzeko. Kendu gainerako bideoak eta saiatu berriro."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Mezuak aplikazioak ezin izan du eranskina kargatu."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Bidali halere"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Ezin izan da hasi elkarrizketa"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> hautatu da"</string>
+</resources>
diff --git a/res/values-fa/arrays.xml b/res/values-fa/arrays.xml
new file mode 100644
index 0000000..01b6f3e
--- /dev/null
+++ b/res/values-fa/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"بدون موضوع"</item>
+ <item msgid="272485471009191934">"بدون موضوع"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"بله"</item>
+ <item msgid="6049132459802288033">"خیر"</item>
+ <item msgid="3084376867445867895">"تأیید"</item>
+ <item msgid="3155097332660174689">"هه‌هه"</item>
+ <item msgid="2611328818571146775">"سپاسگزارم"</item>
+ <item msgid="4881335087096496747">"موافقم"</item>
+ <item msgid="2422296858597420738">"خوب"</item>
+ <item msgid="4805581752819452687">"در راهم"</item>
+ <item msgid="4746700499431366214">"بسیار خوب، بگذارید بعداً با شما تماس بگیرم"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..6a78532
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"پیام‌رسانی"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"پیام‌رسانی"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"انتخاب مکالمه"</string>
+ <string name="action_settings" msgid="1329008122345201684">"تنظیمات"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"ارسال پیام"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"افزودن پیوست"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"راهنما"</string>
+ <string name="welcome" msgid="2857560951820802321">"خوش آمدید"</string>
+ <string name="skip" msgid="7238879696319945853">"رد شدن"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"‏بعدی &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"بعدی"</string>
+ <string name="exit" msgid="1905187380359981199">"خروج"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"‏تنظیمات &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"تنظیمات"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"«پیام‌رسانی» به مجوز دسترسی به پیامک، تلفن و مخاطبین نیاز دارد."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"‏می‌توانید مجوزها را در تنظیمات &gt; برنامه‌ها &gt; پیام‌رسانی &gt; مجوزها تغییر دهید."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"می‌توانید مجوزها را در تنظیمات، برنامه‌ها، پیام‌رسانی، مجوزها تغییر دهید."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"مکرر"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"همه مخاطبین"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"ارسال به <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"گرفتن عکس یا ویدیو"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"انتخاب تصاویر از این دستگاه"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ضبط صدا"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"انتخاب عکس"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"رسانه انتخاب شده است."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"رسانه انتخاب نشده است."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> مورد انتخاب شد"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"تاریخ تصویر <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" msgid="8142175029053370662">"تصویر"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ضبط صدا"</string>
+ <string name="action_share" msgid="2143483844803153871">"اشتراک‌گذاری"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"هم‌اکنون"</string>
+ <string name="posted_now" msgid="867560789350406701">"اکنون"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one">‏<xliff:g id="COUNT_1">%d</xliff:g> دقیقه</item>
+ <item quantity="other">‏<xliff:g id="COUNT_1">%d</xliff:g> دقیقه</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one">‏<xliff:g id="COUNT_1">%d</xliff:g> ساعت</item>
+ <item quantity="other">‏<xliff:g id="COUNT_1">%d</xliff:g> ساعت</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one">‏<xliff:g id="COUNT_1">%d</xliff:g> روز</item>
+ <item quantity="other">‏<xliff:g id="COUNT_1">%d</xliff:g> روز</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one">‏<xliff:g id="COUNT">%d</xliff:g> هفته</item>
+ <item quantity="other">‏<xliff:g id="COUNT">%d</xliff:g> هفته</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one">‏<xliff:g id="COUNT">%d</xliff:g> ماه</item>
+ <item quantity="other">‏<xliff:g id="COUNT">%d</xliff:g> ماه</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one">‏<xliff:g id="COUNT">%d</xliff:g> سال</item>
+ <item quantity="other">‏<xliff:g id="COUNT">%d</xliff:g> سال</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"پیام رده ۰"</string>
+ <string name="save" msgid="5081141452059463572">"ذخیره‌"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"فضای ذخیره‌سازی دستگاه کم است. «پیام‌رسانی» برای آزاد کردن فضا، پیام‌های قدیمی‌تر را به‌طور خودکار پاک می‌کند."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"فضای ذخیره‌سازی رو به پایان است"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"شاید تا وقتی فضای بیشتری در دستگاهتان در دسترس نباشد، «پیام‌رسانی» پیام ارسال یا دریافت نکند."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"فضای ذخیره‌سازی پیامک کم است. ممکن است لازم باشد پیام‌ها را حذف کنید."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"شماره تلفنتان را تأیید کنید"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"با انجام این مرحله یک‌باره مطمئن می‌شوید «پیام‌رسانی» پیام‌های گروهی شما را به‌درستی تحویل می‌دهد."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"شماره تلفن"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"حذف همه پیام‌های دارای رسانه"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"حذف پیام‌های قدیمی‌تر از <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"حذف خودکار پیام‌های قدیمی‌تر از <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"نادیده گرفتن"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"همه پیام‌های دارای رسانه حذف شوند؟"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"پیام‌های قدیمی‌تر از <xliff:g id="DURATION">%s</xliff:g> حذف شوند؟"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"پیام‌های قدیمی‌تر از <xliff:g id="DURATION">%s</xliff:g> حذف شود و حذف خودکار روشن شود؟"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> گفت"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"گفتید"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"پیام از <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"پیامی ارسال کردید"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"در حال ارسال…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"ارسال نشد. برای امتحان مجدد لمس کنید."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"ارسال نشد. در حال امتحان مجدد…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ارسال مجدد یا حذف"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"لطفاً یک تماس صوتی با خدمات اضطراری برقرار کنید. در حال حاضر تحویل پیامک شما ممکن نیست."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"انجام نشد"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"‏پیام MMS جدید برای دانلود"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"‏پیام MMS جدید"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"دانلود نشد"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"برای امتحان مجدد لمس کنید"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"برای دانلود لمس کنید"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"دانلود یا حذف"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"در حال دانلود…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"پیام منقضی شده است یا در دسترس نیست"</string>
+ <string name="mms_info" msgid="3402311750134118165">"اندازه: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>، تاریخ انقضا: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"ارسال نمی‌شود. گیرنده معتبر نیست."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"سرویس در شبکه فعال نشده است"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"به علت مشکل شبکه ارسال نشد"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"پیام منقضی شده است یا در دسترس نیست"</string>
+ <string name="no_subject" msgid="5587715902648568767">"‫(بدون موضوع)‏"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"فرستنده ناشناس"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"تحویل داده شد"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"‫پیام <xliff:g id="SUBJECT">%1$s</xliff:g> از <xliff:g id="FROM">%2$s</xliff:g> دانلود نشد.‏"</string>
+ <string name="low_memory" msgid="5300743415198486619">"به دلیل حافظه کم، عملیات پایگاه داده انجام نشد"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"پیام ارسال نشد"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"بعضی از پیام‌ها در «پیام‌رسانی» ارسال نشدند"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one">‏<xliff:g id="MESSAGES_1">%d</xliff:g> پیام در <xliff:g id="CONVERSATIONS">%d</xliff:g> مکالمه</item>
+ <item quantity="other">‏<xliff:g id="MESSAGES_1">%d</xliff:g> پیام در <xliff:g id="CONVERSATIONS">%d</xliff:g> مکالمه</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"پیام دانلود نشد"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"بعضی از پیام‌ها در «پیام‌رسانی» دانلود نشدند"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one">‏<xliff:g id="MESSAGES_1">%d</xliff:g> پیام در <xliff:g id="CONVERSATIONS">%d</xliff:g> مکالمه</item>
+ <item quantity="other">‏<xliff:g id="MESSAGES_1">%d</xliff:g> پیام در <xliff:g id="CONVERSATIONS">%d</xliff:g> مکالمه</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"پیام به <xliff:g id="NUMBER">%1$s</xliff:g> ارسال نشد"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"لطفاً یک تماس صوتی با خدمات اضطراری برقرار کنید. در حال حاضر تحویل پیامک شما به <xliff:g id="NUMBER">%1$s</xliff:g> ممکن نیست."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> پیام جدید</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> پیام جدید</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"شروع"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"دوربین در دسترس نیست"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"دوربین در دسترس نیست"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ضبط ویدیو در دسترس نیست"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"رسانه ذخیره نمی‌شود"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"عکس نمی‌گیرد"</string>
+ <string name="back" msgid="1477626055115561645">"برگشت"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"بایگانی شده"</string>
+ <string name="action_delete" msgid="4076795795307486019">"حذف"</string>
+ <string name="action_archive" msgid="5437034800324083170">"بایگانی"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"لغو بایگانی"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"خاموش کردن اعلان‌ها"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"روشن کردن اعلان‌ها"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"افزودن مخاطب"</string>
+ <string name="action_download" msgid="7786338136368564146">"دانلود"</string>
+ <string name="action_send" msgid="377635240181672039">"ارسال"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"حذف"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"این پیام حذف شود؟"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"این عملکرد قابل واگرد نیست."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"حذف"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">این مکالمه حذف شود؟</item>
+ <item quantity="other">این مکالمه‌ها حذف شود؟</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"حذف"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"لغو"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"به"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"انتخاب چند تصویر"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"تأیید انتخاب"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"صدا ضبط نمی‌شود. دوباره امتحان کنید."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"صدا پخش نمی‌شود. دوباره امتحان کنید."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"صدا ذخیره نشد. دوباره امتحان کنید."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"لمس کنید و نگه‌دارید"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"، "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"تصویر"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"کلیپ صوتی"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ویدیو"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"کارت ویزیت"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"دانلود"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"پاسخ از طریق پیامک"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"‏پاسخ از طریق MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"پاسخ"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one">‏<xliff:g id="COUNT_1">%d</xliff:g> شرکت‌کننده</item>
+ <item quantity="other">‏<xliff:g id="COUNT_1">%d</xliff:g> شرکت‌کننده</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"من"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"مخاطب مسدود و بایگانی شد"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"انسداد مخاطب برطرف شد و از بایگانی خارج شد"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> بایگانی شد"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> از بایگانی خارج شد"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"اعلان‌ها خاموش شد"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"اعلان‌ها روشن شد"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"همه چیز تنظیم شد. ارسال را دوباره لمس کنید."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"«پیام‌رسانی» با موفقیت به‌عنوان برنامه پیش‌فرض پیامک تنظیم شد."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">صرف‌‌نظر از پیوست</item>
+ <item quantity="other">صرف‌‌نظر از پیوست‌ها</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"پیوست صوتی"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"پخش پیوست صوتی"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"توقف موقت"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"پیام از <xliff:g id="SENDER">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"پیام ناموفق از <xliff:g id="SENDER">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"پیام از <xliff:g id="SENDER">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"پیام ارسال نشده به <xliff:g id="CONTACT">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"ارسال پیام به <xliff:g id="CONTACT">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"پیام ناموفق به <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"پیام به <xliff:g id="CONTACT">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"پیام ناموفق از <xliff:g id="SENDER">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>. ‏<xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"پیام از <xliff:g id="SENDER">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>. ‏<xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"پیام ارسال نشده به <xliff:g id="GROUP">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"ارسال پیام به <xliff:g id="GROUP">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"پیام ناموفق به <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"پیام به <xliff:g id="GROUP">%s</xliff:g>: ‏<xliff:g id="MESSAGE">%s</xliff:g>. زمان: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"پیام ناموفق. برای امتحان مجدد، لمس کنید."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"مکالمه با <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"حذف موضوع"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"فیلم‌برداری"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ضبط تصویر ثابت"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"عکس گرفتن"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"شروع فیلم‌برداری"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"تغییر به دوربین تمام صفحه"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"جابه‌جایی بین دوربین جلو و عقب"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"توقف ضبط و پیوست ویدیو"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"توقف ضبط ویدیو"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"عکس‌های «پیام‌رسانی»"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> عکس در آلبوم «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ذخیره شد</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> عکس در آلبوم «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ذخیره شد</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ویدیو در آلبوم «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ذخیره شد</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ویدیو در آلبوم «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ذخیره شد</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> پیوست در آلبوم «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ذخیره شد</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> پیوست در آلبوم «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ذخیره شد</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> پیوست در «دانلودها» ذخیره شد</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> پیوست در «دانلودها» ذخیره شد</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one">‏<xliff:g id="QUANTITY_1">%d</xliff:g> پیوست ذخیره شد</item>
+ <item quantity="other">‏<xliff:g id="QUANTITY_1">%d</xliff:g> پیوست ذخیره شد</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">‏<xliff:g id="QUANTITY_1">%d</xliff:g> پیوست ذخیره نشد</item>
+ <item quantity="other">‏<xliff:g id="QUANTITY_1">%d</xliff:g> پیوست ذخیره نشد</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"‏پیوست MMS ذخیره شد"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"تنظیمات"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"بایگانی شد"</string>
+ <string name="action_close" msgid="1840519376200478419">"بستن"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"پیشرفته"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"اشکال‌زدایی"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"اعلان‌ها"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"صدا"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"بی‌صدا"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"لرزش"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"مسدود شده"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"گزارش‌های تحویل پیامک"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"درخواست گزارش تحویل برای هر پیامکی که ارسال می‌کنید"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"بازیابی خودکار"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"‏بازیابی خودکار پیام‌های MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"بازیابی خودکار در طول رومینگ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"‏بازیابی خودکار MMS هنگام رومینگ"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"پیام‌رسانی گروهی"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"‏در صورت وجود چند گیرنده، برای ارسال یک پیام از MMS استفاده شود"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"برنامه پیامک پیش‌فرض"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"برنامه پیامک پیش‌فرض"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"شماره تلفن شما"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"نامشخص"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"صداهای پیام خروجی"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"نسخه‌برداری پیامک"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"نسخه‌برداری اطلاعات خام پیامک دریافتی در فایل فضای ذخیره‌سازی خارجی"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"‏نسخه‌برداری MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"‏نسخه‌برداری از اطلاعات خام MMS دریافتی در فایل فضای ذخیره‌سازی خارجی"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"هشدارهای بی‌سیم"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"گزینه‌های پیام"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"کپی کردن نوشتار"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"مشاهده جزئیات"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"حذف"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"بازارسال"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"جزئیات پیام"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"نوع: "</string>
+ <string name="text_message" msgid="7415419755252205721">"پیامک"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"پیام چندرسانه‌ای"</string>
+ <string name="from_label" msgid="1947831848146564875">"از: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"به: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ارسال شده: "</string>
+ <string name="received_label" msgid="4442494712757995203">"دریافت شده: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"موضوع: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"اندازه: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"اولویت: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"سیم‌کارت: "</string>
+ <string name="priority_high" msgid="728836357310908368">"زیاد"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"عادی"</string>
+ <string name="priority_low" msgid="7398724779026801851">"کم"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"سیم‌کارت <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"مخفی کردن آدرس فرستنده"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"نمی‌توانید در حال بارگیری پیوست‌ها، پیام ارسال کنید."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"پیوست بارگیری نمی‌شود. دوباره امتحان کنید."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"شبکه آماده نیست. دوباره امتحان کنید."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"حذف نوشتار"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"جابه‌جایی بین وارد کردن نوشتار و عدد"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"افزودن شرکت‌کننده‌های بیشتر"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"تأیید شرکت‌کنندگان"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"شروع مکالمه جدید"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"انتخاب این مورد"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"پخش ویدیو"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"افراد و گزینه‌ها"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"اشکال‌زدایی"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"افراد و گزینه‌ها"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"کلی"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"افراد در این مکالمه"</string>
+ <string name="action_call" msgid="6596167921517350362">"برقراری تماس تلفنی"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"ارسال پیام"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"‏ارسال پیام&lt;br/&gt;&lt;small&gt;از <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ارسال عکس</item>
+ <item quantity="other">ارسال عکس‌ها</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ارسال فایل صوتی</item>
+ <item quantity="other">ارسال فایل‌های صوتی</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">ارسال ویدیو</item>
+ <item quantity="other">ارسال ویدیوها</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">ارسال کارت تماس</item>
+ <item quantity="other">ارسال کارت‌های تماس</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">ارسال پیوست</item>
+ <item quantity="other">ارسال پیوست‌ها</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one">‏<xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> پیوست آماده ارسال است</item>
+ <item quantity="other">‏<xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> پیوست آماده ارسال است</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ارسال بازخورد"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"‏نمایش در فروشگاه Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"اطلاعات نسخه"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"‏نسخه %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"مجوزهای منبع باز"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"اعلان‌ها"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"به حد مجاز پیوست رسیدید"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"پیوست بارگیری نشد."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"به مخاطبین افزوده شود؟"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"افزودن مخاطب"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"موضوع"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"موضوع: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"در حال بارگیری کارت مخاطب"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"کارت مخاطب بارگیری نشد"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"مشاهده کارت مخاطب"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> مخاطب</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> مخاطب</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"کارت‌های مخاطب"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"تاریخ تولد"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"یادداشت‌ها"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"بازارسال پیام"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"پاسخ"</string>
+ <string name="plus_one" msgid="9010288417554932581">"1+"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"پیامک غیرفعال شد"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"برای ارسال، «پیام‌رسانی» را به‌عنوان برنامه پیش‌فرض پیامک تنظیم کنید"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"«پیام‌رسانی» را به‌عنوان برنامه پیش‌فرض پیامک تنظیم کنید"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"تغییر"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"برای دریافت پیام، «پیام‌رسانی» را به‌عنوان برنامه پیش‌فرض پیامک تنظیم کنید"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"هیچ سیم‌کارت برگزیده‌ای برای ارسال پیامک‌ها انتخاب نشده است"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"مالک دستگاه این برنامه را مجاز نکرده است."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"تأیید"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"شرکت‌کننده‌های خیلی زیادی در مکالمه وجود دارد"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">مخاطب نامعتبر</item>
+ <item quantity="other">مخاطبین نامعتبر</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"تصویر دوربین بارگیری نشد"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"شما: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"پيش‌نويس"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"وقتی مکالمه جدیدی را شروع کنید، آن را در این لیست مشاهده می‌کنید"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"مکالمات بایگانی‌شده در اینجا نشان داده می‌شود"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"در حال بارگیری مکالمات…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"تصویر"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"کلیپ صوتی"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ویدیو"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"کارت ویزیت"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"واگرد"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"امتحان مجدد"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"نام یا شماره تلفن مخاطب را برای شروع یک پیام جدید وارد کنید"</string>
+ <string name="action_block" msgid="9032076625645190136">"مسدود کردن"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"مسدود کردن <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"رفع انسداد <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> مسدود شود؟"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"همچنان از این شماره پیام‌ها را دریافت می‌کنید، اما دیگر به شما اطلاع داده نمی‌شود. این مکالمه بایگانی می‌شود."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"مخاطبین مسدود شده"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"رفع انسداد"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"مخاطبین مسدود شده"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"انتخاب تصویر از کتابخانه سند"</string>
+ <string name="sending_message" msgid="6363584950085384929">"در حال ارسال پیام"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"پیام ارسال شد"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"داده شبکه تلفن همراه خاموش است. تنظیماتتان را بررسی کنید."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"در حالت هواپیما پیام‌ها ارسال نمی‌شوند"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"پیام ارسال نشد"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"پیام دانلود شد"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"داده سلولی خاموش است. تنظیماتتان را بررسی کنید."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"پیام‌ در حالت هواپیما دانلود نمی‌شود"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"پیام دانلود نشد"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"صفر"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"یک"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"دو"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"سه"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"چهار"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"پنج"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"شش"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"هفت"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"هشت"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"نه"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"ارسال پیام با <xliff:g id="CARRIERNAME">%1$s</xliff:g> امکان‌پذیر نیست، خطای <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"ارسال پیام با شرکت مخابراتی ناشناس امکان‌پذیر نیست، خطای <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"بازارسال: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"پیام ارسال نشد: سرویس در شبکه فعال نیست"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"پیام ارسال نشد: آدرس مقصد نامعتبر است"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"پیام ارسال نشد: پیام نامعتبر است"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"پیام ارسال نشد: محتوا پشتیبانی نمی‌شود"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"پیام ارسال نشد: پیام پشتیبانی نمی‌شود"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"پیام ارسال نشد: بیش از حد بزرگ است"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"پیام جدید"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"مشاهده"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"تصویر"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"برنامه مناسبی یافت نشد"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"حذف گیرنده"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"پیام جدید"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"لغو"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ویرایش نقطه دستیابی"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"تنظیم نشده"</string>
+ <string name="apn_name" msgid="1572691851070894985">"نام"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"نام نقطه دستیابی"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"پروکسی فراپیام"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"درگاه فراپیام"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"نوع نام نقطه دستیابی"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"حذف نام نقطه دستیابی"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"نام نقطه دستیابی جدید"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"ذخیره‌"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"صرف‌نظر کردن"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"فیلد نام نباید خالی باشد."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"نام نقطه دستیابی نباید خالی باشد."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"‏فیلد MCC باید ۳ رقمی باشد."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"‏فیلد MNC باید ۲ یا ۳ رقمی باشد."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"درحال بازیابی تنظیمات پیش‌فرض نام نقطه دستیابی."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"بازنشانی به پیش‌فرض"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"تنظیمات پیش‌فرض نام نقطه دستیابی بازنشانی شد."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"بدون عنوان"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"نام‌های نقطه دسترسی"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"نام‌های نقاط دستیابی"</string>
+ <string name="menu_new" msgid="8286285392706532511">"نام نقطه دستیابی جدید"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"تنظیمات نام تقطه دسترسی برای این کاربر در دسترس نیست"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"کپی در بریده‌دان؟"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"کپی"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"به <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"کلی"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"پیشرفته"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"تنظیمات کلی"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"تنظیمات پیشرفته"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"سیم کارت «<xliff:g id="SIM_NAME">%s</xliff:g>»"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ارسال پیامک‌های جداگانه به همه گیرندگان. فقط شما جواب‌ها را دریافت می‌کنید"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"‏ارسال یک MMS تکی به همه گیرندگان"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"شماره ناشناس"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"پیام جدید"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"پیام جدید."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"انتخاب سیم"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> انتخاب شد، انتخاب‌گر سیم‌کارت"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"ویرایش موضوع"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"انتخاب سیم کارت یا ویرایش موضوع"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"برای ضبط کردن صدا، لمس کنید و نگه‌دارید"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"شروع مکالمه جدید"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"پیام‌رسانی"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"فهرست «پیام‌رسانی»"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"پیام‌رسانی"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"پیام جدید"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"فهرست مکالمات"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"در حال بارگیری مکالمات"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"در حال بارگیری پیام‌ها"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"مشاهده مکالمات بیشتر"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"مشاهده پیام‌های دیگر"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"مکالمه حذف شد"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"مکالمه حذف شد. لمس کنید تا مکالمه «پیام‌رسانی» دیگری نشان داده شود."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"مسدود شد"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"گشوده شد"</string>
+ <string name="db_full" msgid="8459265782521418031">"فضای ذخیره‌سازی کم است. ممکن است برخی از اطلاعات از دست بروند."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"انتخاب پیوست‌ها"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"تأیید انتخاب"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> مورد انتخاب شد"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"لطفاً یک یا چند پیوست را بردارید و دوباره امتحان کنید."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"می‌توانید سعی کنید پیامتان را ارسال کنید اما شاید تا وقتی که یک یا چند پیوست را برندارید، تحویل داده نشود."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"فقط می‌توانید یک ویدیو در هر پیام ارسال کنید. لطفاً ویدیوهای اضافی را حذف کنید و دوباره امتحان نمایید."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"«پیام‌رسانی» پیوست را بارگیری نکرد."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"در هر حال فرستاده شود"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"مکالمه را نمی‌توان شروع کرد"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g>‏ (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> انتخاب شد"</string>
+</resources>
diff --git a/res/values-fi/arrays.xml b/res/values-fi/arrays.xml
new file mode 100644
index 0000000..95d4f23
--- /dev/null
+++ b/res/values-fi/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ei aihetta"</item>
+ <item msgid="272485471009191934">"ei aihetta"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Kyllä"</item>
+ <item msgid="6049132459802288033">"Ei"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Kiitos"</item>
+ <item msgid="4881335087096496747">"Olen samaa mieltä"</item>
+ <item msgid="2422296858597420738">"Kiva"</item>
+ <item msgid="4805581752819452687">"Matkalla"</item>
+ <item msgid="4746700499431366214">"OK, palaan asiaan"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..47ef00f
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Viestit"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Viestit"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Valitse keskustelu"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Asetukset"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Lähetä viesti"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Lisää liite"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ohje"</string>
+ <string name="welcome" msgid="2857560951820802321">"Tervetuloa"</string>
+ <string name="skip" msgid="7238879696319945853">"Ohita"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Seuraava &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Seuraava"</string>
+ <string name="exit" msgid="1905187380359981199">"Sulje"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Asetukset &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Asetukset"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Viestit tarvitsee käyttöoikeuden tekstiviesteihin, puhelimeen ja yhteystietoihin."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Voit muuttaa käyttöoikeuksia kohdassa Asetukset &gt; Sovellukset &gt; Viestit &gt; Käyttöoikeudet."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Voit muuttaa käyttöoikeuksia kohdassa Asetukset, Sovellukset, Viestit, Käyttöoikeudet."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Usein käytetyt"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Yhteystiedot"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Lähetä numeroon <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Tallenna kuva tai video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Valitse kuvia tästä laitteesta"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Tallenna ääntä"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Valitse valokuva"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Media on valittu."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Median valinta on poistettu."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> valittu"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"kuva <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" msgid="8142175029053370662">"kuva"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Tallenna ääntä"</string>
+ <string name="action_share" msgid="2143483844803153871">"Jaa"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Juuri nyt"</string>
+ <string name="posted_now" msgid="867560789350406701">"Nyt"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> minuutti</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> tuntia</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> tunti</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> päivää</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> päivä</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> viikkoa</item>
+ <item quantity="one">viikko</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> kuukautta</item>
+ <item quantity="one">kuukausi</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> vuotta</item>
+ <item quantity="one">vuosi</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Luokan 0 viesti"</string>
+ <string name="save" msgid="5081141452059463572">"Tallenna"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Laitteen tila on vähissä. Viestit poistaa automaattisesti vanhempia viestejä vapauttaakseen tilaa."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Tallennustila vähissä"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Viestit ei ehkä lähetä tai vastaanota viestejä ennen kuin laitteessa on enemmän vapaata tilaa."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Tekstiviestien tallennustila on vähissä. Harkitse viestien poistamista."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Vahvista puhelinnumerosi"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Tekemällä tämän kertaluontoisen vaiheen varmistat, että Viestit toimittaa ryhmäviestisi oikein."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Puhelinnumero"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Poista kaikki mediaa sisältävät viestit"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Poista viestit, joiden ikä on yli <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Poista automaattisesti viestit, jotka ovat vanhempia kuin <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ohita"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Poistetaanko kaikki mediaa sisältävät viestit?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Poistetaanko viestit, jotka ovat vanhempia kuin <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Poistetaanko viestit, jotka ovat vanhempia kuin <xliff:g id="DURATION">%s</xliff:g>, ja otetaanko automaattinen poisto käyttöön?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> sanoi"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Sinä sanoit"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Viesti käyttäjältä <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Lähetit viestin"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Lähetetään…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ei lähetetty. Yritä uudelleen koskettamalla."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ei lähetetty. Yritetään uudelleen…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Lähetä uudelleen tai poista"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Ota yhteyttä hätäpalveluihin soittamalla äänipuhelu. Tekstiviestiäsi ei voida toimittaa tällä hetkellä."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Epäonnistui"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Uusi multimediaviesti ladattavissa"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Uusi multimediaviesti"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Lataus epäonnistui"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Yritä uudelleen koskettamalla"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Lataa koskettamalla"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Lataa tai poista"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Ladataan…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Viesti on vanhentunut tai ei ole käytettävissä"</string>
+ <string name="mms_info" msgid="3402311750134118165">"koko: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, vanhenee: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Ei voi lähettää. Vastaanottaja ei kelpaa."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Palvelua ei ole aktivoitu verkossa"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Ei voitu lähettää verkkovian takia"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Viesti on vanhentunut tai ei ole käytettävissä"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Ei aihetta)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Tuntematon lähettäjä"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Toimitettu"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Lähettäjän <xliff:g id="FROM">%2$s</xliff:g> viestin <xliff:g id="SUBJECT">%1$s</xliff:g> lataaminen epäonnistui."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Tietokantatoimintoa ei voi suorittaa: muisti on vähissä"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Viestiä ei lähetetty"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Joitakin viestejä ei lähetetty Viesteissä."</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> viestiä <xliff:g id="CONVERSATIONS">%d</xliff:g> keskustelussa</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> viesti yhdessä keskustelussa</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Viestiä ei ladattu."</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Joitakin viestejä ei ladattu Viesteissä."</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> viestiä <xliff:g id="CONVERSATIONS">%d</xliff:g> keskustelussa</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> viesti yhdessä keskustelussa</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Viestiä numeroon <xliff:g id="NUMBER">%1$s</xliff:g> ei lähetetty."</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Ota yhteyttä hätäpalveluihin soittamalla äänipuhelu. Tekstiviestiäsi numeroon <xliff:g id="NUMBER">%1$s</xliff:g> ei voida toimittaa tällä hetkellä."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> uutta viestiä</item>
+ <item quantity="one">Uusi viesti</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Käynnistä"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera ei käytettävissä"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera ei käytettävissä"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Videon kaappaus ei käytettävissä"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Mediaa ei voi tallentaa"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Kuvan ottaminen ei onnistu."</string>
+ <string name="back" msgid="1477626055115561645">"Edellinen"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arkistoitu"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Poista"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arkistoi"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Poista arkistosta"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Poista ilmoitukset käytöstä"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Ota ilmoitukset käyttöön"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Lisää kontakti"</string>
+ <string name="action_download" msgid="7786338136368564146">"Lataa"</string>
+ <string name="action_send" msgid="377635240181672039">"Lähetä"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Poista"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Poistetaanko tämä viesti?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Tätä toimintoa ei voi kumota."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Poista"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Poistetaanko nämä keskustelut?</item>
+ <item quantity="one">Poistetaanko tämä keskustelu?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Poista"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Peruuta"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Vastaanottaja"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Valitse useita kuvia"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Vahvista valinta"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Ääntä ei voi tallentaa. Yritä uudelleen."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Ääntä ei voi toistaa. Yritä uudelleen."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Ääntä ei voitu tallentaa. Yritä uudelleen."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Kosketa pitkään"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Kuva"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Äänileike"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Yhteystietokortti"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Lataa"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Vastaa tekstiviestitse"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Vastaa MMS-viestillä"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Vastaa"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> osallistujaa</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> osallistuja</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Minä"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Yhteystieto estetty ja arkistoitu"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Yhteystiedon esto on poistettu, ja se on poistettu arkistosta."</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> arkistoitua"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> poistettiin arkistosta"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Ilmoitukset on poistettu käytöstä."</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Ilmoitukset on otettu käyttöön."</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Kaikki valmiina. Kosketa Lähetä uudelleen."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Viestit asetettiin oletustekstiviestisovellukseksi."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Hylkää liitteet</item>
+ <item quantity="one">Hylkää liite</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Ääniliite"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Toista ääniliite"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Tauko"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Viesti lähettäjältä <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Epäonnistunut viesti lähettäjältä <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Viesti lähettäjältä <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Lähettämätön viesti vastaanottajalle <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Lähetetään viestiä vastaanottajalle <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Epäonnistunut viesti vastaanottajalle <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Viesti vastaanottajalle <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Epäonnistunut viesti lähettäjältä <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Viesti lähettäjältä <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Lähettämätön viesti vastaanottajalle <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Lähetetään viestiä vastaanottajalle <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Epäonnistunut viesti vastaanottajalle <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Viesti vastaanottajalle <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Aika: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Viesti epäonnistui. Yritä uudelleen koskettamalla."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Keskustelu henkilöiden <xliff:g id="PARTICIPANTS">%s</xliff:g> kanssa"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Poista aihe"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Tallenna video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Ota kuva"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Ota kuva"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Aloita videon tallentaminen"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Siirry koko näytön kameraan"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Vaihda etu- ja takakameran välillä"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Lopeta tallennus ja liitä video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Lopeta videon tallentaminen"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Viestien valokuvat"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> valokuvaa tallennettu albumiin <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> valokuva tallennettu albumiin <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videota tallennettu albumiin <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video tallennettu albumiin <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> liitettä tallennettu albumiin <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> liite tallennettu albumiin <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> liitettä tallennettu Lataukset-kansioon</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> liite tallennettu Lataukset-kansioon</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> liitettä tallennettiin</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> liite tallennettiin.</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> liitteen tallentaminen epäonnistui.</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> liitteen tallentaminen epäonnistui.</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Multimedialiite tallennettiin"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Asetukset"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arkistoitu"</string>
+ <string name="action_close" msgid="1840519376200478419">"Sulje"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"Multimediaviesti"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Lisäasetukset"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Vianetsintä"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Ilmoitukset"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ääni"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Äänetön"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Värinä"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Estetty"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Tekstiviestin lähetystiedot"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Pyydä lähetystiedot kaikista lähettämistäsi tekstiviesteistä"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automaattilataus"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Lataa MMS-viestit automaattisesti"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automaattilataus roaming-tilassa"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Hae MMS-viestit automaattisesti roaming-tilassa."</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Ryhmäviestit"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Jaa yksi multimediaviesti useille vastaanottajille"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Oletusarvoinen tekstiviestisovellus"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Oletusarvoinen tekstiviestisovellus"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Puhelinnumerosi"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Tuntematon"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Lähtevien viestien äänet"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Tekstiviestivedos"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Vedosta vastaanotetut tekstiviestien raakatiedot ulkoiseen tallennustiedostoon"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS-vedos"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Vedosta vastaanotetut MMS-raakatiedot ulkoiseen tallennustiedostoon"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Langattoman verkon ilmoitukset"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Viestiasetukset"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopioi teksti"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Näytä tiedot"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Poista"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Lähetä edelleen"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Viestin tiedot"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tyyppi: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Tekstiviesti"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimediaviesti"</string>
+ <string name="from_label" msgid="1947831848146564875">"Lähettäjä: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Vastaanottaja: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Lähetetty: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Vastaanotettu: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Aihe: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Koko: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Tärkeys: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-kortti: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Korkea"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normaali"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Matala"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM-kortti <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Piilotettu lähettäjän osoite"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Viestiä ei voi lähettää liitetiedostojen latauksen aikana."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Liitettä ei voi ladata. Yritä uudelleen."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Verkko ei ole valmis. Yritä uudelleen."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Poista teksti"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Vaihda tekstin ja numeroiden syötön välillä"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Lisää osallistujia"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Vahvista osallistujat"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Aloita uusi keskustelu"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Valitse tämä"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Katso video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Henkilöt ja asetukset"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Vianetsintä"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Henkilöt ja asetukset"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Yleiset"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Keskustelun osallistujat"</string>
+ <string name="action_call" msgid="6596167921517350362">"Soita puhelu"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Lähetä viesti"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Lähetä viesti&lt;br/&gt;&lt;small&gt;kortilta <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Lähetä kuvia</item>
+ <item quantity="one">Lähetä kuva</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Lähetä äänitiedostoja</item>
+ <item quantity="one">Lähetä äänitiedosto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Lähetä videoita</item>
+ <item quantity="one">Lähetä video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Lähetä yhteystietokortteja</item>
+ <item quantity="one">Lähetä yhteystietokortti</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Lähetä liitteitä</item>
+ <item quantity="one">Lähetä liite</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> liitettä valmiina lähetettäväksi</item>
+ <item quantity="one">Yksi liite valmiina lähetettäväksi</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Lähetä palautetta"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Näytä Google Play Kaupassa"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versiotiedot"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versio %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Avoimen lähdekoodin käyttöluvat"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Ilmoitukset"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Liitetiedostojen enimmäisraja on saavutettu."</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Liitetiedoston lataaminen epäonnistui."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Lisätäänkö yhteystietoihin?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Lisää yhteystieto"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Aihe"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Aihe: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Ladataan yhteystietokorttia"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Yhteystietokorttia ei voitu ladata:"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Näytä yhteystietokortti"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> yhteystietoa</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> yhteystieto</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Yhteystietokortit"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Syntymäpäivä"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Muistiinpanot"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Lähetä viesti edelleen"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Vastaa"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Tekstiviestit on poistettu käytöstä"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Voit lähettää valittuasi Viestit oletustekstiviestisovellukseksi."</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Aseta Viestit oletustekstiviestisovellukseksi."</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Vaihda"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Voit vastaanottaa viestejä valittuasi Viestit oletustekstiviestisovellukseksi."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Ensisijaista SIM-korttia ei ole valittu tekstiviestien lähettämiselle"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Laitteen omistaja ei ole myöntänyt lupaa tälle sovellukselle."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Keskustelussa on liian monta osallistujaa"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Virheelliset yhteystiedot</item>
+ <item quantity="one">Virheellinen yhteystieto</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kamerakuvaa ei voitu ladata"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Sinä: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Luonnos"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Uudet keskustelut on lueteltu tässä"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arkistoidut keskustelut näkyvät tässä"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Ladataan keskusteluja…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Kuva"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Äänileike"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Yhteystietokortti"</string>
+ <string name="mms_text" msgid="1528791558806015806">"Multimediaviesti"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Kumoa"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Yritä uudelleen"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Aloita uusi viesti antamalla yhteystiedon nimi tai puhelinnumero"</string>
+ <string name="action_block" msgid="9032076625645190136">"Estä"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Estä <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Kumoa esto: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Estetäänkö <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Saat edelleen viestejä tästä numerosta, mutta et enää ilmoituksia. Tämä keskustelu arkistoidaan."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Estetyt yhteystiedot"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"KUMOA ESTO"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Estetyt yhteystiedot"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Valitse kuva dokumenttikirjastosta"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Lähetetään viestiä"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Viesti lähetetty"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Matkapuhelindata on kytketty pois. Tarkista asetukset."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Viestejä ei voi lähettää lentokonetilassa"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Viestiä ei voitu lähettää"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Viesti ladattu"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Matkapuhelindata on kytketty pois. Tarkista asetukset."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Viestejä ei voi ladata lentokonetilassa."</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Viestin lataaminen epäonnistui."</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nolla"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Yksi"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Kaksi"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Kolme"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Neljä"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Viisi"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Kuusi"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Seitsemän"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Kahdeksan"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Yhdeksän"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Viestin lähettäminen operaattorin <xliff:g id="CARRIERNAME">%1$s</xliff:g> kautta ei onnistunut. Virhe <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Viestin lähettäminen tuntemattoman operaattorin kautta ei onnistunut. Virhe <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Vl: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Viestiä ei lähetetty: palvelu ei ole käytössä verkossa"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Viestiä ei lähetetty: virheellinen kohdeosoite"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Viestiä ei lähetetty: virheellinen viesti"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Viestiä ei lähetetty: sisältöä ei tueta"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Viestiä ei lähetetty: viestiä ei tueta"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Viestiä ei lähetetty: liian suuri"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Uusi viesti"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Näytä"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Kuva"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Sopivaa sovellusta ei löytynyt"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Poista vastaanottaja"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Uusi viesti"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Peruuta"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Muokkaa tukiasemaa"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ei asetettu"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nimi"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Multimediaviestien välityspalvelin"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Multimediaviestien portti"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-tyyppi"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Poista APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Uusi APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Tallenna"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Hylkää"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Nimi-kenttä ei voi olla tyhjä."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ei voi olla tyhjä."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-kentän arvon oltava kolminumeroinen."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC-kentän arvon on oltava kaksi- tai kolminumeroinen."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Palautetaan APN-oletusasetuksia"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Palauta oletukseksi"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"APN-oletusasetukset on palautettu."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Nimetön"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Tukiasemien nimet"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN:t"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Uusi APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Tämä käyttäjä ei voi käyttää APN-asetuksia"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopioidaanko leikepöydälle?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopioi"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"vastaanottaja: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Yleiset"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Lisäasetukset"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Yleiset asetukset"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Lisäasetukset"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"<xliff:g id="SIM_NAME">%s</xliff:g> SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Lähetä oma tekstiviesti kullekin vastaanottajalle. Vain sinä saat vastaukset."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Lähetä yksi MMS-viesti kaikille vastaanottajille"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Tuntematon numero"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Uusi viesti"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Uusi viesti."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-valitsin"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> valittu, SIM-valitsin"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Muokkaa aihetta"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Valitse SIM tai muokkaa aihetta"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Nauhoita ääntä koskettamalla pitkään"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Aloita uusi keskustelu."</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Viestit"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Viestit-luettelo"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Viestit"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Uusi viesti"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Keskusteluluettelo"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Ladataan keskusteluja."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Ladataan viestejä."</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Näytä lisää keskusteluita."</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Näytä lisää viestejä."</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Keskustelu on poistettu."</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Keskustelu poistettu. Näytä toinen Viestit-keskustelu."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Estetty"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Esto kumottu"</string>
+ <string name="db_full" msgid="8459265782521418031">"Tallennustila on vähissä. Tietoja voi kadota."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Valitse liitteet"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Vahvista valinta"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> valittu"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Poista yksi tai useampi liitetiedosto ja yritä uudelleen."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Voit yrittää lähettää viestin, mutta sitä ei ehkä toimiteta ennen kuin olet poistanut yhden tai useamman liitetiedoston."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Voit lähettää vain yhden videon viestiä kohden. Poista ylimääräiset videot ja yritä uudelleen."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Viestit ei pystynyt lataamaan liitetiedostoa."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Lähetä silti"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Keskustelun aloittaminen epäonnistui"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> valittu"</string>
+</resources>
diff --git a/res/values-fr-rCA/arrays.xml b/res/values-fr-rCA/arrays.xml
new file mode 100644
index 0000000..05d76fb
--- /dev/null
+++ b/res/values-fr-rCA/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sans objet"</item>
+ <item msgid="272485471009191934">"aucun objet"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Oui"</item>
+ <item msgid="6049132459802288033">"Non"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hé hé"</item>
+ <item msgid="2611328818571146775">"Merci"</item>
+ <item msgid="4881335087096496747">"J\'accepte"</item>
+ <item msgid="2422296858597420738">"Bien"</item>
+ <item msgid="4805581752819452687">"En chemin"</item>
+ <item msgid="4746700499431366214">"OK, je te réponds plus tard"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..239f4af
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Messagerie"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Messagerie"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Sélectionner la conversation"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Paramètres"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Envoyer un message"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Ajouter une pièce jointe"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Aide"</string>
+ <string name="welcome" msgid="2857560951820802321">"Bienvenue"</string>
+ <string name="skip" msgid="7238879696319945853">"Ignorer"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Suivant &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Suivant"</string>
+ <string name="exit" msgid="1905187380359981199">"Quitter"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Paramètres &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Paramètres"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Vous devez accorder à l\'application Messagerie des autorisations d\'accès aux messages, au téléphone et aux contacts."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Vous pouvez modifier les autorisations dans Paramètres &gt; Applications &gt; Messagerie &gt; Autorisations."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Vous pouvez modifier les autorisations dans Paramètres &gt; Applications &gt; Messagerie &gt; Autorisations."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Fréquents"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Tous les contacts"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Envoyer à <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Prendre des photos ou filmer une vidéo"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Choisir des images de cet appareil"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Enregistrer des fichiers audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Choisir une photo"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Support sélectionné."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Support désélectionné."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> sélectionné(s)"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"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" msgid="8142175029053370662">"image"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Enregistrer des fichiers audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Partager"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"À l\'instant"</string>
+ <string name="posted_now" msgid="867560789350406701">"Maintenant"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> heure</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> heures</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> jour</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> jours</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> semaine</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semaines</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mois</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mois</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> an</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ans</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Message de catégorie 0"</string>
+ <string name="save" msgid="5081141452059463572">"Enregistrer"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"L\'appareil manque d\'espace. Pour en libérer, les messages de l\'application Messagerie les plus anciens seront automatiquement supprimés."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"L\'espace de stockage est presque plein"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Il est possible que vous ne puissiez pas envoyer ni recevoir de messages dans l\'application Messagerie jusqu\'à ce que votre appareil ait plus d\'espace libre."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Stockage limité pour les textos. Vous devrez peut-être supprimer des messages."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirmez votre numéro de téléphone"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Cette étape unique permet de garantir que vos messages de groupe sont correctement envoyés dans Messagerie."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Numéro de téléphone"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Supprimer tous les messages avec du contenu multimédia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Supprimer les messages datant de plus de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Suppression automatique des messages de plus de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorer"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Supprimer tous les messages avec contenu multimédia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Supprimer les messages datant de plus de <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Supprimer les messages de plus de <xliff:g id="DURATION">%s</xliff:g> et activer la suppression automatique?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> a dit"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Vous avez dit"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Message de <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Vous avez envoyé un message"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Envoi en cours..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Message non envoyé. Touchez ici pour réessayer."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Message non envoyé. Nouvelle tentative en cours..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Renvoyer ou supprimer"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre texto pour l\'instant."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Échec"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nouveau message multimédia à télécharger"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nouveau message multimédia"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Échec du téléchargement"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Touchez ici pour réessayer"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Touchez ici pour lancer le téléchargement."</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Télécharger ou supprimer"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Téléchargement en cours..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Le message a expiré ou n\'est plus disponible."</string>
+ <string name="mms_info" msgid="3402311750134118165">"taille : <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, date d\'expiration : <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Impossible d\'envoyer le message. Destinataire non valide."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Ce service n\'est pas activé sur le réseau"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Impossible d\'envoyer le message en raison d\'un problème de réseau"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Le message est expiration ou n\'est plus disponible."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Aucun objet)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Expéditeur inconnu"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Distribué"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Impossible de télécharger le message « <xliff:g id="SUBJECT">%1$s</xliff:g> » de l\'utilisateur suivant : <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Impossible d\'effectuer cette opération dans la base de données. Mémoire insuffisante."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Le message n\'a pas été envoyé"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Certains messages n\'ont pas été envoyés dans Messagerie"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> message dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Message non téléchargé"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Certains messages n\'ont pas été téléchargés dans Messagerie"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> message dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Échec de l\'envoi du message à <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre texto au numéro <xliff:g id="NUMBER">%1$s</xliff:g> pour l\'instant."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> nouveau message</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nouveaux messages</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Démarrer"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"L\'appareil photo n\'est pas disponible"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"L\'appareil photo n\'est pas disponible"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"L\'enregistrement vidéo n\'est pas disponible"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Impossible d\'enregistrer le média"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Impossible de prendre la photo."</string>
+ <string name="back" msgid="1477626055115561645">"Précédent"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archivée"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Supprimer"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archiver"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Annuler l\'archivage"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Désactiver les notifications"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activer les notifications"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Ajouter un contact"</string>
+ <string name="action_download" msgid="7786338136368564146">"Télécharger"</string>
+ <string name="action_send" msgid="377635240181672039">"Envoyer"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Supprimer"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Supprimer ce message?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Cette action ne peut être annulée."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Supprimer"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Supprimer cette conversation?</item>
+ <item quantity="other">Supprimer ces conversations?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Supprimer"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Annuler"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"À"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Sélectionner plusieurs images"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmer la sélection"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Impossible d\'enregistrer l\'audio. Veuillez réessayer."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Impossible de lire l\'audio. Veuillez réessayer."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Impossible d\'enregistrer l\'audio. Veuillez réessayer."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Maintenez le doigt ici"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Image"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Extrait audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vidéo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Fiche de contact"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Télécharger"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Répondre par texto"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Rép. m. multim."</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Répondre"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> participant</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participants</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Moi"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contact bloqué et archivé"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contact débloqué et archivage annulé"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archivée(s)"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Archivage annulé de <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Les notifications sont désactivées"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Les notifications sont activées"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Tout est prêt. Touchez « Envoyer » de nouveau."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"L\'application Messagerie a été définie comme application de messagerie par défaut."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Supprimer la pièce jointe</item>
+ <item quantity="other">Supprimer les pièces jointes</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Contenu audio joint"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Jouer le contenu audio joint"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Message de <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Échec de la réception du message de <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Message de <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Message non envoyé à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Envoi du message à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Échec de l\'envoi du message à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Message à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Échec de la réception du message de <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Message de <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Message non envoyé à : <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Heure: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Envoi du message à <xliff:g id="GROUP">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Échec de l\'envoi du message à <xliff:g id="GROUP">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Message à <xliff:g id="GROUP">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Échec du message. Touchez pour réessayer."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversation avec <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Supprimer l\'objet"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Filmer une vidéo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturer une image fixe"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Prendre une photo"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Lancer l\'enregistrement vidéo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Activer le mode plein écran de l\'appareil photo"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Basculer entre l\'appareil photo avant et arrière"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Arrêter l\'enregistrement et joindre la vidéo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Arrêter l\'enregistrement vidéo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Photos de Messagerie"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> photo enregistrée dans l\'album « <xliff:g id="ALBUMNAME_3">%s</xliff:g> »</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> photos enregistrées dans l\'album « <xliff:g id="ALBUMNAME_3">%s</xliff:g> »</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> vidéo enregistrée dans l\'album « <xliff:g id="ALBUMNAME_3">%s</xliff:g> »</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vidéos enregistrées dans l\'album « <xliff:g id="ALBUMNAME_3">%s</xliff:g> »</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> pièce jointe enregistrée dans l\'album « <xliff:g id="ALBUMNAME_3">%s</xliff:g> »</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> pièces jointes enregistrées dans l\'album « <xliff:g id="ALBUMNAME_3">%s</xliff:g> »</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> pièce jointe enregistrée dans le dossier « Téléchargements »</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> pièces jointes enregistrées dans le dossier « Téléchargements »</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> pièce jointe enregistrée</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> pièces jointes enregistrées</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Impossible d\'enregistrer <xliff:g id="QUANTITY_1">%d</xliff:g> pièce jointe</item>
+ <item quantity="other">Impossible d\'enregistrer <xliff:g id="QUANTITY_1">%d</xliff:g> pièces jointes</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"La pièce jointe du message multimédia a été enregistrée"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Paramètres"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archivée"</string>
+ <string name="action_close" msgid="1840519376200478419">"Fermer"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"Message multimédia"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avancés"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Déboguer"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifications"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Son"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Mode silencieux"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibreur"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqué"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Accusés de réception des messages texte"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Demander un accusé de réception pour chaque texto envoyé"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Récupération automatique"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Récupérer automatiquement les messages multimédias"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Récupération auto. en itinérance"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Récupérer automatiquement les messages multimédias en itinérance"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Messagerie de groupe"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utiliser la messagerie multimédia pour envoyer un message unique à plusieurs destinataires"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Application de messagerie texte par défaut"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Application de messagerie texte par défaut"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Votre numéro de téléphone"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Inconnu"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sons pour les messages sortants"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Capturer les messages texte"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Capturer les données brutes reçues des messages multimédias dans un fichier de stockage externe"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Capturer les messages multimédias"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Capturer les données brutes reçues des messages multimédias dans un fichier de stockage externe"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertes par réseau sans fil"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Options relatives aux messages"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copier le texte"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Afficher les détails"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Supprimer"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Transférer"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Détails du message"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type : "</string>
+ <string name="text_message" msgid="7415419755252205721">"Message texte"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Message multimédia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De : "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"À : "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Envoyé : "</string>
+ <string name="received_label" msgid="4442494712757995203">"Reçu : "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Objet : "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Taille : "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorité : "</string>
+ <string name="sim_label" msgid="2706003016582772108">"Carte SIM : "</string>
+ <string name="priority_high" msgid="728836357310908368">"Élevée"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normale"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Faible"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"Fente SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Adresse d\'expéditeur masquée"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Impossible d\'envoyer le message pendant le chargement des pièces jointes."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Impossible de charger la pièce jointe. Veuillez réessayer."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Le réseau n\'est pas prêt. Réssayez."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Supprimer le texte"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Alterner entre l\'entrée de texte et de chiffres"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Ajouter des participants"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmer les participants"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Commencer une nouvelle discussion"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Sélectionner cet article"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Lire la vidéo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Participants et options"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Déboguer"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Participants et options"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Général"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Participants à cette conversation"</string>
+ <string name="action_call" msgid="6596167921517350362">"Faire un appel"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Envoyer un message"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Envoyer un message&lt;br/&gt;&lt;small&gt;à partir de <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Envoyer la photo</item>
+ <item quantity="other">Envoyer les photos</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Envoyer le fichier audio</item>
+ <item quantity="other">Envoyer les fichiers audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Envoyer la vidéo</item>
+ <item quantity="other">Envoyer les vidéos</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Envoyer la fiche de contact</item>
+ <item quantity="other">Envoyer les fiches de contact</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Envoyer la pièce jointe</item>
+ <item quantity="other">Envoyer les pièces jointes</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> pièce jointe est prête à être envoyée</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> pièces jointes sont prêtes à être envoyées</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Envoyer des commentaires"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Afficher dans la boutique Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Détails de la version"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licences de logiciels libres"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifications"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Taille maximale des pièces jointes atteinte"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Échec du chargement de la pièce jointe."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Ajouter aux contacts?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Ajouter un contact"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Objet"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Objet : "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g> <xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Chargement de la fiche de contact"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Impossible de charger la fiche de contact"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Afficher la fiche de contact"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> contact</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contacts</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Fiches de contact"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Anniversaire"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Remarques"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Transférer le message"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Répondre"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Messagerie texte désactivée"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Pour envoyer des messages, définissez Messagerie comme application de messagerie par défaut"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Définissez Messagerie comme application de messagerie par défaut"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Modifier"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Pour recevoir des messages, vous devez définir l\'application Messagerie comme application de messagerie par défaut."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Aucune carte SIM préférée n\'a été sélectionnée pour l\'envoi des messages texte"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Cette application n\'est pas autorisée par le propriétaire de l\'appareil."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Trop de participants dans une conversation"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Contact non valide</item>
+ <item quantity="other">Contacts non valides</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Impossible de charger l\'image de l\'appareil photo"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Vous : "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g> : "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Brouillon"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Lorsque vous commencez une conversation, elle s\'affiche ici"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Les conversations archivées s\'affichent ici"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Chargement des conversations en cours..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Image"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Extrait audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vidéo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Fiche de contact"</string>
+ <string name="mms_text" msgid="1528791558806015806">"Message multimédia"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Annuler"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Réessayer"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Entrez le nom d\'un contact ou un numéro de téléphone pour commencer un nouveau message"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquer"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquer <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Débloquer <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Bloquer <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Vous continuerez à recevoir les messages envoyés par ce numéro, mais vous ne recevrez plus de notifications. Cette conversation sera archivée."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contacts bloqués"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DÉBLOQUER"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contacts bloqués"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Choisissez une image dans la bibliothèque de documents"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Envoi du message en cours..."</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Message envoyé"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Les données cellulaires sont désactivées. Vérifiez vos paramètres."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Impossible d\'envoyer des messages en mode Avion"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Le message n\'a pas pu être envoyé"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Message téléchargé"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Les données cellulaires sont désactivées. Vérifiez vos paramètres."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Impossible de télécharger des messages en mode Avion"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Le message n\'a pas pu être téléchargé"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zéro"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Un"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Deux"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Trois"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Quatre"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinq"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Six"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sept"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Huit"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Neuf"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Impossible d\'envoyer le message par l\'intermédiaire de <xliff:g id="CARRIERNAME">%1$s</xliff:g>. Erreur  : <xliff:g id="ERRORCODE">%2$d</xliff:g>."</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Impossible d\'envoyer le message par l\'intermédiaire d\'un fournisseur de services inconnu. Erreur : <xliff:g id="ERRORCODE">%1$d</xliff:g>."</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Tr : <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Le message n\'a pas été envoyé, car le service n\'est pas activé sur le réseau"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Le message n\'a pas été envoyé, car l\'adresse de destination est incorrecte"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Le message n\'a pas été envoyé, car il n\'est pas valide"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Le message n\'a pas été envoyé, car le contenu est incompatible"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Le message n\'a pas été envoyé, car il n\'est pas pris en charge"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Le message n\'a pas été envoyé, car il est trop gros"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nouveau message"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Afficher"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Image"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Impossible de trouver une application appropriée."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Supprimer le destinataire"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nouveau message"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Annuler"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Modifier le point d\'accès"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Non défini"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nom"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Nom du point d\'accès"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Serveur mandataire pour la messagerie multimédia"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port pour la messagerie multimédia"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Type de point d\'accès"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Supprimer le point d\'accès"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nouveau point d\'accès"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Enregistrer"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Supprimer"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Le champ « Nom » est obligatoire."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Le champ « Point d\'accès » est obligatoire."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Le champ MCC doit contenir 3 chiffres."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Le champ MNC doit contenir 2 ou 3 chiffres."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restauration des paramètres de points d\'accès par défaut en cours..."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Rétablir les valeurs par défaut"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"La réinitialisation des paramètres de points d\'accès par défaut est terminée."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sans titre"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Noms des points d\'accès"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Noms des points d\'accès"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nouveau point d\'accès"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Les paramètres de point d\'accès ne sont pas disponibles pour cet utilisateur"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copier dans le presse-papiers?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copier"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"à <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Général"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avancés"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Paramètres généraux"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Paramètres avancés"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Carte SIM « <xliff:g id="SIM_NAME">%s</xliff:g> »"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Envoyer des textos individuels à tous les destinataires. Vous seul recevrez les réponses."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Envoyer des messages multimédias individuels à tous les destinataires."</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Numéro inconnu"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nouveau message"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nouveau message."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Sélecteur de carte SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> sélectionnée, sélecteur de carte SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Modifier l\'objet"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Sélectionner une carte SIM ou modifier l\'objet"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Maintenez le doigt sur le bouton pour enregistrer des contenus audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Commencer une nouvelle discussion"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messagerie"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Liste des conversations"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messagerie"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nouveau message"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Liste des conversations"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Chargement des conversations en cours..."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Messages en cours de chargement…"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Afficher plus de conversations"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Voir plus de messages"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversation supprimée"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"La conversation a été supprimée. Touchez pour afficher une autre conversation dans Messagerie."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloqué"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Débloqué"</string>
+ <string name="db_full" msgid="8459265782521418031">"L\'espace de stockage est faible. Vous risquez de perdre certaines données."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Sélectionner les pièces jointes"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmer la sélection"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> sélectionné(s)"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Veuillez supprimer une ou plusieurs pièces jointes, puis réessayer."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Vous pouvez essayer d\'envoyer votre message, mais il risque de ne pas être remis sauf si vous supprimez une ou plusieurs pièces jointes."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Vous ne pouvez envoyer qu\'une vidéo par message. Veuillez supprimer les vidéos supplémentaires, puis réessayer."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Échec du chargement de la pièce jointe."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Envoyer quand même"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Impossible de démarrer la conversation"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> sélectionnée"</string>
+</resources>
diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml
new file mode 100644
index 0000000..6bd25ad
--- /dev/null
+++ b/res/values-fr/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"aucun objet"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Oui"</item>
+ <item msgid="6049132459802288033">"Non"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Haha"</item>
+ <item msgid="2611328818571146775">"Merci"</item>
+ <item msgid="4881335087096496747">"J\'accepte."</item>
+ <item msgid="2422296858597420738">"Bien"</item>
+ <item msgid="4805581752819452687">"J\'arrive."</item>
+ <item msgid="4746700499431366214">"OK, je te réponds plus tard."</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..688c1d5
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"SMS/MMS"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"SMS/MMS"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Sélectionner la conversation"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Paramètres"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Envoyer le message"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Ajouter une pièce jointe"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Aide"</string>
+ <string name="welcome" msgid="2857560951820802321">"Bienvenue"</string>
+ <string name="skip" msgid="7238879696319945853">"Ignorer"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Suivant &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Suivant"</string>
+ <string name="exit" msgid="1905187380359981199">"Quitter"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Paramètres &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Paramètres"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Vous devez accorder à l\'application de SMS/MMS des autorisations d\'accès aux SMS, au téléphone et aux contacts."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Vous pouvez modifier les autorisations dans Paramètres &gt; Applications &gt; SMS/MMS &gt; Autorisations."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Vous pouvez modifier les autorisations dans Paramètres &gt; Applications &gt; SMS/MMS &gt; Autorisations."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Fréquents"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Tous les contacts"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Envoyer au <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Prendre des photos ou filmer une vidéo"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Sélectionner des images sur cet appareil"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Enregistrer un fichier audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Sélectionner une photo"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Le support est sélectionné."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Le support est désélectionné."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> élément(s) sélectionné(s)"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"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" msgid="8142175029053370662">"image"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Enregistrer un fichier audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Partager"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"À l\'instant"</string>
+ <string name="posted_now" msgid="867560789350406701">"À l\'instant"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> heure</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> heures</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> jour</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> jours</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> semaine</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semaines</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mois</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mois</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> an</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ans</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Message de catégorie 0"</string>
+ <string name="save" msgid="5081141452059463572">"Enregistrer"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"L\'appareil manque d\'espace. Pour en libérer, les messages de l\'application SMS/MMS les plus anciens seront supprimés automatiquement."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Espace de stockage bientôt saturé"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Il est possible que vous ne puissiez pas envoyer ni recevoir de messages via l\'application de SMS/MMS jusqu\'à ce que votre appareil dispose de plus d\'espace."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Espace de stockage limité. Il est possible que vous deviez supprimer des SMS."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirmez votre numéro de téléphone"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Cette étape unique permet de garantir que vos messages de groupe sont correctement envoyés via les SMS/MMS."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Numéro de téléphone"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Supprimer tous les messages comportant du contenu multimédia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Supprimer les messages datant de plus de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Supprimer automatiquement les messages datant de plus de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorer"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Supprimer tous les messages comportant du contenu multimédia ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Supprimer les messages datant de plus de <xliff:g id="DURATION">%s</xliff:g> ?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Supprimer les messages datant de plus de <xliff:g id="DURATION">%s</xliff:g> et activer la suppression automatique ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> a dit"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Vous avez dit"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Expéditeur du message : <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Vous avez envoyé un message."</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Envoi en cours…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Message non envoyé. Veuillez appuyer ici pour réessayer."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Message non envoyé. Nouvelle tentative en cours…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Renvoyer ou supprimer"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre SMS pour l\'instant."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Échec."</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nouveau MMS à télécharger"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nouveau message MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Téléchargement impossible."</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Appuyer ici pour réessayer"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Appuyer ici pour lancer le téléchargement"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Télécharger ou supprimer"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Téléchargement en cours…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Le message est arrivé à expiration ou n\'est plus disponible."</string>
+ <string name="mms_info" msgid="3402311750134118165">"taille : <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration : <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Impossible d\'envoyer le message. Destinataire non valide."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Service non activé sur le réseau."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Impossible d\'envoyer le message en raison de problèmes sur le réseau."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Le message est arrivé à expiration ou n\'est plus disponible."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Aucun objet)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Expéditeur inconnu"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Distribué"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Impossible de télécharger le message \"<xliff:g id="SUBJECT">%1$s</xliff:g>\" de l\'utilisateur suivant : <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Impossible d\'effectuer cette opération dans la base de données. Mémoire insuffisante."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Le message n\'a pas été envoyé."</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Certains messages n\'ont pas été envoyés via les SMS/MMS."</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> messages dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversation</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Message non téléchargé."</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Certains messages n\'ont pas été téléchargés via les SMS/MMS."</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> messages dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversation</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messages dans <xliff:g id="CONVERSATIONS">%d</xliff:g> conversations</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Échec de l\'envoi du message à <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Veuillez appeler les services d\'urgence. Impossible d\'envoyer votre SMS au numéro <xliff:g id="NUMBER">%1$s</xliff:g> pour l\'instant."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> nouveau message</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nouveaux messages</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Démarrer"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Caméra indisponible."</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Caméra indisponible."</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Enregistrement vidéo indisponible."</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Impossible d\'enregistrer le fichier multimédia."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Impossible de prendre la photo."</string>
+ <string name="back" msgid="1477626055115561645">"Retour"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archivées"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Supprimer"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archiver"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Annuler l\'archivage"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Désactiver les notifications"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activer les notifications"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Ajouter un contact"</string>
+ <string name="action_download" msgid="7786338136368564146">"Télécharger"</string>
+ <string name="action_send" msgid="377635240181672039">"Envoyer"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Supprimer"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Supprimer ce message ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Cette action est irréversible."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Supprimer"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Supprimer cette conversation ?</item>
+ <item quantity="other">Supprimer ces conversations ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Supprimer"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Annuler"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"À"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Sélectionner plusieurs images"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmer la sélection"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Impossible d\'enregistrer l\'audio. Veuillez réessayer."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Impossible de lire l\'audio. Veuillez réessayer."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Impossible d\'enregistrer l\'audio. Veuillez réessayer."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Appuyez ici de manière prolongée"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">" : "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Image"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Extrait audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vidéo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Fiche de contact"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Télécharger"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Répondre par SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Répondre par MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Répondre"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> participant</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participants</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Moi"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contact bloqué et archivé"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contact débloqué et archivage annulé."</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archivée(s)"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Archivage annulé de <xliff:g id="COUNT">%d</xliff:g> conversation(s)."</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notifications désactivées."</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notifications activées."</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Configuration réussie. Appuyez de nouveau sur \"Envoyer\"."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"L\'application de SMS/MMS a bien été définie comme application de SMS par défaut."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Supprimer la pièce jointe</item>
+ <item quantity="other">Supprimer les pièces jointes</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Pièce jointe audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Jouer le contenu audio joint"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Message de <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Échec de la réception du message envoyé par <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Message envoyé par <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Message non envoyé à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Envoi du message à <xliff:g id="CONTACT">%s</xliff:g> en cours : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Échec de l\'envoi du message à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Message à <xliff:g id="CONTACT">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Échec de la réception du message envoyé par <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Message envoyé par <xliff:g id="SENDER">%s</xliff:g> : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Message non envoyé au groupe \"<xliff:g id="GROUP">%s</xliff:g>\" : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Envoi du message au groupe \"<xliff:g id="GROUP">%s</xliff:g>\" en cours : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Échec de l\'envoi du message au groupe \"<xliff:g id="GROUP">%s</xliff:g>\" : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Message au groupe \"<xliff:g id="GROUP">%s</xliff:g>\" : <xliff:g id="MESSAGE">%s</xliff:g>. Heure : <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Échec du message. Appuyez pour réessayer."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversation avec <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Supprimer l\'objet"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Enregistrer une vidéo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturer une image fixe"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Prendre une photo"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Lancer l\'enregistrement vidéo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Passer en caméra plein écran"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Passer de la caméra frontale à la caméra arrière, et inversement"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Arrêter l\'enregistrement et joindre la vidéo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Arrêter l\'enregistrement vidéo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Photos envoyées ou reçues par SMS/MMS"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> photo a été enregistrée dans l\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> photos ont été enregistrées dans l\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\".</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> vidéo a été enregistrée dans l\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\".</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vidéos ont été enregistrées dans l\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\".</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> pièce jointe a été enregistrée dans l\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\".</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> pièces jointes ont été enregistrées dans l\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\".</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> pièce jointe a été enregistrée dans le dossier \"Téléchargements\".</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> pièces jointes ont été enregistrées dans le dossier \"Téléchargements\".</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> pièce jointe a été enregistrée.</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> pièces jointes ont été enregistrées.</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Impossible d\'enregistrer <xliff:g id="QUANTITY_1">%d</xliff:g> pièce jointe.</item>
+ <item quantity="other">Impossible d\'enregistrer <xliff:g id="QUANTITY_1">%d</xliff:g> pièces jointes.</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Pièce jointe du MMS enregistrée"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Paramètres"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archivées"</string>
+ <string name="action_close" msgid="1840519376200478419">"Fermer"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Paramètres avancés"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Déboguer"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifications"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Son"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Mode silencieux"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibreur"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqué"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Accusés de réception de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Demander un accusé de réception pour chaque SMS envoyé"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Récupération automatique"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Récupérer automatiquement les MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Récupération auto. en itinérance"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Récupérer automatiquement les MMS en itinérance"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Messagerie de groupe"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utiliser un MMS pour envoyer un message unique à plusieurs destinataires"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Application SMS par défaut"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Application SMS par défaut"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Votre numéro de téléphone"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Inconnu"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sons pour les messages sortants"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Créer une copie des SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Créer une copie des données SMS brutes reçues dans un fichier de stockage externe"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Créer une copie des MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Créer une copie des données MMS brutes reçues dans un fichier de stockage externe"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertes via le réseau sans fil"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Options relatives au message"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copier le texte"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Afficher les détails"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Supprimer"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Transférer"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Détails du message"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type : "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"MMS"</string>
+ <string name="from_label" msgid="1947831848146564875">"Expéditeur : "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Destinataire : "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Envoyé le : "</string>
+ <string name="received_label" msgid="4442494712757995203">"Reçu le : "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Objet : "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Taille : "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorité : "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM : "</string>
+ <string name="priority_high" msgid="728836357310908368">"Élevée"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normale"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Basse"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Adresse de l\'expéditeur masquée"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Impossible d\'envoyer le message pendant le chargement des pièces jointes."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Impossible de charger la pièce jointe. Veuillez réessayer."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Le réseau n\'est pas prêt. Veuillez réessayer."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Supprimer le texte"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Alterner entre la saisie de texte et de chiffres"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Ajouter d\'autres participants"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmer les participants"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Démarrer une nouvelle conversation"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Sélectionner cet article"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Lire la vidéo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Participants et options"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Déboguer"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Participants et options"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Général"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Participants à cette conversation"</string>
+ <string name="action_call" msgid="6596167921517350362">"Passer un appel"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Envoyer un message"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Envoyer un message&lt;br/&gt;&lt;small&gt;avec la carte SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\"&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Envoyer la photo</item>
+ <item quantity="other">Envoyer les photos</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Envoyer le fichier audio</item>
+ <item quantity="other">Envoyer les fichiers audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Envoyer la vidéo</item>
+ <item quantity="other">Envoyer les vidéos</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Envoyer la carte de visite</item>
+ <item quantity="other">Envoyer les cartes de visite</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Envoyer la pièce jointe</item>
+ <item quantity="other">Envoyer les pièces jointes</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> pièce jointe est prête à être envoyée.</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> pièces jointes sont prêtes à être envoyées.</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Envoyer des commentaires"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Afficher sur le Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informations sur la version"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licences Open Source"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifications"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Taille maximale des pièces jointes atteinte"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Échec du chargement de la pièce jointe."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Ajouter aux contacts ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Ajouter un contact"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Objet"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Objet : "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Chargement de la fiche de contact en cours…"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Impossible de charger la fiche de contact."</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Afficher la fiche de contact"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> contact</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contacts</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Fiches de contacts"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Anniversaire"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Remarques"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Transférer le message"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Répondre"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+ 1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS désactivés"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Pour envoyer des messages, vous devez définir l\'application de SMS/MMS comme application de SMS par défaut."</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Définissez l\'application de SMS/MMS comme application de SMS par défaut."</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Modifier"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Pour recevoir des messages, vous devez définir l\'application de SMS/MMS comme application de SMS par défaut."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Aucune carte SIM préférée sélectionnée pour l\'envoi de SMS."</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Cette application n\'est pas autorisée par le propriétaire de l\'appareil."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Trop de participants dans une conversation."</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Contact non valide.</item>
+ <item quantity="other">Contacts non valides.</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Impossible de charger l\'image de la caméra."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Vous : "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g> : "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Brouillon"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Lorsque vous commencez une conversation, elle s\'affiche ici."</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Les conversations archivées s\'affichent ici."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Chargement des conversations en cours…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Image"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Extrait audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vidéo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Fiche de contact"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Annuler"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Réessayer"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Saisissez le nom d\'un contact ou un numéro de téléphone pour commencer un nouveau message"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquer"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquer \"<xliff:g id="DESTINATION">%s</xliff:g>\""</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Débloquer \"<xliff:g id="DESTINATION">%s</xliff:g>\""</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Bloquer <xliff:g id="DESTINATION">%s</xliff:g> ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Vous continuerez à recevoir les messages envoyés par ce numéro, mais vous ne recevrez plus de notifications. Cette conversation sera archivée."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contacts bloqués"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DÉBLOQUER"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contacts bloqués"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Sélectionner une image dans la bibliothèque de documents"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Envoi du message en cours..."</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Message envoyé."</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Les données cellulaires sont désactivées. Veuillez vérifier vos paramètres."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Impossible d\'envoyer des messages en mode Avion."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Envoi du SMS impossible."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Message téléchargé."</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Les données cellulaires sont désactivées. Veuillez vérifier vos paramètres."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Impossible de télécharger les messages en mode Avion."</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Impossible de télécharger le message."</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zéro"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Un"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Deux"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Trois"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Quatre"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinq"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Six"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sept"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Huit"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Neuf"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Impossible d\'envoyer le message via <xliff:g id="CARRIERNAME">%1$s</xliff:g>. Erreur : <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Impossible d\'envoyer le message via un opérateur inconnu. Erreur : <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"TR : <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Le message n\'a pas été envoyé, car le service n\'est pas activé sur le réseau."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Le message n\'a pas été envoyé, car l\'adresse de destination est incorrecte."</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Le message n\'a pas été envoyé, car il n\'est pas valide."</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Le message n\'a pas été envoyé, car le contenu est incompatible."</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Le message n\'a pas été envoyé, car il est incompatible."</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Le message n\'a pas été envoyé, car il est trop volumineux."</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nouveau message"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Afficher"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Image"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Impossible de trouver une application appropriée."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Supprimer le destinataire"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nouveau message"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Annuler"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Modifier le point d\'accès"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Non défini"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nom"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy pour les MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port pour les MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Type d\'APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Supprimer l\'APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nouvel APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Enregistrer"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Supprimer"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Champ \"Nom\" obligatoire."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN obligatoire."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Le champ \"MCC\" doit contenir trois chiffres."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Le champ \"MNC\" doit contenir deux ou trois chiffres."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restauration des paramètres APN par défaut en cours…"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Rétablir les paramètres par défaut"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Les paramètres APN par défaut ont bien été réinitialisés."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sans titre"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Points d\'accès"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nouvel APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Les paramètres de point d\'accès ne sont pas disponibles pour cet utilisateur."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copier dans le Presse-papiers ?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copier"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"destinataire : \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="general_settings" msgid="5409336577057897710">"Général"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Paramètres avancés"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Paramètres généraux"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Paramètres avancés"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Carte SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Envoyer des SMS individuels à tous les destinataires. Vous seul recevrez les réponses."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Envoyer un MMS unique à tous les destinataires"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Depuis un numéro inconnu"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nouveau message"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nouveau message."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Sélecteur de carte SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"\"<xliff:g id="SIM_0">%1$s</xliff:g>\" sélectionnée – Sélecteur de carte SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Modifier l\'objet"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Sélectionner une carte SIM ou modifier l\'objet"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Appuyer de manière prolongée pour enregistrer des contenus audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Lancer une conversation"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS/MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Liste des SMS/MMS"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS/MMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nouveau message"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Liste des conversations"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Chargement des conversations en cours…"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Chargement des messages en cours…"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Afficher plus de conversations"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Afficher d\'autres messages"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversation supprimée."</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"La conversation a bien été supprimée. Appuyez pour afficher une autre conversation de SMS/MMS."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloqué"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Débloqué"</string>
+ <string name="db_full" msgid="8459265782521418031">"L\'espace de stockage est faible. Vous risquez de perdre certaines données."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Sélectionner les pièces jointes"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmer la sélection"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> élément(s) sélectionné(s)"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Veuillez supprimer une ou plusieurs pièces jointes, puis réessayer."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Vous pouvez essayer d\'envoyer votre message, mais il risque de ne pas être remis sauf si vous supprimez une ou plusieurs pièces jointes."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Vous ne pouvez envoyer qu\'une vidéo par message. Veuillez supprimer les vidéos supplémentaires, puis réessayer."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Échec du chargement de la pièce jointe via les SMS/MMS."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Envoyer quand même"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Impossible de lancer la conversation."</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> sélectionnée"</string>
+</resources>
diff --git a/res/values-gl-rES/arrays.xml b/res/values-gl-rES/arrays.xml
new file mode 100644
index 0000000..1e43974
--- /dev/null
+++ b/res/values-gl-rES/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sen asunto"</item>
+ <item msgid="272485471009191934">"senasunto"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Si"</item>
+ <item msgid="6049132459802288033">"Non"</item>
+ <item msgid="3084376867445867895">"Aceptar"</item>
+ <item msgid="3155097332660174689">"He he"</item>
+ <item msgid="2611328818571146775">"Grazas"</item>
+ <item msgid="4881335087096496747">"Acepto"</item>
+ <item msgid="2422296858597420738">"Excelente"</item>
+ <item msgid="4805581752819452687">"Á miña maneira"</item>
+ <item msgid="4746700499431366214">"De acordo, póñome en contacto contigo máis tarde"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
new file mode 100644
index 0000000..91a6311
--- /dev/null
+++ b/res/values-gl-rES/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mensaxería"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mensaxería"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Seleccionar conversa"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Configuración"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Enviar mensaxe"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Engadir un anexo"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Axuda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Dámosche a benvida"</string>
+ <string name="skip" msgid="7238879696319945853">"Omitir"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Seguinte &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Seguinte"</string>
+ <string name="exit" msgid="1905187380359981199">"Saír"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Configuración &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Configuración"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mensaxería necesita permiso para acceder ás SMS, ao teléfono e aos contactos."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Podes cambiar os permisos en Configuración &gt; Aplicacións &gt; Mensaxería &gt; Permisos."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Podes cambiar os permisos en Configuración, Aplicacións, Mensaxería, Permisos."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frecuentes"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Todos os contactos"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Enviar ao <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capturar fotos ou vídeo"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Escoller imaxes deste dispositivo"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Gravar audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Escoller foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"O ficheiro multimedia está seleccionado."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"O ficheiro multimedia non está seleccionado."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> seleccionado"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imaxe do <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" msgid="8142175029053370662">"imaxe"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Gravar audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Compartir"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Agora mesmo"</string>
+ <string name="posted_now" msgid="867560789350406701">"Agora"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> minutos</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> minuto</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> días</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> día</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semanas</item>
+ <item quantity="one">unha semana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> meses</item>
+ <item quantity="one">un mes</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> anos</item>
+ <item quantity="one">un ano</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mensaxe de clase 0"</string>
+ <string name="save" msgid="5081141452059463572">"Gardar"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Este dispositivo ten pouco espazo. Mensaxería eliminará automaticamente as mensaxes antigas para liberar espazo."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Estase esgotando o espazo de almacenamento"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"É posible que Mensaxería non envíe nin reciba mensaxes ata que haxa máis espazo dispoñible no teu dispositivo."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Queda pouco almacenamento de SMS restante. É posible que precises eliminar mensaxes."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirma o teu número de teléfono"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Este paso único garantirá que Mensaxería entregue as túas mensaxes de grupo de forma adecuada."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Número de teléfono"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Eliminar todas as mensaxes con ficheiros multimedia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Eliminar mensaxes anteriores a <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Eliminar automaticamente mensaxes anteriores a <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorar"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Queres eliminar todas as mensaxes con ficheiros multimedia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Queres eliminar as mensaxes anteriores a <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Queres eliminar mensaxes anteriores a <xliff:g id="DURATION">%s</xliff:g> e activar a eliminación automática?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> dixo"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Dixeches"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mensaxe de <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Enviaches unha mensaxe"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Enviando…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Non se enviou. Toca para tentalo de novo."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Non se enviou. Tentando de novo…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Reenviar ou eliminar"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Realiza unha chamada de voz aos servizos de emerxencia. Non se puido entregar a túa mensaxe de texto neste momento."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Erro"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nova mensaxe MMS para descargar"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nova mensaxe MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Non se puido descargar"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tocar para tentalo de novo"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Tocar para descargar"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Descargar ou eliminar"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Descargando..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"A mensaxe caducou ou non está dispoñible"</string>
+ <string name="mms_info" msgid="3402311750134118165">"tamaño: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, caducidade: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Non se pode enviar. O destinatario non é válido."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Servizo non activado na rede"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Non se puido enviar debido a un problema de rede"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"A mensaxe caducou ou non está dispoñible"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Sen asunto)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Remitente descoñecido"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Entregado"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Non se puido descargar a mensaxe <xliff:g id="SUBJECT">%1$s</xliff:g> de <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Non se puido completar a operación da base de datos debido a que queda pouca memoria"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mensaxe non enviada"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Hai mensaxes non enviadas en Mensaxería"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensaxes en <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensaxes nunha conversa</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Non se descargou a mensaxe"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Algunhas mensaxes non se descargaron en Mensaxería"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensaxes en <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensaxes nunha conversa</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Non se enviou a mensaxe ao <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Realiza unha chamada de voz aos servizos de emerxencia. Non se puido entregar a túa mensaxe de texto ao <xliff:g id="NUMBER">%1$s</xliff:g> neste momento."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> mensaxes novas</item>
+ <item quantity="one">Mensaxe nova</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Iniciar"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"A cámara non está dispoñible"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"A cámara non está dispoñible"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"A captura de vídeo non está dispoñible"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Non se pode gardar o ficheiro multimedia"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Non se pode sacar a foto"</string>
+ <string name="back" msgid="1477626055115561645">"Atrás"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arquivadas"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Eliminar"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arquivar"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Cancelar o arquivado"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desactivar notificacións"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activar notificacións"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Engadir contacto"</string>
+ <string name="action_download" msgid="7786338136368564146">"Descargar"</string>
+ <string name="action_send" msgid="377635240181672039">"Enviar"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Eliminar"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Queres eliminar esta mensaxe?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Esta acción non se pode desfacer."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Eliminar"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Queres eliminar estas conversas?</item>
+ <item quantity="one">Queres eliminar esta conversa?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Eliminar"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancelar"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Para"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Seleccionar varias imaxes"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmar selección"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Non se pode gravar o audio. Téntao de novo."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Non se pode reproducir o audio. Téntao de novo."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Non se puido gardar o audio. Téntao de novo."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Mantén premido"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imaxe"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clip de audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vídeo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Tarxeta de contacto"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Descargar"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Responder mediante SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Resposta MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Responde"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participantes</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participante</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Eu"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contacto bloqueado e arquivado"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contacto desbloqueado e sen arquivar"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> arquivada/s"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> non arquivada/s"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificacións desactivadas"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificacións activadas"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Todo listo. Toca Enviar de novo."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Mensaxería estableceuse correctamente como a aplicación de SMS predeterminada."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Descartar anexos</item>
+ <item quantity="one">Descartar anexo</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Anexo de audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reproducir anexo de audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausa"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mensaxe de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Erro ao recibir a mensaxe de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mensaxe de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Non se enviou a mensaxe a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Enviando mensaxe a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>E. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Erro ao enviar a mensaxe a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mensaxe para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Erro ao recibir a mensaxe de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mensaxe de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Non se enviou a mensaxe a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Enviando mensaxe a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>E. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Erro ao enviar a mensaxe a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mensaxe para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Erro na mensaxe. Toca para tentalo de novo."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversa con <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Borrar asunto"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capturar vídeo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturar unha imaxe estática"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Sacar foto"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Comezar a gravar vídeo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Cambiar á cámara de pantalla completa"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Cambiar entre a cámara dianteira e traseira"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Deter a gravación e anexar vídeo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Deter a gravación de vídeo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos de Mensaxería"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">Gardáronse <xliff:g id="QUANTITY_2">%d</xliff:g> fotos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Gardouse <xliff:g id="QUANTITY_0">%d</xliff:g> foto no álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">Gardáronse <xliff:g id="QUANTITY_2">%d</xliff:g> vídeos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Gardouse <xliff:g id="QUANTITY_0">%d</xliff:g> vídeo no álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">Gardáronse <xliff:g id="QUANTITY_2">%d</xliff:g> anexos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Gardouse <xliff:g id="QUANTITY_0">%d</xliff:g> anexo no álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">Gardáronse <xliff:g id="QUANTITY_1">%d</xliff:g> anexos en \"Descargas\"</item>
+ <item quantity="one">Gardouse <xliff:g id="QUANTITY_0">%d</xliff:g> anexo en \"Descargas\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">Gardáronse <xliff:g id="QUANTITY_1">%d</xliff:g> anexos</item>
+ <item quantity="one">Gardouse <xliff:g id="QUANTITY_0">%d</xliff:g> anexo</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Non se puideron gardar <xliff:g id="QUANTITY_1">%d</xliff:g> anexos</item>
+ <item quantity="one">Non se puido gardar <xliff:g id="QUANTITY_0">%d</xliff:g> anexo</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Anexo de MMS gardado"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Configuración"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arquivadas"</string>
+ <string name="action_close" msgid="1840519376200478419">"Pechar"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avanzado"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Depuración"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificacións"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Son"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silencio"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqueado"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Informes de entrega de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Solicita un informe de entrega cando envías un SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recuperación automática"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recupera mensaxes MMS automaticamente"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Recuperación automática en itinerancia"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Recuperación automática de MMS en itinerancia"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mensaxería en grupo"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utiliza MMS para enviar unha soa mensaxe cando hai varios destinatarios"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplicación de SMS predeterminada"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplicación de SMS predeterminada"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"O teu número de teléfono"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Descoñecido"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sons de mensaxes saíntes"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Pasar SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Pasa os datos recibidos de SMS sen procesar a un ficheiro de almacenamento externo"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Pasar MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Pasa os datos recibidos de MMS sen procesar a un ficheiro de almacenamento externo"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertas sen fíos"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opcións de mensaxes"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copiar texto"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ver detalles"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Eliminar"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Reenviar"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalles da mensaxe"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mensaxe de texto"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mensaxe multimedia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Para: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Enviada: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Recibida: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Asunto: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Tamaño: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioridade: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Baixa"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Enderezo do remitente oculto"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Non é posible enviar a mensaxe ao cargar anexos."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Non se pode cargar o anexo. Téntao de novo."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"A rede non está lista. Téntao de novo."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Eliminar texto"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Cambiar entre introducir texto e números"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Engadir máis participantes"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmar participantes"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Iniciar nova conversa"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Selecciona este elemento"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reproducir vídeo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Persoas e opcións"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Depurar"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Persoas e opcións"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Xeral"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Persoas que participan nesta conversa"</string>
+ <string name="action_call" msgid="6596167921517350362">"Facer unha chamada"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Enviar mensaxe"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Envía a mensaxe&lt;br/&gt;&lt;small&gt;desde <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Enviar fotos</item>
+ <item quantity="one">Enviar foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Enviar gravacións de audio</item>
+ <item quantity="one">Enviar gravación de audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Enviar vídeos</item>
+ <item quantity="one">Enviar vídeo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Enviar tarxetas de contacto</item>
+ <item quantity="one">Enviar tarxeta de contacto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Enviar anexos</item>
+ <item quantity="one">Enviar anexo</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> anexos listos para enviar</item>
+ <item quantity="one">Un anexo listo para enviar</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Dános a túa opinión"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ver en Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Información da versión"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versión %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licenzas de código aberto"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificacións"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Acadouse o límite para anexos"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Non se puido cargar o anexo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Queres engadir este número aos teus contactos?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Engadir contacto"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Asunto"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Asunto: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Cargando tarxeta de contacto"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Non se puido cargar a tarxeta de contacto"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ver tarxeta de contacto"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contactos</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contacto</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Tarxetas de contacto"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Aniversario"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notas"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Reenviar mensaxe"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Responder"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS desactivadas"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Establece Mensaxería como a aplicación de SMS predeterminada para enviar mensaxes"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Establece Mensaxería como a aplicación de SMS predeterminada"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Cambiar"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Establece Mensaxería como a aplicación de SMS predeterminada para recibir mensaxes"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Non se seleccionou unha SIM preferida para o envío de mensaxes SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"O propietario do dispositivo non permite esta aplicación."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Aceptar"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Hai demasiados participantes nunha conversa"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Contactos non válidos</item>
+ <item quantity="one">Contacto non válido</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Non se puido cargar a imaxe da cámara"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ti: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Borrador"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Cando inicies unha nova conversa, aparecerá aquí"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"As conversas arquivadas aparecen aquí"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Cargando conversas..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imaxe"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clip de audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vídeo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Tarxeta de contacto"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Desfacer"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Tentar de novo"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Introduce o nome dun contacto ou o número de teléfono para iniciar unha mensaxe nova"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquear"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquear <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desbloquear <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Queres bloquear <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Seguirás recibindo mensaxes deste número, pero xa non se notificarán. Arquivarase esta conversa."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contactos bloqueados"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOQUEAR"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contactos bloqueados"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Escolle unha imaxe da biblioteca de documentos"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Enviando a mensaxe"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mensaxe enviada"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Os datos móbiles están desactivados. Comproba a túa configuración."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Non se poden enviar mensaxes en modo avión"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Erro ao enviar a mensaxe"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mensaxe descargada"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Os datos móbiles están desactivados. Comproba a túa configuración."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Non se poden descargar mensaxes no modo avión"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Non se puido descargar a mensaxe"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Cero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Un"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dous"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tres"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Catro"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinco"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seis"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sete"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Oito"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nove"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Non se pode enviar a mensaxe con <xliff:g id="CARRIERNAME">%1$s</xliff:g>. Erro: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Non se pode enviar a mensaxe cun operador descoñecido. Erro: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Rv: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mensaxe non enviada: servizo non activado na rede"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mensaxe non enviada: enderezo de destino non válido"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mensaxe non enviada: mensaxe non válida"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mensaxe non enviada: contido non compatible"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mensaxe non enviada: mensaxe non compatible"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mensaxe non enviada: demasiado longa"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mensaxe nova"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ver"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imaxe"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Non se puido encontrar unha aplicación adecuada"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Elimina o destinatario"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mensaxe nova"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancelar"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Editar punto de acceso"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Sen configurar"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nome"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Porto de MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipo de APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Eliminar APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Novo APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Gardar"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Descartar"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"O nome do campo non pode estar baleiro."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"O APN non pode estar baleiro."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"O campo MCC debe conter 3 díxitos."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"O campo MNC debe conter 2 ou 3 díxitos."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restaurando a configuración de APN predeterminada."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Restablecer aos valores predeterminados"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Completouse o restablecemento da configuración de APN predeterminada."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sen título"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nomes do punto de acceso"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Novo APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"A configuración do nome do punto de acceso non está dispoñible para este usuario"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copiar no portapapeis?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copiar"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"en <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Xeral"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avanzada"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Configuración xeral"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Configuración avanzada"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Enviar mensaxes SMS individuais a todos os destinatarios. Só ti recibirás as respostas"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Enviar unha mensaxe MMS simple a todos os destinatarios"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Número descoñecido"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mensaxe nova"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mensaxe nova."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Selector de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Seleccionouse <xliff:g id="SIM_0">%1$s</xliff:g>, selector da SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editar asunto"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Seleccionar a SIM ou editar o asunto"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Tocar e manter premido para gravar audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Iniciar nova conversa"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mensaxería"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista de Mensaxería"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mensaxería"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mensaxe nova"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista de conversas"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Cargando conversas"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Cargando mensaxes"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ver máis conversas"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Ver máis mensaxes"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversa eliminada"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Eliminouse a conversa. Toca para mostrar unha conversa de Mensaxería diferente"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloqueado"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Desbloqueado"</string>
+ <string name="db_full" msgid="8459265782521418031">"Queda pouco espazo de almacenamento. É posible que se perdan algúns datos."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Seleccionar anexos"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmar selección"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> seleccionado/s"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Elimina un ou varios anexos e téntao de novo."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Podes probar a enviar a túa mensaxe, pero é posible que non se entregue a menos que elimines un ou varios anexos."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Só podes enviar un vídeo por mensaxe. Elimina os vídeos adicionais e téntao de novo."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Mensaxería non puido cargar o anexo."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Enviar igualmente"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Non se puido iniciar a conversa"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Seleccionouse <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-gu-rIN/arrays.xml b/res/values-gu-rIN/arrays.xml
new file mode 100644
index 0000000..89975e0
--- /dev/null
+++ b/res/values-gu-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"કોઇ વિષય નહીં"</item>
+ <item msgid="272485471009191934">"વિષય નથી"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"હા"</item>
+ <item msgid="6049132459802288033">"નહીં"</item>
+ <item msgid="3084376867445867895">"ઑકે"</item>
+ <item msgid="3155097332660174689">"હીહી"</item>
+ <item msgid="2611328818571146775">"આભાર"</item>
+ <item msgid="4881335087096496747">"હું સહમત છું"</item>
+ <item msgid="2422296858597420738">"સરસ"</item>
+ <item msgid="4805581752819452687">"મારી અનુકૂળતાએ"</item>
+ <item msgid="4746700499431366214">"ઑકે, હું તમને પછી મળું છું"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
new file mode 100644
index 0000000..866823c
--- /dev/null
+++ b/res/values-gu-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"મેસેજિંગ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"મેસેજિંગ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"વાર્તાલાપ પસંદ કરો"</string>
+ <string name="action_settings" msgid="1329008122345201684">"સેટિંગ્સ"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"સંદેશ મોકલો"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"એક જોડાણ ઉમેરો"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"સહાય"</string>
+ <string name="welcome" msgid="2857560951820802321">"સ્વાગત છે"</string>
+ <string name="skip" msgid="7238879696319945853">"છોડી દો"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"આગલું &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"આગલું"</string>
+ <string name="exit" msgid="1905187380359981199">"બહાર નીકળો"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"સેટિંગ્સ &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"સેટિંગ્સ"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"મેસેજિંગને SMS, ફોન અને સંપર્કોની પરવાનગીની જરૂર છે."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"તમે સેટિંગ્સ &gt; એપ્લિકેશન્સ &gt; મેસેજિંગ &gt; પરવાનગીઓમાં પરવાનગીઓ બદલી શકો છો."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"તમે સેટિંગ્સ, એપ્લિકેશન્સ, મેસેજિંગ, પરવાનગીઓમાં પરવાનગીઓ બદલી શકો છો."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"વારંવાર"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"તમામ સંપર્કો"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> પર મોકલો"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ચિત્રો અથવા વિડિઓ કેપ્ચર કરો"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"આ ઉપકરણથી છબીઓ પસંદ કરો"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ઑડિઓ રેકોર્ડ કરો"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ફોટો પસંદ કરો"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"મીડિયા પસંદ થયેલ છે."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"મીડિયા પસંદ કરેલ નથી."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> પસંદ કર્યાં"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"છબી <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" msgid="8142175029053370662">"છબી"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ઑડિઓ રેકોર્ડ કરો"</string>
+ <string name="action_share" msgid="2143483844803153871">"શેર કરો"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"હમણાં જ"</string>
+ <string name="posted_now" msgid="867560789350406701">"હમણાં"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> મિનિટ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> મિનિટ</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> કલાક</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> કલાક</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> દિવસ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> દિવસ</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> અઠવાડિયા</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> અઠવાડિયા</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> મહિના</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> મહિના</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> વર્ષ</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> વર્ષ</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"વર્ગ 0 સંદેશ"</string>
+ <string name="save" msgid="5081141452059463572">"સાચવો"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ઉપકરણ પર જગ્યા ઓછી છે. મેસેજિંગ જગ્યા ખાલી કરવા માટે જૂના સંદેશા આપમેળે કાઢી નાખશે."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"સ્ટોરેજ સ્થાન સમાપ્ત થયું"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"તમારા ઉપકરણ પર વધુ સ્થાન ઉપલબ્ધ ન થાય ત્યાં સુધી મેસેજિંગ સંદેશા મોકલી અથવા પ્રાપ્ત કરી શકશે નહીં."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"ઓછો SMS સંગ્રહ. તમારે કેટલાક સંદેશા કાઢી નાખવાની જરૂર હોઈ શકે છે."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"તમારા ફોન નંબરની પુષ્ટિ કરો"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"મેસેજિંગ તમારા જૂથ સંદેશાનું યોગ્ય રીતે વિતરણ કરે તે બાબતની આ એક-વારનું પગલું ખાતરી કરશે."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ફોન નંબર"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"મીડિયા સાથેના તમામ સંદેશા કાઢી નાખો"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> કરતાં જૂનાં સંદેશા કાઢી નાખો"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> કરતા જૂના સંદેશા આપમેળે કાઢી નાખો"</string>
+ <string name="ignore" msgid="7063392681130898793">"અવગણો"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"મીડિયા સાથેના તમામ સંદેશા કાઢી નાખીએ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> કરતા જૂના સંદેશા કાઢી નાખીએ?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> કરતા જૂનાં સંદેશા કાઢી નાખીએ અને આપમેળે કાઢી નાખવું ચાલુ કરીએ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> એ કહ્યું"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"તમે કહ્યું"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> તરફથી સંદેશ"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"તમે એક સંદેશ મોકલ્યો"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"મોકલી રહ્યાં છે…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"મોકલાયો નથી. ફરીથી પ્રયાસ કરવા માટે ટચ કરો."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"મોકલાયું નથી. ફરીથી પ્રયાસ કરી રહ્યું છે…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ફરીથી મોકલો અથવા કાઢી નાખો"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"કૃપા કરીને કટોકટીની સેવાઓ માટે એક વૉઇસ કૉલ કરો. તમારો ટેક્સ્ટ સંદેશ આ સમયે વિતરિત કરી શકાયો નથી."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"નિષ્ફળ થયું"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ડાઉનલોડ કરવા માટેનો નવો MMS સંદેશ"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"નવો MMS સંદેશ"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ડાઉનલોડ કરી શક્યાં નથી"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ફરીથી પ્રયત્ન કરવા ટચ કરો"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ડાઉનલોડ કરવા ટચ કરો"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ડાઉનલોડ કરો અથવા કાઢી નાખો"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ડાઉનલોડ કરી રહ્યાં છે..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"સંદેશની સમયસીમા સમાપ્ત થઈ અથવા ઉપલબ્ધ નથી"</string>
+ <string name="mms_info" msgid="3402311750134118165">"કદ: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, સમય સમાપ્તિ: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"મોકલી શકતા નથી. પ્રાપ્તકર્તા માન્ય નથી."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"નેટવર્ક પર સેવા સક્રિય કરી નથી"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"નેટવર્ક સમસ્યાને કારણે મોકલી શકાયો નથી"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"સંદેશની સમયસીમા સમાપ્ત થઈ અથવા ઉપલબ્ધ નથી"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(કોઇ વિષય નથી)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"અજાણ્યો પ્રેષક"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"વિતરિત"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> તરફનાં <xliff:g id="SUBJECT">%1$s</xliff:g> સંદેશા ડાઉનલોડ કરી શકાયા નથી."</string>
+ <string name="low_memory" msgid="5300743415198486619">"ઓછી મેમરીને કારણે ડેટાબેસ ક્રિયા પૂર્ણ કરી શકાઈ નથી"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"સંદેશ મોકલ્યો નથી"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"મેસેજિંગમાં કેટલાક સંદેશા મોકલ્યાં નથી"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> વાર્તાલાપમાં <xliff:g id="MESSAGES_1">%d</xliff:g> સંદેશા</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> વાર્તાલાપમાં <xliff:g id="MESSAGES_1">%d</xliff:g> સંદેશા</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"સંદેશ ડાઉનલોડ થયેલ નથી"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"મેસેજિંગમાં કેટલાક સંદેશા ડાઉનલોડ થયાં નથી"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> વાર્તાલાપમાં <xliff:g id="MESSAGES_1">%d</xliff:g> સંદેશા</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> વાર્તાલાપમાં <xliff:g id="MESSAGES_1">%d</xliff:g> સંદેશા</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> ને સંદેશ મોકલાયો નથી"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"કટોકટી સેવાઓને કૃપા કરીને એક વૉઇસ કૉલ કરો. <xliff:g id="NUMBER">%1$s</xliff:g> ને તમારો ટેક્સ્ટ સંદેશ આ સમયે વિતરિત કરી શકાયો નથી."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> નવા સંદેશા</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> નવા સંદેશા</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"પ્રારંભ કરો"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"કૅમેરો ઉપલબ્ધ નથી"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"કૅમેરો ઉપલબ્ધ નથી"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"વિડિઓ કેપ્ચર ઉપલબ્ધ નથી"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"મીડિયા સાચવી શકતા નથી"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ચિત્ર લઈ શકતા નથી"</string>
+ <string name="back" msgid="1477626055115561645">"પાછળ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"આર્કાઇવ કરેલા"</string>
+ <string name="action_delete" msgid="4076795795307486019">"કાઢી નાખો"</string>
+ <string name="action_archive" msgid="5437034800324083170">"આર્કાઇવ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"આર્કાઇવ.કરેલ નથી"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"સૂચનાઓ બંધ કરો"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"સૂચનાઓ ચાલુ કરો"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"સંપર્ક ઉમેરો"</string>
+ <string name="action_download" msgid="7786338136368564146">"ડાઉનલોડ કરો"</string>
+ <string name="action_send" msgid="377635240181672039">"મોકલો"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"કાઢી નાખો"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"આ સંદેશ કાઢી નાખીએ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"આ ક્રિયા પૂર્વવત્ કરી શકાતી નથી."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"કાઢી નાખો"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">આ વાર્તાલાપ કાઢી નાખીએ?</item>
+ <item quantity="other">આ વાર્તાલાપ કાઢી નાખીએ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"કાઢી નાખો"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"રદ કરો"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"પ્રતિ"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"બહુવિધ છબીઓ પસંદ કરો"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"પસંદગીની પુષ્ટિ કરો"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ઑડિઓ રેકોર્ડ કરી શકાતો નથી. ફરીથી પ્રયાસ કરો."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ઑડિઓ ચલાવી શકાતો નથી. ફરીથી પ્રયાસ કરો."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ઑડિઓ સાચવી શકાયો નથી. ફરીથી પ્રયાસ કરો."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ટચ કરો અને પકડો"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ચિત્ર"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ઑડિઓ ક્લિપ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"વિડિઓ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"સંપર્ક કાર્ડ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ડાઉનલોડ કરો"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS દ્વારા જવાબ આપો"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS દ્વારા જવાબ આપો"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"જવાબ આપો"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> સહભાગીઓ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> સહભાગીઓ</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"મારા"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"સંપર્ક અવરોધિત કર્યો અને આર્કાઇવ કર્યો"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"સંપર્ક અનાવરોધિત અને અનઆર્કાઇવ કર્યો"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> આર્કાઇવ કર્યાં"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> અનઆર્કાઇવ કર્યા"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"સૂચનાઓ બંધ કરી"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"સૂચનાઓ ચાલુ કરી"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"બધું સેટ થયું. મોકલો ને ફરીથી ટચ કરો."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"ડિફોલ્ટ SMS એપ્લિકેશન તરીકે મેસેજિંગ સફળતાપૂર્વક સેટ થયું."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">જોડાણો નિકાળો</item>
+ <item quantity="other">જોડાણો નિકાળો</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ઑડિઓ જોડાણ"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ઑડિઓ જોડાણ ચલાવો"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"થોભો"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> તરફથી સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> તરફથી સંદેશ નિષ્ફળ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> તરફથી સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> ને વણમોકલાયેલ સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> ને સંદેશ મોકલી રહ્યું છે: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> ને સંદેશ મોકલવું નિષ્ફળ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> ને સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> તરફથી સંદેશ નિષ્ફળ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> તરફથી સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> ને વણમોકલાયેલ સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> ને સંદેશ મોકલી રહ્યું છે: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> ને સંદેશ મોકલવું નિષ્ફળ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> ને સંદેશ: <xliff:g id="MESSAGE">%s</xliff:g>. સમય: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"નિષ્ફળ સંદેશ. ફરી પ્રયાસ કરવા ટચ કરો."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> સાથે વાર્તાલાપ"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"વિષય કાઢી નાખો"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"વિડિઓ કેપ્ચર કરો"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"એક સ્થિર છબી કેપ્ચર કરો"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ફોટો લો"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"વિડિઓ રેકોર્ડિંગ પ્રારંભ કરો"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"પૂર્ણ સ્ક્રીન કૅમેરા પર સ્વિચ કરો"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"આગળનાં અને પાછળનાં કૅમેરા વચ્ચે સ્વિચ કરો"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"રેકોર્ડિંગ બંધ કરો અને વિડિઓ જોડો"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"વિડિઓ રેકોર્ડિંગ બંધ કરો"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"મેસેજિંગ ફોટા"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" આલ્બમમાં <xliff:g id="QUANTITY_2">%d</xliff:g> ફોટા સાચવ્યાં</item>
+ <item quantity="other">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" આલ્બમમાં <xliff:g id="QUANTITY_2">%d</xliff:g> ફોટા સાચવ્યાં</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" આલ્બમમાં <xliff:g id="QUANTITY_2">%d</xliff:g> વિડિઓઝ સાચવી</item>
+ <item quantity="other">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" આલ્બમમાં <xliff:g id="QUANTITY_2">%d</xliff:g> વિડિઓઝ સાચવી</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" આલ્બમમાં <xliff:g id="QUANTITY_2">%d</xliff:g> જોડાણો સાચવ્યાં</item>
+ <item quantity="other">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" આલ્બમમાં <xliff:g id="QUANTITY_2">%d</xliff:g> જોડાણો સાચવ્યાં</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one">\"ડાઉનલોડ્સ\"માં <xliff:g id="QUANTITY_1">%d</xliff:g> જોડાણો સાચવ્યાં</item>
+ <item quantity="other">\"ડાઉનલોડ્સ\"માં <xliff:g id="QUANTITY_1">%d</xliff:g> જોડાણો સાચવ્યાં</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> જોડાણ સાચવ્યાં</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> જોડાણ સાચવ્યાં</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> જોડાણ સાચવી શક્યાં નથી</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> જોડાણ સાચવી શક્યાં નથી</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"સાચવેલ MMS જોડાણ"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"સેટિંગ્સ"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"આર્કાઇવ કરેલા"</string>
+ <string name="action_close" msgid="1840519376200478419">"બંધ કરો"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"વિગતવાર"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ડીબગ"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"સૂચનાઓ"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ધ્વનિ"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"શાંત"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"વાઇબ્રેટ"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"અવરોધિત"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS વિતરણ રિપોર્ટ્સ"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"તમે મોકલેલા દરેક SMS માટે વિતરણ રિપોર્ટની વિનંતી કરો"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"સ્વતઃ-પુનર્પ્રાપ્ત કરો"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS સંદેશા સ્વતઃ-પુનર્પ્રાપ્ત કરો"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"રોમિંગ સ્વતઃ-પુનર્પ્રાપ્ત કરો"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"રોમિંગ પર હોય ત્યારે MMS આપમેળે પુનર્પ્રાપ્ત કરો"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"જૂથ મેસેજિંગ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"બહુવિધ પ્રાપ્તકર્તાઓ હોય ત્યારે એક જ સંદેશ મોકલવા માટે MMS નો ઉપયોગ કરો"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ડિફોલ્ટ SMS એપ્લિકેશન"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ડિફોલ્ટ SMS એપ્લિકેશન"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"તમારો ફોન નંબર"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"અજાણ્યો"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"આઉટગોઇંગ સંદેશ ધ્વનિઓ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS ડમ્પ કરો"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"પ્રાપ્ત થયેલ SMS અપૂર્ણ ડેટાને બાહ્ય સંગ્રહ ફાઇલમાં ડમ્પ કરો"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS ડમ્પ કરો"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"પ્રાપ્ત થયેલ MMS અપૂર્ણ ડેટાને બાહ્ય સંગ્રહ ફાઇલમાં ડમ્પ કરો"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"વાયરલેસ ચેતવણીઓ"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"સંદેશ વિકલ્પો"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ટેક્સ્ટ કૉપિ કરો"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"વિગતો જુઓ"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"કાઢી નાખો"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ફોરવર્ડ કરો"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"સંદેશ વિગતો"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"પ્રકાર: "</string>
+ <string name="text_message" msgid="7415419755252205721">"ટેક્સ્ટ સંદેશ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"મલ્ટિમીડિયા સંદેશ"</string>
+ <string name="from_label" msgid="1947831848146564875">"અહીંથી: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"પ્રતિ: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"મોકલ્યું: "</string>
+ <string name="received_label" msgid="4442494712757995203">"પ્રાપ્ત કર્યુ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"વિષય: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"કદ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"પ્રાધાન્યતા: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"ઉચ્ચ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"સામાન્ય"</string>
+ <string name="priority_low" msgid="7398724779026801851">"નિમ્ન"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"પ્રેષક સરનામું છુપાયેલ છે"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"જોડાણો લોડ કરતી વખતે સંદેશ મોકલી શકાતો નથી."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"જોડાણ લોડ કરી શકાતું નથી. ફરીથી પ્રયાસ કરો."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"નેટવર્ક તૈયાર નથી. ફરીથી પ્રયાસ કરો."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ટેક્સ્ટ કાઢી નાખો"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ટેક્સ્ટ અને સંખ્યાઓ દાખલ કરવા વચ્ચે સ્વિચ કરો"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"વધુ સહભાગીઓ ઉમેરો"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"સહભાગીઓની પુષ્ટિ કરો"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"નવો વાર્તાલાપ પ્રારંભ કરો"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"આ આઇટમ પસંદ કરો"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"વિડિઓ ચલાવો"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"લોકો અને વિકલ્પો"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ડીબગ"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"લોકો અને વિકલ્પો"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"સામાન્ય"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"આ વાર્તાલાપમાંના લોકો"</string>
+ <string name="action_call" msgid="6596167921517350362">"એક કૉલ કરો"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"સંદેશ મોકલો"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; દ્વારા &lt;br/&gt;&lt;small&gt;સંદેશ મોકલો"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ફોટા મોકલો</item>
+ <item quantity="other">ફોટા મોકલો</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ઑડિઓ મોકલો</item>
+ <item quantity="other">ઑડિઓ મોકલો</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">વિડિઓઝ મોકલો</item>
+ <item quantity="other">વિડિઓઝ મોકલો</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">સંપર્ક કાર્ડ્સ મોકલો</item>
+ <item quantity="other">સંપર્ક કાર્ડ્સ મોકલો</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">જોડાણ મોકલો</item>
+ <item quantity="other">જોડાણ મોકલો</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one">મોકલવા માટે <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> જોડાણ તૈયાર છે</item>
+ <item quantity="other">મોકલવા માટે <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> જોડાણ તૈયાર છે</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"પ્રતિસાદ મોકલો"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store માં જુઓ"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"સંસ્કરણ માહિતી"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"સંસ્કરણ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ઓપન સોર્સ લાઇસેંસેસ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"સૂચનાઓ"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"જોડાણની મર્યાદા સુધી પહોંચ્યાં"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"જોડાણ લોડ કરવામાં નિષ્ફળ."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"સંપર્કોમાં ઉમેરીએ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"સંપર્ક ઉમેરો"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"વિષય"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"વિષય: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"સંપર્ક કાર્ડ લોડ કરી રહ્યું છે"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"સંપર્ક કાર્ડ લોડ કરી શકાયું નથી"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"સંપર્ક કાર્ડ જુઓ"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> સંપર્કો</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> સંપર્કો</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"સંપર્ક કાર્ડ્સ"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"જન્મદિવસ"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"નોંધો"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"સંદેશ ફોરવર્ડ કરો"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"જવાબ આપો"</string>
+ <string name="plus_one" msgid="9010288417554932581">"પસંદ"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS અક્ષમ કરેલ છે"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"મોકલવા માટે, મેસેજિંગને ડિફોલ્ટ SMS એપ્લિકેશન તરીકે સેટ કરો"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"મેસેજિંગને ડિફોલ્ટ SMS એપ્લિકેશન તરીકે સેટ કરો"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"બદલો"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"સંદેશા પ્રાપ્ત કરવા માટે, મેસેજિંગને ડિફોલ્ટ SMS એપ્લિકેશન તરીકે સેટ કરો"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS સંદેશા મોકલવા માટે પસંદીદા SIM પસંદ કરેલ નથી"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"આ એપ્લિકેશનને ઉપકરણ માલિક દ્વારા મંજૂરી નથી."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ઑકે"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"વાર્તાલાપમાં ઘણાં બધા સહભાગીઓ છે"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">અમાન્ય સંપર્કો</item>
+ <item quantity="other">અમાન્ય સંપર્કો</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"કૅમેરા છબી લોડ કરી શકાઈ નથી"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"તમે: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ડ્રાફ્ટ"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"એકવાર તમે એક નવો વાર્તાલાપ પ્રારંભ કરી લો તે પછી, તમે તેને અહીં સૂચિબદ્ધ જોશો"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"આર્કાઇવ કરેલ વાર્તાલાપ અહીં દેખાશે"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"વાર્તાલાપ લોડ કરી રહ્યાં છે…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ચિત્ર"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ઑડિઓ ક્લિપ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"વિડિઓ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"સંપર્ક કાર્ડ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"પૂર્વવત્ કરો"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ફરી પ્રયાસ કરો"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"એક નવો સંદેશ પ્રારંભ કરવા માટે સંપર્ક નામ અથવા ફોન નંબર દાખલ કરો"</string>
+ <string name="action_block" msgid="9032076625645190136">"અવરોધિત કરો"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> ને અવરોધિત કરો"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> ને અનાવરોધિત કરો"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> ને અવરોધિત કરીએ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"તમે આ નંબરથી સંદેશા પ્રાપ્ત કરવાનું ચાલુ રાખશો પરંતુ હવેથી સૂચિત કરવામાં આવશે નહીં. આ વાર્તાલાપ આર્કાઇવ કરવામાં આવશે."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"અવરોધિત કરેલા સંપર્કો"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"અનાવરોધિત કરો"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"અવરોધિત કરેલા સંપર્કો"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"દસ્તાવેજ લાઇબ્રેરીમાંથી છબી પસંદ કરો"</string>
+ <string name="sending_message" msgid="6363584950085384929">"સંદેશ મોકલી રહ્યાં છીએ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"સંદેશ મોકલ્યો"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"સેલ્યુલર ડેટા બંધ છે. તમારી સેટિંગ્સ તપાસો."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"એરપ્લેન મોડમાં સંદેશા મોકલી શકાતા નથી"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"સંદેશ મોકલી શકાયો નથી"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"સંદેશ ડાઉનલોડ થયો"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"સેલ્યુલર ડેટા બંધ છે. તમારી સેટિંગ્સ તપાસો."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"એરપ્લેન મોડમાં સંદેશા ડાઉનલોડ કરી શકાતા નથી"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"સંદેશ ડાઉનલોડ કરી શકાયો નથી"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"શૂન્ય"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"એક"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"બે"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"ત્રણ"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"ચાર"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"પાંચ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"છ"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"સાત"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"આઠ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"નવ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> સાથે સંદેશ મોકલી શકાતો નથી, ભૂલ <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"અજાણ્યાં કેરીઅર સાથે સંદેશ મોકલી શકાતો નથી, ભૂલ <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"ફોરવર્ડ: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"સંદેશ મોકલાયો નથી: નેટવર્ક પર સેવા સક્રિય કરેલ નથી"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"સંદેશ મોકલાયો નથી: અમાન્ય લક્ષ્યસ્થાન સરનામું"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"સંદેશ મોકલાયો નથી: અમાન્ય સંદેશ"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"સંદેશ મોકલાયો નથી: અસમર્થિત સામગ્રી"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"સંદેશ મોકલાયો નથી: અસમર્થિત સંદેશ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"સંદેશ મોકલ્યો નથી: ખૂબ મોટો"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"નવો સંદેશ"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"જુઓ"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"છબી"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"કોઈ યોગ્ય એપ્લિકેશન મળી શકી નથી"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"પ્રાપ્તકર્તા દૂર કરો"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"નવો સંદેશ"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"રદ કરો"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"અ‍ૅક્સેસ પોઇન્ટ સંપાદિત કરો"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"સેટ નથી"</string>
+ <string name="apn_name" msgid="1572691851070894985">"નામ"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS પ્રોક્સી"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS પોર્ટ"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN પ્રકાર"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN કાઢી નાખો"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"નવું APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"સાચવો"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"છોડી દો"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"નામ ફીલ્ડ ખાલી હોઈ શકતી નથી."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ખાલી હોઈ શકતું નથી."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ફીલ્ડ 3 અંકોની હોવી આવશ્યક છે."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ફીલ્ડ 2 અથવા 3 અંકોની હોવી આવશ્યક છે."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ડિફોલ્ટ APN સેટિંગ્સ પુનર્સ્થાપિત કરવી."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"ડિફોલ્ટ પર ફરીથી સેટ કરો"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"ડિફોલ્ટ APN સેટિંગ્સ ફરીથી સેટ કરો પૂર્ણ થયું."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"મથાળા વગર"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ઍક્સેસ પોઇન્ટનું નામ"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"નવું APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"અ‍ૅક્સેસ પોઇન્ટનું નામ સેટિંગ્સ આ વપરાશકર્તા માટે ઉપલબ્ધ નથી"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ક્લિપબોર્ડ પર કૉપિ કરીએ?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"કૉપિ કરો"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> પર"</string>
+ <string name="general_settings" msgid="5409336577057897710">"સામાન્ય"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"વિગતવાર"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"સામાન્ય સેટિંગ્સ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"વિગતવાર સેટિંગ્સ"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"તમામ પ્રાપ્તકર્તાઓને વ્યક્તિગત SMS સંદેશાઓ મોકલો. ફક્ત તમને જ કોઇ જવાબો મળશે"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"તમામ પ્રાપ્તકર્તાઓને એક જ MMS મોકલો"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"અજાણ્યો નંબર"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"નવો સંદેશ"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"નવો સંદેશ."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM પસંદગીકર્તા"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> પસંદ કર્યા, SIM પસંદગીકર્તા"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"વિષય સંપાદિત કરો"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM પસંદ કરો અથવા વિષય સંપાદિત કરો"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ઑડિઓ રેકોર્ડ કરવા માટે ટચ કરો અને પકડો"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"નવો વાર્તાલાપ પ્રારંભ કરો"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"મેસેજિંગ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"મેસેજિંગ સૂચિ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"મેસેજિંગ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"નવો સંદેશ"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"વાર્તાલાપની સૂચિ"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"વાર્તાલાપ લોડ કરી રહ્યાં છે"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"સંદેશા લોડ કરી રહ્યું છે"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"વધુ વાર્તાલાપ જુઓ"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"વધુ સંદેશા જુઓ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"વાર્તાલાપ કાઢી નખાયો"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"વાર્તાલાપ કાઢી નાખ્યો. એક ભિન્ન મેસેજિંગ વાર્તાલાપ દર્શાવવા માટે ટચ કરો"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"અવરોધિત"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"અનાવરોધિત"</string>
+ <string name="db_full" msgid="8459265782521418031">"સંગ્રહ સ્થાન ઓછું છે. કેટલોક ડેટા ખોવાઈ શકે છે."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"જોડાણો પસંદ કરો"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"પસંદગીની પુષ્ટિ કરો"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> પસંદ કર્યાં"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"કૃપા કરીને એક અથવા વધુ જોડાણો દૂર કરો અને ફરીથી પ્રયાસ કરો."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"તમે તમારો સંદેશ મોકલવાનો પ્રયાસ કરી શકો છો, પરંતુ તમે એક અથવા વધુ જોડાણો દૂર નહીં કરો ત્યાં સુધી તે વિતરિત કરવામાં આવી શકશે નહીં."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"તમે પ્રતિ સંદેશ ફક્ત એક વિડિઓ મોકલી શકો છો. કૃપા કરીને અતિરિક્ત વિડિઓઝ દૂર કરો અને ફરી પ્રયાસ કરો."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"જોડાણ લોડ કરવામાં મેસેજિંગ નિષ્ફળ."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"કોઈપણ રીતે મોકલો"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"વાર્તાલાપ પ્રારંભ કરી શકાયો નથી"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> પસંદ કરી"</string>
+</resources>
diff --git a/res/values-hi/arrays.xml b/res/values-hi/arrays.xml
new file mode 100644
index 0000000..abdcedf
--- /dev/null
+++ b/res/values-hi/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"कोई विषय नहीं"</item>
+ <item msgid="272485471009191934">"कोई विषय नहीं"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"हां"</item>
+ <item msgid="6049132459802288033">"नहीं"</item>
+ <item msgid="3084376867445867895">"ठीक"</item>
+ <item msgid="3155097332660174689">"हेहे"</item>
+ <item msgid="2611328818571146775">"धन्यवाद"</item>
+ <item msgid="4881335087096496747">"मैं सहमत हूं"</item>
+ <item msgid="2422296858597420738">"बढ़िया"</item>
+ <item msgid="4805581752819452687">"मेरे रास्ते में रहने के दौरान"</item>
+ <item msgid="4746700499431366214">"ठीक है, मैं आपसे बाद में संपर्क करूंगा/करूंगी"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..c281680
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"संदेश सेवा"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"संदेश सेवा"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"वार्तालाप चुनें"</string>
+ <string name="action_settings" msgid="1329008122345201684">"सेटिंग"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"संदेश भेजें"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"कोई अटैचमेंट जोड़ें"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"सहायता"</string>
+ <string name="welcome" msgid="2857560951820802321">"स्वागत है"</string>
+ <string name="skip" msgid="7238879696319945853">"अभी नहीं"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"अगला &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"अगला"</string>
+ <string name="exit" msgid="1905187380359981199">"बाहर निकलें"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"सेटिंग &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"सेटिंग"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"संदेश सेवा को SMS, फ़ोन और संपर्कों के लिए अनुमति की आवश्यकता होती है."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"आप अनुमतियों को सेटिंग &gt; ऐप्स &gt; संदेश सेवा &gt; अनुमतियां में बदल सकते हैं."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"आप अनुमतियों को सेटिंग, ऐप्‍स, संदेश सेवा, अनुमतियां में बदल सकते हैं."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"अक्सर"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"सभी संपर्क"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> पर भेजें"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"चित्र या वीडियो कैप्चर करें"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"इस डिवाइस के चित्र चुनें"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ऑडियो रिकॉर्ड करें"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"फ़ोटो चुनें"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"मीडिया को चुना है."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"मीडिया को नहीं चुना है."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> चुने गए"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"चित्र <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" msgid="8142175029053370662">"चित्र"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ऑडियो रिकॉर्ड करें"</string>
+ <string name="action_share" msgid="2143483844803153871">"साझा करें"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"अभी-अभी"</string>
+ <string name="posted_now" msgid="867560789350406701">"अभी"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> मिनट</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> मिनट</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> घंटे</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> घंटे</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> दिन</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> दिन</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> सप्‍ताह</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> सप्‍ताह</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> महीने</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> महीने</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> वर्ष</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> वर्ष</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"वर्ग 0 संदेश"</string>
+ <string name="save" msgid="5081141452059463572">"जोड़ें"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"डिवाइस में स्थान कम है. स्थान खाली करने के लिए संदेश सेवा पुराने संदेशों को अपने आप हटा देगी."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"मेमोरी स्‍थान समाप्‍त हो रहा है"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"हो सकता है कि आपके डिवाइस पर अधिक स्‍थान उपलब्‍ध होने तक संदेश सेवा संदेशों को ना भेजे या प्राप्‍त ना करे."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"कम SMS मेमोरी. आपको संदेशों को हटाने की आवश्‍यकता हो सकती है."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"अपने फ़ोन नंबर की पुष्टि करें"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"यह एक-बार का चरण यह सुनिश्चित करेगा कि संदेश सेवा आपके समूह संदेशों को ठीक से वितरित करेगी."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"फ़ोन नंबर"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"मीडिया वाले सभी संदेशों को हटाएं"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> से पुराने संदेशों को हटाएं"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> से पुराने संदेशों को स्‍वत: हटाएं"</string>
+ <string name="ignore" msgid="7063392681130898793">"अनदेखा करें"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"मीडिया वाले सभी संदेशों को हटाएं?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> से पुराने संदेशों को हटाएं?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> से पुराने संदेश हटाएं और स्‍वत: हटाना चालू करें?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"प्रेषक: <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"आपने कहा"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> की ओर से संदेश"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"आपने एक संदेश भेजा"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"भेजा जा रहा है..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"नहीं भेजा गया. पुन: प्रयास करने के लिए स्‍पर्श करें."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"नहीं भेजा गया. पुन: प्रयास किया जा रहा है…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"फिर से भेजें या हटाएं"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"कृपया आपातकालीन सेवाओं को वॉइस कॉल करें. आपका लेख संदेश इस समय वितरित नहीं किया जा सकता."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"विफल रहा"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"डाउनलोड करने के लिए नया MMS संदेश"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"नया MMS संदेश"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"डाउनलोड नहीं किया जा सका"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"पुन: प्रयास करने के लिए स्पर्श करें"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"डाउनलोड करने के लिए स्‍पर्श करें"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"डाउनलोड करें या हटाएं"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"डाउनलोड किया जा रहा है..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"संदेश की समय सीमा समाप्त हो गई या वह उपलब्ध नहीं है"</string>
+ <string name="mms_info" msgid="3402311750134118165">"आकार: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, समाप्‍ति: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"भेजा नहीं जा सकता. प्राप्‍तकर्ता मान्‍य नहीं है."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"नेटवर्क पर सेवा सक्रिय नहीं है"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"नेटवर्क समस्‍या के कारण भेजा नहीं जा सका"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"संदेश की समय सीमा समाप्त हो गई या वह उपलब्ध नहीं है"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(कोई विषय नहीं)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"अज्ञात प्रेषक"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"डिलीवर कर दिया गया"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> से <xliff:g id="SUBJECT">%1$s</xliff:g> संदेश डाउनलोड नहीं किया जा सका."</string>
+ <string name="low_memory" msgid="5300743415198486619">"कम मेमोरी के कारण डेटाबेस कार्रवाई पूर्ण नहीं किया जा सका"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"संदेश नहीं भेजा गया"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"कुछ संदेशों को संदेश सेवा में नहीं भेजा गया है"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> वार्तालापों में <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> वार्तालापों में <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"संदेश डाउनलोड नहीं किया गया"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"कुछ संदेशों को संदेश सेवा में डाउनलोड नहीं किया गया है"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> वार्तालापों में <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> वार्तालापों में <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> को संदेश नहीं भेजा गया"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"कृपया आपातकालीन सेवाओं को वॉइस कॉल करें. <xliff:g id="NUMBER">%1$s</xliff:g> को भेजा गया आपका लेख संदेश इस समय वितरित नहीं किया जा सकता."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> नए संदेश</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> नए संदेश</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"प्रारंभ करें"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"कैमरा उपलब्ध नहीं है"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"कैमरा उपलब्ध नहीं है"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"वीडियो कैप्‍चर उपलब्‍ध नहीं है"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"मीडिया सहेजा नहीं जा सकता"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"चित्र नहीं लिया जा सकता"</string>
+ <string name="back" msgid="1477626055115561645">"पीछे"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"संग्रहीत"</string>
+ <string name="action_delete" msgid="4076795795307486019">"हटाएं"</string>
+ <string name="action_archive" msgid="5437034800324083170">"संग्रहीत करें"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"संग्रह से निकालें"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"नोटिफिकेशन बंद करें"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"नोटिफिकेशन चालू करें"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"संपर्क जोड़ें"</string>
+ <string name="action_download" msgid="7786338136368564146">"डाउनलोड करें"</string>
+ <string name="action_send" msgid="377635240181672039">"भेजें"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"हटाएं"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"इस संदेश को हटाएं?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"इस क्रिया को पूर्ववत नहीं किया जा सकता."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"हटाएं"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">इन वार्तालापों को हटाएं?</item>
+ <item quantity="other">इन वार्तालापों को हटाएं?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"हटाएं"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"अभी नहीं"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"प्रति"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"एकाधिक चित्र चुनें"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"चयन की दुबारा पूछें"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ऑडियो रिकॉर्ड नहीं किया जा सकता. पुनः प्रयास करें."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ऑडियो नहीं चलाया जा सकता. पुन: प्रयास करें."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ऑडियो सहेजा नहीं जा सका. पुनः प्रयास करें."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"स्पर्श करके रखें"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"चित्र"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ऑडियो क्लिप"</string>
+ <string name="notification_video" msgid="4331423498662606204">"वीडियो"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"संपर्क कार्ड"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"डाउनलोड करें"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS के द्वारा उत्तर दें"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS से उत्तर दें"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"उत्तर दें"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> प्रतिभागी</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> प्रतिभागी</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"मुझे"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"संपर्क अवरोधित तथा संग्रहीत किया गया"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"संपर्क को अनवरोधित किया गया और संग्रह से निकाला गया"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> को संग्रहीत किया गया"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> को संग्रह से निकाला गया"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"नोटिफ़िकेशन बंद हैं"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"नोटिफ़िकेशन चालू हैं"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"बिल्‍कुल तैयार. भेजें को पुन: स्‍पर्श करें."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"संदेश सेवा को डिफ़ॉल्ट SMS ऐप के रूप में सफलतापूर्वक सेट कर दिया गया है."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">अटैचमेंट छोड़ें</item>
+ <item quantity="other">अटैचमेंट छोड़ें</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ऑडियो अटैचमेंट"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ऑडियो अटैचमेंट चलाएं"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"रोकें"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> की ओर से संदेश: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> का भेजने में विफल रहा संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> का संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> को नहीं भेजा गया संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> को संदेश भेजा जा रहा है: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> को भेजने में विफल रहा संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> के लिए संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> का विफल रहा संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> का संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> को नहीं भेजा गया संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> को संदेश भेजा जा रहा है: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> को भेजने में विफल रहा संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> के लिए संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"विफल रहा संदेश. पुन: प्रयास करने के लिए स्पर्श करें."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> के साथ बातचीत"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"विषय हटाएं"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"वीडियो कैप्चर करें"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"स्थिर चित्र कैप्चर करें"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"चित्र लें"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"वीडियो रिकॉर्ड करना प्रारंभ करें"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"पूर्ण स्क्रीन कैमरा पर स्विच करें"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"सामने और पीछे वाले कैमरे के बीच स्‍विच करें"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"रिकॉर्डिंग रोकें और वीडियो अटैच करें"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"वीडियो रिकॉर्ड करना बंद करें"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"संदेश सेवा फ़ोटो"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> फ़ोटो \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बम में सहेजी गईं</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> फ़ोटो \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बम में सहेजी गईं</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> वीडियो \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बम में सहेजे गए</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> वीडियो \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बम में सहेजे गए</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> अटैचमेंट \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बम में सहेजे गए</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> अटैचमेंट \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बम में सहेजे गए</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> अटैचमेंट \"डाउनलोड\" में सहेजे गए</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> अटैचमेंट \"डाउनलोड\" में सहेजे गए</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> अटैचमेंट सहेजे गए</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> अटैचमेंट सहेजे गए</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> अटैचमेंट सहेजे नहीं जा सके</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> अटैचमेंट सहेजे नहीं जा सके</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS अटैचमेंट सहेजा गया"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"सेटिंग"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"संग्रहीत"</string>
+ <string name="action_close" msgid="1840519376200478419">"बंद करें"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"अतिरिक्त सेटिंग"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"डीबग"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"नोटिफिकेशन"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ध्वनि"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"मौन"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"वाइब्रेट"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"अवरोधित"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS वितरण रिपोर्ट"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"आपके द्वारा भेजे गए प्रत्येक SMS की वितरण रिपोर्ट का अनुरोध करें"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"स्वतः पुनर्प्राप्ति"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS संदेशों को अपने आप प्राप्त करें"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"रोमिंग स्वतः पुनर्प्राप्ति"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"रोमिंग के दौरान MMS अपने आप प्राप्त करें"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"समूह संदेश सेवा"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"एक से अधिक प्राप्तकर्ता होने पर एक ही संदेश भेजने के लिए MMS का उपयोग करें"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"डिफ़ॉल्ट SMS ऐप"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"डिफ़ॉल्ट SMS ऐप"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"आपका फ़ोन नंबर"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"अज्ञात"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"आउटगोइंग संदेश ध्वनि"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS डंप करें"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"बाहरी मेमोरी फ़ाइल में प्राप्त किया गया SMS अपरिष्कृत डेटा डंप करें"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS डंप करें"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"बाहरी मेमोरी फ़ाइल में प्राप्त किया गया MMS अपरिष्कृत डेटा डंप करें"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"वायरलेस अलर्ट"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"संदेश के विकल्प"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"लेख की प्रतिलिपि बनाएं"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"विवरण देखें"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"हटाएं"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"अग्रेषित करें"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"संदेश के विवरण"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"प्रकार: "</string>
+ <string name="text_message" msgid="7415419755252205721">"लेख संदेश"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"मल्टीमीडिया संदेश"</string>
+ <string name="from_label" msgid="1947831848146564875">"प्रेषक: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"प्रति: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"भेजा गया: "</string>
+ <string name="received_label" msgid="4442494712757995203">"प्राप्त हुआ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"विषय: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"आकार: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"प्राथमिकता: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"सिम: "</string>
+ <string name="priority_high" msgid="728836357310908368">"उच्च"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"सामान्य"</string>
+ <string name="priority_low" msgid="7398724779026801851">"निम्न"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"सिम <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"प्रेषक का पता छिपाएं"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"अटैचमेंट लोड करते समय संदेश नहीं भेजा जा सकता."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"अटैचमेंट लोड नहीं किया जा सकता. पुन: प्रयास करें."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"नेटवर्क तैयार नहीं है. पुनः प्रयास करें."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"लेख हटाएं"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"लेख और संख्याएं डालने के बीच स्विच करें"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"और अधिक प्रतिभागी जोड़ें"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"प्रतिभागियों की पुष्‍टि करें"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"नई बातचीत प्रारंभ करें"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"यह आइटम चुनें"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"वीडियो चलाएं"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"लोग और विकल्प"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"डीबग"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"लोग और विकल्प"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"सामान्य"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"इस वार्तालाप में लोग"</string>
+ <string name="action_call" msgid="6596167921517350362">"कॉल करें"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"संदेश भेजें"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;from <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; से संदेश भेजें"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">फ़ोटो भेजें</item>
+ <item quantity="other">फ़ोटो भेजें</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ऑडियो भेजें</item>
+ <item quantity="other">ऑडियो भेजें</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">वीडियो भेजें</item>
+ <item quantity="other">वीडियो भेजें</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">संपर्क कार्ड भेजें</item>
+ <item quantity="other">संपर्क कार्ड भेजें</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">अटैचमेंट भेजें</item>
+ <item quantity="other">अटैचमेंट भेजें</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> अटैचमेंट भेजे जाने के लिए तैयार हैं</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> अटैचमेंट भेजे जाने के लिए तैयार हैं</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"फ़ीडबैक भेजें"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play स्टोर में देखें"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"वर्शन जानकारी"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"वर्शन %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ओपन सोर्स लाइसेंस"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"नोटिफिकेशन"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"अटैचमेंट सीमा पर पहुंच गए"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"अटैचमेंट लोड करने में विफल रहा."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"संपर्कों में जोड़ें?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"संपर्क जोड़ें"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"विषय"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"विषय: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"संपर्क कार्ड लोड हो रहा है"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"संपर्क कार्ड लोड नहीं किया जा सका"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"संपर्क कार्ड देखें"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> संपर्क</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> संपर्क</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"संपर्क कार्ड"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"जन्मदिन"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"नोट"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"संदेश अग्रेषित करें"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"जवाब दें"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS अक्षम है"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"भेजने के लिए, संदेश सेवा को डिफ़ॉल्ट SMS ऐप के रूप में सेट करें"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"संदेश सेवा को डिफ़ॉल्ट SMS ऐप के रूप में सेट करें"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"बदलें"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"संदेश प्राप्‍त करने के लिए, संदेश सेवा को डिफ़ॉल्‍ट SMS ऐप के रूप में सेट करें"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS संदेश भेजने के लिए कोई भी पसंदीदा सिम नहीं चुनी गई है"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"डिवाइस स्‍वामी द्वारा इस ऐप्स की अनुमति नहीं दी गई है."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ठीक"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"बातचीत में बहुत अधिक प्रतिभागी हैं"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">अमान्‍य संपर्क</item>
+ <item quantity="other">अमान्‍य संपर्क</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"कैमरे का चित्र लोड नहीं किया जा सका"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"आप: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ड्राफ़्ट"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"आपके द्वारा नया वार्तालाप प्रारंभ करने के बाद, वह आपको यहां सूचीबद्ध दिखाई देगा"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"संग्रहीत वार्तालाप यहां दिखाई देते हैं"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"वार्तालाप लोड हो रहे हैं…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"चित्र"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ऑडियो क्लिप"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"वीडियो"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"संपर्क कार्ड"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"वापस लाएं"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"पुन: प्रयास करें"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"नया संदेश प्रारंभ करने के लिए कोई संपर्क नाम या फ़ोन नंबर डालें"</string>
+ <string name="action_block" msgid="9032076625645190136">"अवरुद्ध करें"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> को अवरुद्ध करें"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> को अनवरोधित करें"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> को अवरोधित करें?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"आपको इस संदेश से नंबर प्राप्‍त होते रहेंगे लेकिन अब उनकी सूचना नहीं दी जाएगी. इस वार्तालाप को संग्रहीत किया जाएगा."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"अवरोधित संपर्क"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"अनवरोधित करें"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"अवरोधित संपर्क"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"दस्‍तावेज़ लाइब्रेरी से चित्र चुनें"</string>
+ <string name="sending_message" msgid="6363584950085384929">"संदेश भेजा जा रहा है"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"संदेश भेजा गया"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"सेलुलर डेटा बंद हो गया है. अपनी सेटिंग जांचें."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"हवाई जहाज़ मोड में संदेश नहीं भेजे जा सकते"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"संदेश भेजा नहीं जा सका"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"संदेश डाउनलोड किया गया"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"सेल्‍युलर डेटा बंद हो गया है. अपनी सेटिंग जांचें."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"संदेश को हवाई जहाज़ मोड में डाउनलोड नहीं किया जा सकता"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"संदेश डाउनलोड नहीं किया जा सका"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"शून्य"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"एक"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"दो"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"तीन"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"चार"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"पांच"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"छह"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"सात"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"आठ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"नौ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> के साथ संदेश नहीं भेजा जा सकता, त्रुटि <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"अज्ञात वाहक के साथ संदेश नहीं भेजा जा सकता, त्रुटि <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"अग्रेषित: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"संदेश नहीं भेजा गया: नेटवर्क पर सेवा सक्रिय नहीं है"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"संदेश नहीं भेजा गया: अमान्य गंतव्य पता"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"संदेश नहीं भेजा गया: अमान्य संदेश"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"संदेश नहीं भेजा गया: असमर्थित सामग्री"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"संदेश नहीं भेजा गया: असमर्थित संदेश"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"संदेश नहीं भेजा गया: बहुत बड़ा है"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"नया संदेश"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"देखें"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"चित्र"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"उपयुक्त ऐप्लिकेशन नहीं मिला"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"प्राप्तकर्ता को निकालें"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"नया संदेश"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"अभी नहीं"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"पहुंच बिंदु संपादित करें"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"सेट नहीं है"</string>
+ <string name="apn_name" msgid="1572691851070894985">"नाम"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS प्रॉक्‍सी"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS पोर्ट"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN प्रकार"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN हटाएं"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"नया APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"सहेजें"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"छोड़ें"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"नाम फ़ील्‍ड खाली नहीं हो सकता."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN खाली नहीं हो सकता."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC फ़ील्‍ड में 3 अंक होने चाहिए."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MCC फ़ील्‍ड में 2 या 3 अंक होने चाहिए."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"डिफ़ॉल्‍ट APN सेटिंग पुनर्स्थापित हो रही हैं."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"डिफ़ॉल्ट पर रीसेट करें"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"डिफ़ॉल्‍ट APN सेटिंग रीसेट करना पूर्ण हुआ."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"शीर्षक रहित"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"पहुंच बिंदु के नाम"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"नया APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"एैक्सेस बिंदु नाम सेटिंग इस उपयोगकर्ता के लिए उपलब्ध नहीं हैं"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"क्लिपबोर्ड पर कॉपी बनाएं?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"कॉपी बनाएं"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> पर"</string>
+ <string name="general_settings" msgid="5409336577057897710">"सामान्य"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"अतिरिक्त सेटिंस"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"सामान्य सेटिंग"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"अतिरिक्त सेटिंग"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" सिम"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"सभी प्राप्‍तकर्ताओं को व्‍यक्‍तिगत SMS संदेश भेजें. कोई भी जवाब केवल आपको प्राप्‍त होंगे"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"सभी प्राप्‍तकर्ताआें को एकल MMS भेजें"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"अज्ञात नंबर"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"नया संदेश"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"नया संदेश."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"सिम चयनकर्ता"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> चयनित, SIM चयनकर्ता"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"विषय संपादित करें"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"सिम चुनें या विषय संपादित करें"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ऑडियो रिकॉर्ड करने के लिए स्‍पर्श करके रखें"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"नई बातचीत प्रारंभ करें"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"संदेश सेवा"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"संदेश सेवा सूची"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"संदेश सेवा"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"नया संदेश"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"वार्तालाप सूची"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"बातचीत लोड हो रही हैं…"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"संदेश लोड हो रहे हैं"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"और बातचीत देखें"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"अधिक संदेश देखें"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"वार्तालाप हटा दिया गया है"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"वार्तालाप हटा दिया गया. कोई भिन्न संदेश सेवा वार्तालाप दिखाने के लिए स्पर्श करें"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"अवरोधित"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"अनवरोधित"</string>
+ <string name="db_full" msgid="8459265782521418031">"मेमोरी स्‍थान कम है. कुछ डेटा खो सकता है."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"अटैचमेंट चुनें"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"चयन की पुष्टि करें"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> चुने गए"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"कृपया एक या अधिक अटैचमेंट निकालें और पुन: प्रयास करें."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"आप अपना संदेश भेजकर देख सकते हैं, लेकिन यह तब तक वितरित नहीं होगा जब तक कि आप एक या कुछ और अटैचमेंट निकाल नहीं देते."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"आप प्रति संदेश केवल एक वीडियो भेज सकते हैं. कृपया अतिरिक्‍त वीडियो निकालें और पुन: प्रयास करें."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"संदेश सेवा अटैचमेंट लोड करने में विफल रही."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"फिर भी भेजें"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"बातचीत प्रारंभ नहीं की जा सकी"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> चुनी गई"</string>
+</resources>
diff --git a/res/values-hr/arrays.xml b/res/values-hr/arrays.xml
new file mode 100644
index 0000000..5ebf706
--- /dev/null
+++ b/res/values-hr/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"nema predmeta"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Da"</item>
+ <item msgid="6049132459802288033">"Ne"</item>
+ <item msgid="3084376867445867895">"U redu"</item>
+ <item msgid="3155097332660174689">"He-he"</item>
+ <item msgid="2611328818571146775">"Hvala"</item>
+ <item msgid="4881335087096496747">"Slažem se"</item>
+ <item msgid="2422296858597420738">"Lijepo"</item>
+ <item msgid="4805581752819452687">"Krećem"</item>
+ <item msgid="4746700499431366214">"U redu, javit ću se kasnije"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..e12b64f
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,545 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Slanje poruka"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Slanje poruka"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Odabir razgovora"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Postavke"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Pošalji poruku"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Dodajte privitak"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Pomoć"</string>
+ <string name="welcome" msgid="2857560951820802321">"Dobro došli"</string>
+ <string name="skip" msgid="7238879696319945853">"Preskoči"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Dalje &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Dalje"</string>
+ <string name="exit" msgid="1905187380359981199">"Izlaz"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Postavke &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Postavke"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Slanje poruka treba dopuštenje za SMS, Telefon i Kontakte."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Dopuštenja možete promijeniti u Postavkama &gt; Aplikacije &gt; Slanje poruka &gt; Dopuštenja."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Dopuštenja možete promijeniti tako da u Postavkama otvorite Aplikacije, Slanje poruka, Dopuštenja."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Često kontaktirani"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Svi kontakti"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Pošalji na <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Snimite fotografije ili videozapis"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Odaberite slike s ovog uređaja"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Snimanje zvuka"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Odaberite fotografiju"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Medij je odabran."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Odabir medija poništen je."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Odabrano: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"slika <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" msgid="8142175029053370662">"slika"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Snimanje zvuka"</string>
+ <string name="action_share" msgid="2143483844803153871">"Dijeljenje"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Upravo sada"</string>
+ <string name="posted_now" msgid="867560789350406701">"Sad"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> sat</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> sata</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> sati</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dan</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dana</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dana</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> tjedan</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> tjedna</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tjedana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mjesec</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> mjeseca</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mjeseci</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> godina</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> godine</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> godina</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Poruka klase 0"</string>
+ <string name="save" msgid="5081141452059463572">"Spremi"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Na uređaju ponestaje prostora. Slanje poruka automatski će izbrisati starije poruke da bi se oslobodio prostor."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Ponestaje prostora za pohranu"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Slanje poruka možda neće slati ili primati poruke dok se na uređaju ne oslobodi više prostora."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Nema dovoljno prostora za pohranu SMS-a. Možda ćete morati izbrisati poruke."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Potvrđivanje telefonskog broja"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Taj jednokratni korak omogućit će Slanju poruka pravilnu isporuku vaših grupnih poruka."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonski broj"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Izbriši sve poruke s medijskim sadržajima"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Izbriši poruke starije od <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Automatski izbriši poruke starije od <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Zanemari"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Izbrisati sve poruke s medijskim sadržajima?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Izbrisati poruke starije od <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Izbrisati poruke starije od <xliff:g id="DURATION">%s</xliff:g> i uključiti automatsko brisanje?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> kaže"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Rekli ste"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Poruka pošiljatelja <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Poslali ste poruku"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Slanje…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nije poslano. Dodirnite za ponovni pokušaj."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nije poslano. Pokušavamo ponovo…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Ponovno slanje ili brisanje"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Nazovite hitnu službu. Vaša tekstna poruka nije isporučena."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Nije poslano"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nova MMS poruka za preuzimanje"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nova MMS poruka"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Preuzimanje nije moguće"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Dodirnite za ponovni pokušaj"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Dodirnite za preuzimanje"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Preuzimanje ili brisanje"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Preuzimanje..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Poruka je istekla ili nije dostupna"</string>
+ <string name="mms_info" msgid="3402311750134118165">"veličina: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, istek: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nije poslano. Primatelj nije važeći."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Usluga nije aktivirana na mreži"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Slanje nije uspjelo zbog problema s mrežom"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Poruka je istekla ili nije dostupna"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Bez predmeta)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Nepoznati pošiljatelj"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Isporučeno"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Preuzimanje poruke <xliff:g id="SUBJECT">%1$s</xliff:g> od pošiljatelja <xliff:g id="FROM">%2$s</xliff:g> nije uspjelo."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Radnja baze podataka nije dovršena zbog nedostatka memorije"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Poruka nije poslana"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Neke poruke nisu poslane u Slanju poruka"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> poruka u <xliff:g id="CONVERSATIONS">%d</xliff:g> razgovoru</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> poruke u <xliff:g id="CONVERSATIONS">%d</xliff:g> razgovora</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> poruka u <xliff:g id="CONVERSATIONS">%d</xliff:g> razgovora</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Poruka nije preuzeta"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Neke poruke nisu preuzete u Slanje poruka"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> poruka u <xliff:g id="CONVERSATIONS">%d</xliff:g> razgovoru</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> poruke u <xliff:g id="CONVERSATIONS">%d</xliff:g> razgovora</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> poruka u <xliff:g id="CONVERSATIONS">%d</xliff:g> razgovora</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Poruka na broj <xliff:g id="NUMBER">%1$s</xliff:g> nije poslana"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Nazovite hitnu službu. Vaša tekstna poruka na broj <xliff:g id="NUMBER">%1$s</xliff:g> nije isporučena."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> nova poruka</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> nove poruke</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> novih poruka</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Započni"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Fotoaparat nije dostupan"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Fotoaparat nije dostupan"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Snimanje videozapisa nije dostupno"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Medij nije spremljen"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nije moguće snimiti sliku"</string>
+ <string name="back" msgid="1477626055115561645">"Natrag"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arhivirano"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Izbriši"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arhiviraj"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Poništi arhiviranje"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Isključi obavijesti"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Uključi obavijesti"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Dodaj kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Preuzmi"</string>
+ <string name="action_send" msgid="377635240181672039">"Pošalji"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Izbriši"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Želite li izbrisati tu poruku?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Tu radnju nije moguće poništiti."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Izbriši"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Izbrisati razgovore?</item>
+ <item quantity="few">Izbrisati razgovore?</item>
+ <item quantity="other">Izbrisati razgovore?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Izbriši"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Odustani"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Odredište"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Odaberi više slika"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Potvrdite odabir"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Audioporuka nije snimljena. Pokušajte ponovo."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Audioporuku nije moguće reproducirati. Pokušajte ponovo."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Audioporuka nije spremljena. Pokušajte ponovo."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Dodirnite i zadržite"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Slika"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audioisječak"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Videozapis"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kartica kontakta"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Preuzmi"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Odgovorite putem SMS-a"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Odgovor MMS-om"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Odgovorite"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> sudionik</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> sudionika</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> sudionika</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ja"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt je blokiran i arhiviran"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt deblokiran i dearhiviran"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Arhivirano: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Poništena arhiviranja: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Obavijesti isključene"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Obavijesti uključene"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Sve je postavljeno. Ponovo dodirnite Pošalji."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Slanje poruka uspješno je postavljeno kao zadana aplikacija za SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Odbacivanje privitaka</item>
+ <item quantity="few">Odbacivanje privitaka</item>
+ <item quantity="other">Odbacivanje privitaka</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audioprivitak"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reproduciraj audioprivitak"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pauziraj"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Poruka pošiljatelja <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Poruka pošiljatelja <xliff:g id="SENDER">%s</xliff:g> nije uspjela: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Poruka pošiljatelja <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Poruka za kontakt <xliff:g id="CONTACT">%s</xliff:g> nije poslana: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Slanje poruke kontaktu <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Poruka za kontakt <xliff:g id="CONTACT">%s</xliff:g> nije uspjela: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Poruka kontaktu <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Neuspjela poruka pošiljatelja <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Poruka pošiljatelja <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Poruka za grupu <xliff:g id="GROUP">%s</xliff:g> nije poslana: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Slanje poruke grupi <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Poruka za grupu <xliff:g id="GROUP">%s</xliff:g> nije uspjela: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Poruka grupi <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Vrijeme: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Poruka nije uspjela. Dodirnite za ponovni pokušaj."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Razgovor sa sljedećima: <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Brisanje predmeta"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Snimi videozapis"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Snimi fotografiju"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Snimi sliku"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Početak snimanja videozapisa"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Prebacivanje na fotoaparat na punom zaslonu"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Prebacivanje između prednjeg i stražnjeg fotoaparata"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Zaustavi snimanje i priloži videozapis"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Zaustavi snimanje videozapisa"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotografije Slanja poruka"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografija spremljena je u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografije spremljene su u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografija spremljeno je u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> videozapis spremljen je u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> videozapisa spremljena su u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videozapisa spremljeno je u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> privitak spremljen je u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> privitka spremljena su u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> privitaka spremljeno je u album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> privitak spremljen je u \"Preuzimanja\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> privitka spremljena su u \"Preuzimanja\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> privitaka spremljeno je u \"Preuzimanja\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> privitak spremljen</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> privitka spremljena</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> privitaka spremljeno</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Spremanje <xliff:g id="QUANTITY_1">%d</xliff:g> privitka nije uspjelo</item>
+ <item quantity="few">Spremanje <xliff:g id="QUANTITY_1">%d</xliff:g> privitaka nije uspjelo</item>
+ <item quantity="other">Spremanje <xliff:g id="QUANTITY_1">%d</xliff:g> privitaka nije uspjelo</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS privitak spremljen"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Postavke"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arhivirano"</string>
+ <string name="action_close" msgid="1840519376200478419">"Zatvori"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Napredno"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Otklanjanje pogrešaka"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Obavijesti"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Zvuk"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Bešumno"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibriranje"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokirano"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Izvješća o isporuci SMS-a"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Zahtijevanje izvješća o isporuci za svaki poslani SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatski dohvat"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automatsko dohvaćanje MMS-ova"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automatski dohvati u roamingu"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatsko dohvaćanje MMS-ova u roamingu"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Grupne poruke"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Slanje jedne poruke za više primatelja pomoću MMS-a"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Zadana aplikacija za SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Zadana aplikacija za SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Vaš telefonski broj"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Nepoznato"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Zvukovi odlazne poruke"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Sigurnosno kopiraj SMS-ove"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Sigurnosno kopiranje neobrađenih podataka primljenih SMS-ova u datoteku vanjske pohrane"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Sigurnosno kopiraj MMS-ove"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Sigurnosno kopiranje neobrađenih podataka primljenih MMS-ova u datoteku vanjske pohrane"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Upozorenja putem mobilne mreže"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opcije poruka"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopiraj tekst"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Prikaz pojedinosti"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Izbriši"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Proslijedi"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Pojedinosti poruke"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Vrsta: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS poruka"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedijska poruka"</string>
+ <string name="from_label" msgid="1947831848146564875">"Šalje: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Prima: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Poslano: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Primljeno: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Predmet: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Veličina: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Visok"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Uobičajeni"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Niski"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Skrivena adresa pošiljatelja"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Slanje poruke nije moguće tijekom učitavanja privitaka."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Privitak nije učitan. Pokušajte ponovo."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Mreža nije spremna. Pokušajte ponovo."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Brisanje teksta"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Prebacivanje između unosa teksta i brojeva"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Dodaj još sudionika"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Potvrdi sudionike"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Započnite novi razgovor"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Odaberite ovu stavku"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reproduciraj videozapis"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Osobe i opcije"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Otkloni pogreške"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Osobe i opcije"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Općenito"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Osobe u ovom razgovoru"</string>
+ <string name="action_call" msgid="6596167921517350362">"Uputite poziv"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Pošaljite poruku"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Pošalji poruku&lt;br/&gt;&lt;small&gt;sa SIM kartice <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Pošaljite fotografije</item>
+ <item quantity="few">Pošaljite fotografije</item>
+ <item quantity="other">Pošaljite fotografije</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Pošaljite audiozapise</item>
+ <item quantity="few">Pošaljite audiozapise</item>
+ <item quantity="other">Pošaljite audiozapise</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Pošaljite videozapise</item>
+ <item quantity="few">Pošaljite videozapise</item>
+ <item quantity="other">Pošaljite videozapise</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Pošaljite kartice kontakata</item>
+ <item quantity="few">Pošaljite kartice kontakata</item>
+ <item quantity="other">Pošaljite kartice kontakata</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Pošaljite privitke</item>
+ <item quantity="few">Pošaljite privitke</item>
+ <item quantity="other">Pošaljite privitke</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> privitak spreman je za slanje</item>
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> privitka spremna su za slanje</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> privitaka spremno je za slanje</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Slanje povratnih informacija"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Prikaži u Trgovini Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informacije o verziji"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Verzija %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licence otvorenog koda"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Obavijesti"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Dosegnuto je ograničenje za privitke"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Učitavanje privitka nije uspjelo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Želite li dodati u Kontakte?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Dodavanje kontakta"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Predmet"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Predmet: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Učitavanje kartice kontakta"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kartica kontakta nije učitana"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Prikaz kartice kontakta"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kontakt</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontakta</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakata</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kartice kontakata"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Rođendan"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Bilješke"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Proslijedi poruku"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Odgovori"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS je onemogućen"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Postavite Slanje poruka kao zadanu aplikaciju za SMS da biste slali poruke"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Postavite Slanje poruka kao zadanu aplikaciju za SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Promijeni"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Postavite Slanje poruka kao zadanu aplikaciju za SMS da biste primali poruke"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nije odabran preferirani SIM za slanje SMS poruka"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Vlasnik uređaja nije odobrio upotrebu te aplikacije."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"U redu"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Previše sudionika u razgovoru"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Nevažeći kontakti</item>
+ <item quantity="few">Nevažeći kontakti</item>
+ <item quantity="other">Nevažeći kontakti</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Slika nije učitana"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Vi: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Skica"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Ovdje će biti naveden novi razgovor kada ga pokrenete"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arhivirani razgovori prikazuju se ovdje"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Učitavanje razgovora..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Slika"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audioisječak"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Videozapis"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kartica kontakta"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Poništi"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Pokušaj ponovo"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Unesite ime kontakta ili telefonski broj da biste započeli novu poruku"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokiraj"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokiranje <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Deblokiraj <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blokirati <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"I dalje ćete primati poruke s tog broja, ali više nećete primati obavijesti o tome. Razgovor će se arhivirati."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokirani kontakti"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DEBLOKIRAJ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokirani kontakti"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Odaberite sliku iz knjižnice dokumenata"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Slanje poruke"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Poruka je poslana"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobilni su podaci isključeni. Provjerite postavke."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Ne možete slati poruke u načinu rada u zrakoplovu"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Slanje poruke nije uspjelo"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Poruka preuzeta"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobilni su podaci isključeni. Provjerite postavke."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Nije moguće preuzimanje poruka u načinu rada u zrakoplovu"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Preuzimanje poruke nije uspjelo"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nula"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Jedan"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dva"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tri"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Četiri"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Pet"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Šest"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sedam"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Osam"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Devet"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Poruka nije poslana putem operatera <xliff:g id="CARRIERNAME">%1$s</xliff:g>, pogreška <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Poruka nije poslana putem nepoznatog mobilnog operatera, pogreška <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Poruka nije poslana jer usluga nije aktivirana na mreži"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Poruka nije poslana jer odredišna adresa nije važeća"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Poruka nije poslana jer nije važeća"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Poruka nije poslana jer sadržaj nije podržan"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Poruka nije poslana jer nije podržana"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Poruka nije poslana jer je prevelika"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nova poruka"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Prikaz"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Slika"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nije pronađena odgovarajuća aplikacija"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Uklanjanje primatelja"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nova poruka"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Odustani"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Uređivanje pristupne točke"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nije postavljeno"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Naziv"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS priključak"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Vrsta APN-a"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Izbriši APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Novi APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Spremi"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Odbaci"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Polje naziva ne može biti prazno."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ne može biti prazan."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC polje mora imati 3 znamenke."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Polje MNC mora imati 2 ili 3 znamenke."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Vraćaju se zadane postavke APN-a."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Vrati na zadano"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Dovršeno je vraćanje zadanih postavki APN-a."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Bez naziva"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nazivi pristupnih točaka"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-ovi"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Novi APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Postavke naziva pristupne točke nisu dostupne ovom korisniku"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopirati u međuspremnik?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiraj"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"na <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Općenito"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Napredno"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Opće postavke"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Napredne postavke"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Slanje pojedinačnih SMS poruka svim primateljima. Eventualne odgovore dobit ćete samo vi"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Slanje pojedinačnog MMS-a svim primateljima"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nepoznati broj"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nova poruka"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nova poruka."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Gumb za odabir SIM-a"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> odabran, gumb za odabir SIM-a"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Uredi predmet"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Odabir SIM-a ili uređivanje predmeta"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Dodirnite i zadržite da biste snimili zvuk"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Započnite novi razgovor"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Slanje poruka"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Popis Slanja poruka"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Slanje poruka"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nova poruka"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Popis razgovora"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Učitavanje razgovora"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Učitavanje poruka"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Prikaži više razgovora"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Prikaži više poruka"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Razgovor je izbrisan"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Razgovor izbrisan. Dodirnite za prikaz drugog razgovora iz Slanja poruka"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blokirano"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Deblokirano"</string>
+ <string name="db_full" msgid="8459265782521418031">"Nedostaje prostora za pohranu. Neki se podaci mogu izgubiti."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Odaberite privitke"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Potvrdite odabir"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Odabrano: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Uklonite jedan ili više privitaka i pokušajte ponovo."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Možete pokušati poslati poruku, ali možda neće biti dostavljena ako ne uklonite jedan ili više privitaka."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Možete poslati samo jedan videozapis u poruci. Uklonite dodatne videozapise i pokušajte ponovo."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Slanje poruka nije uspjelo učitati privitak."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Svejedno pošalji"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Pokretanje razgovora nije uspjelo"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Odabrani SIM: <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-hu/arrays.xml b/res/values-hu/arrays.xml
new file mode 100644
index 0000000..36c0cd0
--- /dev/null
+++ b/res/values-hu/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"nincs tárgy"</item>
+ <item msgid="272485471009191934">"nincs tárgy"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Igen"</item>
+ <item msgid="6049132459802288033">"Nem"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Haha!"</item>
+ <item msgid="2611328818571146775">"Köszönöm!"</item>
+ <item msgid="4881335087096496747">"Egyetértek"</item>
+ <item msgid="2422296858597420738">"Ügyes"</item>
+ <item msgid="4805581752819452687">"Úton vagyok"</item>
+ <item msgid="4746700499431366214">"Rendben, később jelentkezem"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..cd254f2
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Üzenetváltás"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Üzenetváltás"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Beszélgetés kijelölése"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Beállítások"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Üzenet küldése"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Melléklet hozzáadása"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Súgó"</string>
+ <string name="welcome" msgid="2857560951820802321">"Üdvözöljük!"</string>
+ <string name="skip" msgid="7238879696319945853">"Kihagyás"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Következő &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Tovább"</string>
+ <string name="exit" msgid="1905187380359981199">"Kilépés"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Beállítások &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Beállítások"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"A Messaging engedélyt kér a következőkhöz: SMS, telefon és névjegyek."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Az engedélyeket a Beállítások &gt; Alkalmazások &gt; Messaging &gt; Engedélyek lehetőségnél módosíthatja."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Az engedélyeket a Beállítások, Alkalmazások, Messaging, Engedélyek lehetőségnél módosíthatja."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Gyakran használt névjegyek"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Az összes névjegy"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Küldés: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Kép vagy videó rögzítése"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Képek kiválasztása erről az eszközről"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Hanganyag rögzítése"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Fotó kiválasztása"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Kijelölte a médiaelemet."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Visszavonta a médiaelem kijelölését."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> kiválasztva"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"kép <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" msgid="8142175029053370662">"kép"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Hanganyag rögzítése"</string>
+ <string name="action_share" msgid="2143483844803153871">"Megosztás"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Éppen most"</string>
+ <string name="posted_now" msgid="867560789350406701">"Most"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> perce</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> perce</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> órája</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> órája</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> napja</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> napja</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> hét</item>
+ <item quantity="one">egy hét</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> hónap</item>
+ <item quantity="one">egy hónap</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> év</item>
+ <item quantity="one">egy év</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0. osztályú üzenet"</string>
+ <string name="save" msgid="5081141452059463572">"Mentés"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Az eszközön kevés a tárhely. A Messaging automatikusan törli a régebbi üzeneteket tárhely felszabadítása érdekében."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Kevés a szabad terület"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"A Messaging nem küldhet vagy fogadhat üzenetet, amíg nem áll rendelkezésre több hely az eszközön."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Kevés a tárhely az SMS-eknek. Lehet, hogy üzeneteket kell törölnie."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Telefonszám megerősítése"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Ez az egyszeri lépés biztosítja, hogy a Messaging megfelelően továbbítsa a csoportos üzeneteket."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonszám"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Az összes médiatartalommal bíró üzenet törlése"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"A(z) <xliff:g id="DURATION">%s</xliff:g> időtartamnál régebbi üzenetek törlése"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"A(z) <xliff:g id="DURATION">%s</xliff:g> időtartamnál régebbi üzenetek automatikus törlése"</string>
+ <string name="ignore" msgid="7063392681130898793">"Mellőzés"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Törli az összes, médiát tartalmazó üzenetet?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Törli a(z) <xliff:g id="DURATION">%s</xliff:g> időtartamnál régebbi üzeneteket?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Törli a(z) <xliff:g id="DURATION">%s</xliff:g> időtartamnál régebbi üzeneteket, és bekapcsolja az automatikus törlést?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> a következőt mondta:"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Ön a következőt mondta"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> üzenete:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Ön elküldött egy üzenetet"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Küldés…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nem lehetett elküldeni. Érintse meg az újbóli próbálkozáshoz."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nem lehetett elküldeni. Újabb próbálkozás…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Újraküldés vagy törlés"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Kérjük, tárcsázza a segélyhívót. A szöveges üzenetet jelenleg nem tudjuk elküldeni."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Sikertelen"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Új letölthető MMS üzenet"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Új MMS üzenet"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nem sikerült letölteni"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Érintse meg az újabb próbálkozáshoz"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Érintse meg a letöltéshez"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Letöltés vagy törlés"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Letöltés…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Az üzenet vagy lejárt, vagy nem érhető el."</string>
+ <string name="mms_info" msgid="3402311750134118165">"méret: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, lejárat: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nem lehetett elküldeni. A címzett érvénytelen."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"A szolgáltatás nincs aktiválva a hálózaton"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Hálózati probléma miatt nem lehetett elküldeni"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Az üzenet vagy lejárt, vagy nem érhető el."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Nincs tárgy)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Ismeretlen feladó"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Kézbesítve"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nem sikerült letölteni <xliff:g id="FROM">%2$s</xliff:g> „<xliff:g id="SUBJECT">%1$s</xliff:g>” tárgyú levelét."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Kevés a memória, ezért nem sikerült végrehajtani a műveletet az adatbázison"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"A rendszer nem küldte el az üzenetet"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"A Messaging egyes üzeneteit nem lehetett elküldeni"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> üzenet <xliff:g id="CONVERSATIONS">%d</xliff:g> beszélgetésben</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> üzenet egy beszélgetésben</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Az üzenet nincs letöltve"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"A Messaging egyes üzeneteit nem lehetett letölteni"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> üzenet <xliff:g id="CONVERSATIONS">%d</xliff:g> beszélgetésben</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> üzenet egy beszélgetésben</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Nem sikerült elküldeni az üzenetet a(z) <xliff:g id="NUMBER">%1$s</xliff:g> számra"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Kérjük, tárcsázza a segélyhívót. A szöveges üzenetet jelenleg nem tudjuk elküldeni erre a számra: <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> új üzenet</item>
+ <item quantity="one">Új üzenet</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Indítás"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"A kamera nem érhető el"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"A kamera nem érhető el"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"A videorögzítés nem érhető el"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Nem lehetett menteni a médiát"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nem lehet képet készíteni"</string>
+ <string name="back" msgid="1477626055115561645">"Vissza"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archiválva"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Törlés"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archiválás"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Archiválás visszavonása"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Értesítések kikapcsolása"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Értesítések bekapcsolása"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Névjegy hozzáadása"</string>
+ <string name="action_download" msgid="7786338136368564146">"Letöltés"</string>
+ <string name="action_send" msgid="377635240181672039">"Küldés"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Törlés"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Törli ezt az üzenetet?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Ez a művelet nem vonható vissza."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Törlés"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Törli ezeket a beszélgetéseket?</item>
+ <item quantity="one">Törli ezt a beszélgetést?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Törlés"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Mégse"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Címzett"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Több kép kiválasztása"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Kiválasztás megerősítése"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Nem sikerült rögzíteni a hangot. Próbálja újra."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"A hanganyag nem játszható le. Próbálja újra."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Nem sikerült elmenteni a hangot. Próbálja újra."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Érintés és tartás"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Kép"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Hangfelvétel"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Videó"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Névjegykártya"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Letöltés"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Válasz SMS-ben"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Válasz MMS-ben"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Válasz"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> résztvevő</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> résztvevő</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Én"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Ismerős letiltva és archiválva"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Névjegy tiltása és archiválása visszavonva"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archiválva"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> archiválása megszüntetve"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Értesítések kikapcsolva"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Értesítések bekapcsolva"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Minden készen áll. Érintse meg újra a Küldés lehetőséget."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"A Messaging sikeresen beállítva alapértelmezett SMS-alkalmazásként."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Mellékletek elvetése</item>
+ <item quantity="one">Melléklet elvetése</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Hangmelléklet"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Hangmelléklet lejátszása"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Szünet"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> üzenete: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Sikertelen üzenet <xliff:g id="SENDER">%s</xliff:g> feladótól: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Üzenet <xliff:g id="SENDER">%s</xliff:g> feladótól: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Elküldetlen üzenet a(z) <xliff:g id="CONTACT">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Üzenet küldése a(z) <xliff:g id="CONTACT">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Sikertelen üzenet a(z) <xliff:g id="CONTACT">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Üzenet a(z) <xliff:g id="CONTACT">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Sikertelen üzenet <xliff:g id="SENDER">%s</xliff:g> feladótól: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Üzenet <xliff:g id="SENDER">%s</xliff:g> feladótól: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Elküldetlen üzenet a(z) <xliff:g id="GROUP">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Üzenet küldése a(z) <xliff:g id="GROUP">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Sikertelen üzenet a(z) <xliff:g id="GROUP">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Üzenet a(z) <xliff:g id="GROUP">%s</xliff:g> csoportnak: <xliff:g id="MESSAGE">%s</xliff:g>. Idő: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Sikertelen üzenet. Érintse meg az újrapróbáláshoz."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Beszélgetés a következőkkel: <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Tárgy törlése"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Videó rögzítése"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Állókép rögzítése"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Kép készítése"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Videofelvétel megkezdése"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Váltás teljes képernyős kameranézetre"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Váltás az elülső és a hátulsó kamera között"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Rögzítés leállítása és a videó csatolása"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Videofelvétel leállítása"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging-fotók"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotó mentve a következő albumba: <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> fotó mentve a következő albumba: <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videó mentve a következő albumba: <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> videó mentve a következő albumba: <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> melléklet mentve a következő albumba: <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> melléklet mentve a következő albumba: <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> melléklet mentve a „Letöltések” közé</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> melléklet mentve a „Letöltések” közé</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> melléklet sikeresen mentve</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> melléklet sikeresen mentve</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> melléklet mentése nem sikerült</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> melléklet mentése nem sikerült</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Mentett MMS-melléklet"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Beállítások"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archiválva"</string>
+ <string name="action_close" msgid="1840519376200478419">"Bezárás"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Speciális"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Hibakeresés"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Értesítések"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Hang"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Néma"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Rezgés"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Letiltva"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-kézbesítési jelentések"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Kézbesítési jelentés kérése minden elküldött SMS-ről"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatikus letöltés"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS-üzenetek automatikus letöltése"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Auto. letöltés barangoláskor"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"MMS-ek automatikus lekérése adatbarangoláskor"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Csoportos üzenetküldés"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Küldjön MMS-t, amikor több címzettnek szeretne üzenetet küldeni."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Alapértelmezett SMS-alkalmazás"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Alapértelmezett SMS-alkalmazás"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Az Ön telefonszáma"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Ismeretlen"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Kimenő üzenetek hangja"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS kiírása"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Beérkező nyers SMS-adatok kiírása külső tárolófájlba"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS kiírása"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Beérkező nyers MMS-adatok kiírása külső tárolófájlba"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Vezeték nélküli értesítések"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Üzenetbeállítások"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Szöveg másolása"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Részletek megjelenítése"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Törlés"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Továbbítás"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Üzenet részletei"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Típus: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Szöveges üzenet"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimédiás üzenet"</string>
+ <string name="from_label" msgid="1947831848146564875">"Küldő: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Címzett: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Elküldve: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Érkezett: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Tárgy: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Méret: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritás: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Magas"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normál"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Alacsony"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"<xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>. SIM"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"A küldő címe elrejtve"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"A mellékletek betöltése közben nem lehet elküldeni az üzenetet."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nem lehetett betölteni a mellékletet. Próbálja újra."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"A hálózat nem áll készen. Próbálja újra."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Szöveg törlése"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Váltás szöveg és számok bevitele között"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"További résztvevők hozzáadása"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Résztvevők megerősítése"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Új beszélgetés indítása"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Elem kiválasztása"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Videó lejátszása"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Személyek és beállítások"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Hibakeresés"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Személyek és beállítások"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Általános"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"A jelen beszélgetésben részt vevő személyek"</string>
+ <string name="action_call" msgid="6596167921517350362">"Hívásindítás"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Üzenet küldése"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Üzenet küldése&lt;br/&gt;&lt;small&gt;a következő SIM kártyáról: <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Fotók küldése</item>
+ <item quantity="one">Fotó küldése</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Hangok küldése</item>
+ <item quantity="one">Hang küldése</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Videók küldése</item>
+ <item quantity="one">Videó küldése</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Névjegykártyák küldése</item>
+ <item quantity="one">Névjegykártya küldése</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Mellékletek küldése</item>
+ <item quantity="one">Melléklet küldése</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> melléklet készen áll a küldésre</item>
+ <item quantity="one">Egy melléklet készen áll a küldésre</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Visszajelzés küldése"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Megtekintés a Google Play Áruházban"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Verzióadatok"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Verzió: %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Nyílt forráskódú licencek"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Értesítések"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Elérte a mellékletekre vonatkozó korlátot"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Nem sikerült betölteni a mellékletet."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Hozzáadja a Névjegyekhez?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Névjegy hozzáadása"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Tárgy"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Tárgy: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Névjegykártya betöltése"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Nem lehetett betölteni a névjegykártyát"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Névjegykártya megtekintése"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> névjegy</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> névjegy</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Névjegykártyák"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Születésnap"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Jegyzetek"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Üzenet továbbítása"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Válasz"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS-ek letiltva"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"A küldéshez állítsa be, hogy a Messaging legyen az alapértelmezett SMS-alkalmazás"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"A Messaging beállítása alapértelmezett SMS-alkalmazásként"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Módosítás"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Üzenetek fogadásához állítsa be, hogy a Messaging legyen az alapértelmezett SMS-alkalmazás"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nincs SIM kiválasztva az SMS-küldéshez"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Az eszköz tulajdonosa nem engedélyezi ezt az alkalmazást."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Túl sok a résztvevő a beszélgetésben"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Érvénytelen névjegy</item>
+ <item quantity="one">Érvénytelen névjegy</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Nem lehetett betölteni a kamera képét"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ön: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Piszkozat"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Amikor új beszélgetésbe kezd, az itt jelenik meg"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Itt jelennek meg az archivált beszélgetések"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Beszélgetések betöltése…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Kép"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Hangfelvétel"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Videó"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Névjegykártya"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Visszavonás"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Újra"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Új üzenet létrehozásához adjon meg egy névjegyet vagy telefonszámot"</string>
+ <string name="action_block" msgid="9032076625645190136">"Tiltás"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> letiltása"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> feloldása"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Letiltja ezt: <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Továbbra is kap üzeneteket erről a számról, ám a rendszer nem küld róluk értesítést. A beszélgetést a rendszer archiválja."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Letiltott ismerősök"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"FELOLD"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Letiltott ismerősök"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Kép választása a dokumentumtárból"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Üzenet küldése"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Üzenet elküldve"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"A mobiladat-kapcsolat ki van kapcsolva. Ellenőrizze beállításait."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Repülős módban nem lehet üzeneteket küldeni"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Az üzenetet nem sikerült elküldeni"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Üzenet letöltve"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"A mobiladat-kapcsolat ki van kapcsolva. Ellenőrizze beállításait."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Repülős üzemmódban nem lehet üzenetet letölteni"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Az üzenetet nem sikerült letölteni"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nulla"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Egy"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Kettő"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Három"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Négy"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Öt"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Hat"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Hét"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Nyolc"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Kilenc"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Nem lehet elküldeni az üzenetet a következő szolgáltatónál: <xliff:g id="CARRIERNAME">%1$s</xliff:g> (hibakód: <xliff:g id="ERRORCODE">%2$d</xliff:g>)"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Nem lehet elküldeni az üzenetet egy ismeretlen szolgáltatónál (hibakód: <xliff:g id="ERRORCODE">%1$d</xliff:g>)"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Továbbítás: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Az üzenet nincs elküldve: a szolgáltatás nincs aktiválva a hálózaton"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Az üzenet küldése sikertelen: a címzett címe érvénytelen"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Az üzenet küldése sikertelen: érvénytelen üzenet"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Az üzenet küldése sikertelen: nem támogatott tartalom"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Az üzenet küldése sikertelen: nem támogatott üzenet"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Az üzenet nincs elküldve: túl nagy"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Új üzenet"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Megtekintés"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Kép"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nem található megfelelő alkalmazás"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Címzett eltávolítása"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Új üzenet"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Mégse"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Hozzáférési pont szerkesztése"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nincs megadva"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Név"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN típusa"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN törlése"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Új APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Mentés"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Elvetés"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"A Név mező nem lehet üres."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Az APN nem lehet üres."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Az MCC mezőnek három számjegyet kell tartalmaznia."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Az MNC mezőbe 2 vagy 3 számjegyet kell írni."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Alapértelmezett APN-beállítások visszaállítása."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Visszaállítás alaphelyzetbe"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Az alapértelmezett APN-beállítások visszaállítása befejeződött."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Névtelen"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Hozzáférési pontok nevei"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-ek"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Új APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"A hozzáférési pont nevének beállításai nem érhetők el ennél a felhasználónál"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Átmásolja a vágólapra?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Másolás"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"fogadás a következőn: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Általános"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Speciális"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Általános beállítások"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Speciális beállítások"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"„<xliff:g id="SIM_NAME">%s</xliff:g>” SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Külön SMS küldése valamennyi címzettnek. Csak Ön kap választ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Egyetlen MMS küldése valamennyi címzettnek"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Ismeretlen szám"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Új üzenet"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Új üzenet"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM kártya kiválasztása"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"A(z) <xliff:g id="SIM_0">%1$s</xliff:g> SIM kártya kiválasztva (SIM-választó)"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Tárgy szerkesztése"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM kártya kiválasztása vagy a tárgy szerkesztése"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Hangfelvétel készítéséhez tartsa nyomva"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Új beszélgetés indítása"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Messaging-lista"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Új üzenet"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Beszélgetéslista"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Beszélgetések betöltése"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Üzenetek betöltése"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"További beszélgetések megtekintése"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"További üzenetek megtekintése"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Beszélgetés törölve"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Beszélgetés törölve. Másik Messaging-beszélgetés megjelenítéséhez érintse meg."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Letiltva"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Letiltás visszavonva"</string>
+ <string name="db_full" msgid="8459265782521418031">"Kevés a tárhely. Lehet, hogy bizonyos adatok elvesztek."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Mellékletek kiválasztása"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Kiválasztás megerősítése"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> kiválasztva"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Távolítson el legalább egy mellékletet, majd próbálja újra."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Megpróbálhatja elküldeni az üzenetet, de előfordulhat, hogy az csak akkor lesz kézbesíthető, ha legalább egy mellékletet eltávolít."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Üzenetenként csak egy videót lehet küldeni. Távolítsa el a többi videót, és próbálja újra."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"A Messaging nem tudta betölteni a mellékletet."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Küldés mindenképpen"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Nem sikerült elindítani a beszélgetést"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> kiválasztva"</string>
+</resources>
diff --git a/res/values-hy-rAM/arrays.xml b/res/values-hy-rAM/arrays.xml
new file mode 100644
index 0000000..d520d07
--- /dev/null
+++ b/res/values-hy-rAM/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"անվերնագիր"</item>
+ <item msgid="272485471009191934">"անվերնագիր"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Այո"</item>
+ <item msgid="6049132459802288033">"Ոչ"</item>
+ <item msgid="3084376867445867895">"Լավ"</item>
+ <item msgid="3155097332660174689">"Հե-հե"</item>
+ <item msgid="2611328818571146775">"Շնորհակալություն"</item>
+ <item msgid="4881335087096496747">"Համաձայն եմ"</item>
+ <item msgid="2422296858597420738">"Գերազանց է"</item>
+ <item msgid="4805581752819452687">"Ճանապարհին եմ"</item>
+ <item msgid="4746700499431366214">"Լավ, կմիանամ ձեզ ավելի ուշ"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..4d01fef
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Հաղորդակցում"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Հաղորդակցում"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Ընտրեք խոսակցությունը"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Կարգավորումներ"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Ուղարկել հաղորդագրություն"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Ավելացնել կցորդ"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Օգնություն"</string>
+ <string name="welcome" msgid="2857560951820802321">"Բարի գալուստ"</string>
+ <string name="skip" msgid="7238879696319945853">"Բաց թողնել"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Հաջորդը &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Հաջորդը"</string>
+ <string name="exit" msgid="1905187380359981199">"Ելք"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Կարգավորումներ &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Կարգավորումներ"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Հաղորդակցում հավելվածին հարկավոր է թույլտվություն SMS-ներին, հեռախոսին ու կոնտակտներին:"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Կարող եք փոխել թույլտվությունները՝ անցնելով Կարգավորումներ &gt; Հավելվածներ &gt; Հաղորդակցում &gt; Թույլտվություններ:"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Կարող եք փոխել թույլտվությունները՝ անցնելով Կարգավորումներ, Հավելվածներ, Հաղորդակցում, Թույլտվություններ:"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Հաճախակի օգտագործվող կոնտակտներ"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Բոլոր կոնտակտները"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Ուղարկել <xliff:g id="DESTINATION">%s</xliff:g> համարին"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Լուսանկարել կամ տեսագրել"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Ընտրեք պատկերներ այս սարքից"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Ձայնագրել ձայնանյութ"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Ընտրել լուսանկար"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Մեդիա ֆայլն ընտրված է:"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Մեդիա ֆայլն ապընտրված է:"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ընտրված"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"պատկեր <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" msgid="8142175029053370662">"պատկեր"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Ձայնագրել ձայնանյութ"</string>
+ <string name="action_share" msgid="2143483844803153871">"Տարածել"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Հենց հիմա"</string>
+ <string name="posted_now" msgid="867560789350406701">"Այժմ"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> րոպե</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> րոպե</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ժամ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ժամ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> օր</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> օր</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> շաբաթ</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> շաբաթ</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ամիս</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ամիս</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> տարի</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> տարի</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Class 0 հաղորդագրություն"</string>
+ <string name="save" msgid="5081141452059463572">"Պահել"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Սարքի վրա բավարար ազատ տարածք չկա: Հաղորդակցում հավելվածը ինքնաշխատորեն կջնջի հին հաղորդագրությունները, որպեսզի տեղ ազատի:"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Պահոցային տարածքը սպառվում է"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Հաղորդակցումը չի կարող ուղարկել կամ ստանալ հաղորդագրություններ, քանի դեռ սարքում բավարար տարածք չկա:"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS պահոցի ծավալը փոքր է: Հնարավոր է՝ հաղորդագրություններ ջնջելու կարիք լինի:"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Հաստատեք ձեր հեռախոսահամարը"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Սա մեկանգամյա քայլ է, որի շնորհիվ Հաղորդակցումը անսխալ կուղարկի ձեր խմբային հաղորդագրությունները:"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Հեռախոսահամար"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Ջնջել մեդիաֆայլեր պարունակող բոլոր հաղորդագրությունները"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Ջնջել <xliff:g id="DURATION">%s</xliff:g>-ից հին հաղորդագրությունները"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Ավտոմատ ջնջել <xliff:g id="DURATION">%s</xliff:g>-ից հին հաղորդագրությունները"</string>
+ <string name="ignore" msgid="7063392681130898793">"Անտեսել"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Ջնջե՞լ մեդիա պարունակող բոլոր հաղորդագրությունները:"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Ջնջե՞լ <xliff:g id="DURATION">%s</xliff:g>-ից հին հաղորդագրությունները:"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Ջնջե՞լ <xliff:g id="DURATION">%s</xliff:g>-ից հին հաղորդագրությունները և միացնել ավտոմատ հեռացումը:"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g>-ը ասել է"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Դուք ասել եք"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Հաղորդագրություն <xliff:g id="SENDER">%s</xliff:g>-ից"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Դուք հաղորդագրություն եք ուղարկել"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Ուղարկվում է…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Չի ուղարկվել: Հպեք՝ կրկին փորձելու համար:"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Չի ուղարկվել: Փորձում է կրկին…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Կրկին ուղարկել թե ջնջել"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Խնդրում ենք հեռախոսազանգ կատարել արտակարգ իրավիճակների ծառայություններին: Ձեր տեքստային հաղորդագրությունն այս պահին չի կարող առաքվել:"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Ձախողվեց"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Նոր MMS հաղորդագրություն՝ ներբեռնելու հաար"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Նոր MMS հաղորդագրություն"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Չհաջողվեց ներբեռնել"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Հպեք՝ նորից փորձելու համար"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Հպեք՝ ներբեռնելու համար"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Ներբեռնել թե ջնջել"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Ներբեռնում…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Հաղորդագրության ժամկետը սպառվել է կամ հասանելի չէ"</string>
+ <string name="mms_info" msgid="3402311750134118165">"չափը՝ <xliff:g id="MESSAGESIZE">%1$s</xliff:g> , պիտանիությունը՝ <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Հնարավոր չէ ուղարկել: Ստացողը վավեր չէ:"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Ծառայությունն ակտիվացված չէ ցանցում"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Հնարավոր չէ ուղարկել ցանցային խնդրի պատճառով"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Հաղորդագրության ժամկետը սպառվել է կամ հասանելի չէ"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Անվերնագիր)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Անհայտ ուղարկող"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Առաքված է"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Հնարավոր չէ ներբեռնել <xliff:g id="SUBJECT">%1$s</xliff:g> հաղորդագրությունը <xliff:g id="FROM">%2$s</xliff:g>-ից:"</string>
+ <string name="low_memory" msgid="5300743415198486619">"Չստացվեց ավարտել տվյալների շտեմարանի աշխատանքը՝ փոքր հիշողության պատճառով"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Հաղորդագրությունը չի ուղարկվել"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Հաղորդակցման որոշ հաղորդագրություններ չեն ուղարկվել"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> հաղորդագրություն <xliff:g id="CONVERSATIONS">%d</xliff:g> խոսակցությունում</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> հաղորդագրություն <xliff:g id="CONVERSATIONS">%d</xliff:g> խոսակցությունում</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Հաղորդագրությունը չի ներբեռնվել"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Որոշ հաղորդագրություններ չեն ներբեռնվել Հաղորդակցման մեջ"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> հաղորդագրություն <xliff:g id="CONVERSATIONS">%d</xliff:g> խոսակցությունում</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> հաղորդագրություն <xliff:g id="CONVERSATIONS">%d</xliff:g> խոսակցությունում</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Հաղորդագրությունը <xliff:g id="NUMBER">%1$s</xliff:g> համարին չի ուղարկվել"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Խնդրում ենք հեռախոսազանգ կատարել արտակարգ իրավիճակների ծառայություններին: Ձեր տեքստային հաղորդագրությունն այս պահին չի կարող առաքվել <xliff:g id="NUMBER">%1$s</xliff:g> համարին:"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> նոր հաղորդագրություն</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> նոր հաղորդագրություն</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Սկսել"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Խցիկն անհասանելի է"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Խցիկն անհասանելի է"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Տեսանյութը հասանելի չէ"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Հնարավոր չէ պահել մեդիա ֆայլը"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Չհաջողվեց լուսանկարել"</string>
+ <string name="back" msgid="1477626055115561645">"Հետ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Արխիվացված"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Ջնջել"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Արխիվ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Ապարխիվացնել"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Անջատել ծանուցումները"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Միացնել ծանուցումները"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Ավելացնել կոնտակտ"</string>
+ <string name="action_download" msgid="7786338136368564146">"Ներբեռնել"</string>
+ <string name="action_send" msgid="377635240181672039">"Ուղարկել"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Ջնջել"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Ջնջե՞լ այս հաղորդագրությունը"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Այս գործողությունը չի կարող հետարկվել:"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Ջնջել"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Ջնջե՞լ այս խոսակցությունները:</item>
+ <item quantity="other">Ջնջե՞լ այս խոսակցությունները:</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Ջնջել"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Չեղարկել"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Ստացող`"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Ընտրել մի քանի պատկեր"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Հաստատել ընտրությունը"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Հնարավոր չէ ձայնագրել: Կրկին փորձեք:"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Հնարավոր չէ նվագարկել աուդիո ֆայլը: Կրկին փորձեք:"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Հնարավոր չէր պահել աուդիո ֆայլը: Կրկին փորձեք:"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Հպել և պահել"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Նկար"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Ձայնահոլովակ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Տեսանյութ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Կոնտակտային քարտ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Ներբեռնել"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Պատասխանել SMS-ով"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Պատասխանել MMS-ով"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Պատասխանել"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> մասնակից</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> մասնակից</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ես"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Կոնտակտն արգելափակված և արխիվացված է"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Կոնտակտն արգելաբացվեց և ապաարխիվացվեց"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Արխիվացվեց <xliff:g id="COUNT">%d</xliff:g> խոսակցություն"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Ապաարխիվացվեց <xliff:g id="COUNT">%d</xliff:g> խոսակցություն"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Ծանուցումներն անջատվեցին"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Ծանուցումները միացվեցին"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Սահմանված է: Հպեք Ուղարկել կրկին:"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Հաղորդակցումը դարձավ SMS-ների կանխադրված հավելված:"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Հեռացնել կցորդները</item>
+ <item quantity="other">Հեռացնել կցորդները</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Կցված ձայնային ֆայլ"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Նվագարկել աուդիո կցորդը"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Դադարեցնել"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Հաղորդագրություն <xliff:g id="SENDER">%s</xliff:g>-ից՝ <xliff:g id="MESSAGE">%s</xliff:g>:"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Չառաքված հաղորդագրություն <xliff:g id="SENDER">%s</xliff:g>-ից՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Հաղորդագրություն <xliff:g id="SENDER">%s</xliff:g>-ից՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> կոնտակտին չուղարկված հաղորդագրություն՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Հաղորդագրության ուղարկում <xliff:g id="CONTACT">%s</xliff:g> կոնտակտին՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> կոնտակտին չառաքված հաղորդագրություն՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> կոնտակտին հասցեագրված հաղորդագրություն՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Չառաքված հաղորդագրություն <xliff:g id="SENDER">%s</xliff:g>-ից՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>: <xliff:g id="GROUPINFO">%s</xliff:g>:"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Հաղորդագրություն <xliff:g id="SENDER">%s</xliff:g>-ից՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>: <xliff:g id="GROUPINFO">%s</xliff:g>:"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> խմբին չուղարկված հաղորդագրություն՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Հաղորդագրության ուղարկում <xliff:g id="GROUP">%s</xliff:g> խմբին՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> խմբին չառաքված հաղորդագրություն՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> խմբին հասցեագրված հաղորդագրություն՝ <xliff:g id="MESSAGE">%s</xliff:g>: Ժամանակ՝ <xliff:g id="TIME">%s</xliff:g>:"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Հաղորդագրությունը չի առաքվել: Հպեք՝ նորից փորձելու համար:"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Զրույց <xliff:g id="PARTICIPANTS">%s</xliff:g>-ի հետ"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Ջնջել վերնագիրը"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Տեսագրել"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Նկարահանել ստատիկ պատկեր"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Լուսանկարել"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Սկսել տեսաձայնագրումը"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Անցնել լիաէկրան ֆոտոխցիկի"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Փոխարկել դիմացի և հետևի տեսախցիկները"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Կանգնեցնել ձայնագրումը և կցել տեսանյութը"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Դադարեցնել տեսանկարահանումը"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Հաղորդակցման լուսանկարներ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one">«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ալբոմում պահվել է <xliff:g id="QUANTITY_2">%d</xliff:g> լուսանկար</item>
+ <item quantity="other">«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ալբոմում պահվել է <xliff:g id="QUANTITY_2">%d</xliff:g> լուսանկար</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one">«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ալբոմում պահվել է <xliff:g id="QUANTITY_2">%d</xliff:g> տեսանյութ</item>
+ <item quantity="other">«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ալբոմում պահվել է <xliff:g id="QUANTITY_2">%d</xliff:g> տեսանյութ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one">«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ալբոմում պահվել է <xliff:g id="QUANTITY_2">%d</xliff:g> կցորդ</item>
+ <item quantity="other">«<xliff:g id="ALBUMNAME_3">%s</xliff:g>» ալբոմում պահվել է <xliff:g id="QUANTITY_2">%d</xliff:g> կցորդ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one">«Ներբեռնումներ» պանակում պահվել է <xliff:g id="QUANTITY_1">%d</xliff:g> կցորդ</item>
+ <item quantity="other">«Ներբեռնումներ» պանակում պահվել է <xliff:g id="QUANTITY_1">%d</xliff:g> կցորդ</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> կցորդ պահվեց</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> կցորդ պահվեց</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Չհաջողվեց պահել <xliff:g id="QUANTITY_1">%d</xliff:g> կցորդ</item>
+ <item quantity="other">Չհաջողվեց պահել <xliff:g id="QUANTITY_1">%d</xliff:g> կցորդ</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS կցորդը պահվեց"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Կարգավորումներ"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Արխիվացված"</string>
+ <string name="action_close" msgid="1840519376200478419">"Փակել"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Հավելյալ"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Կարգաբերում"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Ծանուցումներ"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ձայն"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Լուռ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Թրթռոց"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Արգելափակված է"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-ի առաքման հաշվետվություններ"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Առաքման ծանուցում հայցել յուրաքանչյուր ուղարկվող հաղորդագրության համար"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Ինքնաառբերում"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Ավտոմատ առբերել MMS հաղորդագրությունները"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Ավտոմատ առբերում ռոումինգում"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Ինքնաշխատորեն առբերել MMS-ը ռոումինգում"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Խմբային հաղորդագրություններ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Օգտագործել MMS՝ մեկ հաղորդագրությունը մի քանի ստացողների ուղարկելու համար"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"SMS-ների կանխադրված հավելված"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"SMS-ների կանխադրված հավելված"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Ձեր հեռախոսահամարը"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Անհայտ է"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Ելից հաղորդագրությունների ձայներ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Տեղափոխել SMS հաղորդագրությունները"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Տեղափոխել ստացված SMS հաղորդագրությունների չմշակված տվյալները արտաքին հիշողության մեջ"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Տեղափոխել MMS հաղորդագրությունները"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Տեղափոխել ստացված MMS հաղորդագրությունների չմշակված տվյալները արտաքին հիշողության մեջ"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Ծանուցումներ անլար կապի միջոցով"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Հաղորդագրության ընտրանքներ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Պատճենել տեքստը"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Տեսնել մանրամասները"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Ջնջել"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Առաջ"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Հաղորդագրության մանրամասները"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Տեսակ՝ "</string>
+ <string name="text_message" msgid="7415419755252205721">"Տեքստային հաղորդագրություն"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Մուլտիմեդիա հաղորդագրություն"</string>
+ <string name="from_label" msgid="1947831848146564875">"Ումից` "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Ում` "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Ուղարկվել է՝ "</string>
+ <string name="received_label" msgid="4442494712757995203">"Ստացվել է՝ "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Վերնագիր՝ "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Չափ՝ "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Առաջնահերթություն՝ "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM՝ "</string>
+ <string name="priority_high" msgid="728836357310908368">"Բարձր"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Սովորական"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Ցածր"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Թաքնված ուղարկողի հասցեն"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Կցորդները բեռնելիս հնարավոր չէ ուղարկել հաղորդագրությունը:"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Հնարավոր չէ բեռնել կցորդը: Փորձեք կրկին:"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Ցանցը պատրաստ չէ: Նորից փորձեք:"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Ջնջել տեքստը"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Փոխարկել թվեր և տեքստ մուտքագրելու ռեժիմների միջև"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Ավելացնել այլ մասնակիցների"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Հաստատեք մասնակիցներին"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Սկսել նոր խոսակցություն"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Ընտրել այս նյութը"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Նվագարկել տեսանյութը"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Մարդիկ և ընտրանքներ"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Կարգաբերել"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Մարդիկ և ընտրանքներ"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Ընդհանուր"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Այս խոսակցության մասնակիցները"</string>
+ <string name="action_call" msgid="6596167921517350362">"Զանգահարել"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Ուղարկել հաղորդագրություն"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Ուղարկել հաղորդագրություն&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>-ից&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Ուղարկել լուսանկարները</item>
+ <item quantity="other">Ուղարկել լուսանկարները</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Ուղարկել ձայնագրությունները</item>
+ <item quantity="other">Ուղարկել ձայնագրությունները</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Ուղարկել տեսանյութերը</item>
+ <item quantity="other">Ուղարկել տեսանյութերը</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Ուղարկել կոնտակտի քարտերը</item>
+ <item quantity="other">Ուղարկել կոնտակտի քարտերը</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Ուղարկել կցորդները</item>
+ <item quantity="other">Ուղարկել կցորդները</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> կցորդ պատրաստ է ուղարկելու համար</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> կցորդ պատրաստ է ուղարկելու համար</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Ուղարկել կարծիք"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Դիտել Google Play Store-ում"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Տեղեկություններ տարբերակի մասին"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Տարբերակ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Բաց աղբյուրով ծրագրերի լիցենզիաներ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Ծանուցումներ"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Կցորդի սահմանաչափը լրացել է"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Չհաջողվեց բեռնել կցորդը:"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Ավելացնե՞լ կոնտակտներում"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Ավելացնել կոնտակտ"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Թեմա"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Թեմա՝ "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Կոնտակտի քարտը բեռնվում է"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Հնարավոր չէ բեռնել կոնտակտի քարտը"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Դիտել կոնտակտի քարտը"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> կոնտակտ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> կոնտակտ</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Կոնտակտների քարտեր"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Ծննդյան օր"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Գրառումներ"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Փոխանցել հաղորդագրությունը"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Պատասխանել"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS-ն անջատված է"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Հաղորդագրություններ ուղարկելու համար դարձրեք Հաղորդակցումը SMS-ների կանխադրված հավելված"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Դարձրեք Հաղորդակցումը SMS-ների կանխադրված հավելված"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Փոխել"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Հաղորդագրություններ ստանալու համար դարձրեք Հաղորդակցումը SMS-ների կանխադրված հավելված"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Դուք չեք նշել նախընտրելի SIM քարտը՝ SMS հաղորդագրություններն ուղարկելու համար"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Այս հավելվածը սարքի սեփականատիրոջ կողմից թույլատրված չէ:"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Լավ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Չափից շատ մասնակիցներ կան զրույցին"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Անվավեր կոնտակտներ</item>
+ <item quantity="other">Անվավեր կոնտակտներ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Չհաջողվեց բեռնել տեսախցիկի պատկերը"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Դուք՝ "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>՝ "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Սևագիր"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Հենց նոր զրույց սկսեք, այն կտեսնեք այս ցուցակում"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Արխիվացված խոսակցությունները հայտնվում են այստեղ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Խոսակցությունները բեռնվում են…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Նկար"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Ձայնահոլովակ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Տեսանյութ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Կոնտակտային քարտ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Հետարկել"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Կրկնել"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Մուտքագրեք կոնտակտի անունը կամ հեռախոսահամարը՝ նոր հաղորդագրություն սկսելու համար"</string>
+ <string name="action_block" msgid="9032076625645190136">"Արգելափակել"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Արգելափակել <xliff:g id="DESTINATION">%s</xliff:g>-ին"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Արգելաբացել <xliff:g id="DESTINATION">%s</xliff:g>-ին"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Արգելափակե՞լ <xliff:g id="DESTINATION">%s</xliff:g>-ը:"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Դուք կշարունակեք ստանալ հաղորդագրություններ այս հեռախոսահամարից, բայց չեք ծանուցվի դրանց մասին: Այս խոսակցությունը կարխիվացվի:"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Արգելափակված կոնտակտներ"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ԱՐԳԵԼԱԲԱՑԵԼ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Արգելափակված կոնտակտներ"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Ընտրեք պատկեր՝ փաստաթղթերի գրադարանից"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Հաղորդագրությունն ուղարկվում է"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Հաղորդագրությունն ուղարկվել է"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Բջջային տվյալների կապն անջատված է: Ստուգեք ձեր կարգավորումները:"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Հնարավոր չէ ուղարկել հաղորդագրություններ Թռիչքի ռեժիմում"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Հաղորդագրությունը չհաջողվեց ուղարկել"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Հաղորդագրությունը բեռնվել է"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Բջջային տվյալների կապն անջատված է: Ստուգեք ձեր կարգավորումները:"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Հնարավոր չէ ներբեռնել հաղորդագրությունները Ինքնաթիռի ռեժիմում"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Հաղորդագրությունը չներբեռնվեց"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Զրո"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Մեկ"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Երկու"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Երեք"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Չորս"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Հինգ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Վեց"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Յոթ"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Ութ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Ինը"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Հնարավոր չէ ուղարկել հաղորդագրությունը <xliff:g id="CARRIERNAME">%1$s</xliff:g>-ի միջոցով: Սխալի կոդ՝ <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Հնարավոր չէ ուղարկել հաղորդագրությունը անհայտ օպերատորի միջոցով: Սխալի կոդ՝ <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Փխնց՝ <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Հաղորդագրությունը չի ուղարկվել. Ծառայությունն ակտիվացված չէ ցանցում"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Հաղորդագրությունը չի ուղարկվել. ստացողի հասցեն սխալ է"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Հաղորդագրությունը չի ուղարկվել. անվավեր հաղորդագրություն"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Հաղորդագրությունը չի ուղարկվել. չաջակցվող բովանդակություն"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Հաղորդագրությունը չի ուղարկվել. չաջակցվող հաղորդագրություն"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Հաղորդագրությունը չուղարկվեց: Այն չափազանց մեծ է"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Նոր հաղորդագրություն"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Դիտել"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Պատկեր"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Հնարավոր չէր գտնել համապատասխան հավելված"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Հեռացնել ստացողին"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Նոր հաղորդագրություն"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Չեղարկել"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Խմբագրել մուտքի կետը"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Կարգավորված չէ"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Անուն"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS պրոքսի"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS միացք"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-ի տեսակը"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Ջնջել APN-ը"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Նոր APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Պահել"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Հրաժարվել"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Անվանման դաշտը պետք է լրացված լինի:"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN-ը չի կարող դատարկ լինել:"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC դաշտը պետք է 3 նիշ ունենա:"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC դաշտը պետք է լինի առնվազն 2 կամ 3 թվանշան:"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Լռելյայն APN կարգավորումների վերականգնում:"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Վերականգնել կանխադրվածը"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Սկզբնական APN կարգավորումների վերակարգավորումն ավարտված է:"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Անվերնագիր"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Մատչման կետերի անունները"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-ներ"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Նոր APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Մատչման կետի անվան կարգավորումները հասանելի չեն այս օգտվողին"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Պատճենել սեղմատախտակին"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Պատճենել"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g>-ին"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Ընդհանուր"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Հավելյալ"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Ընդհանուր կարգավորումներ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Ընդլայնված կարգավորումներ"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"«<xliff:g id="SIM_NAME">%s</xliff:g>» SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Ուղարկել անհատական ​​SMS հաղորդագրություններ բոլոր ստացողներին: Միայն դուք կստանաք պատասխաններ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Բոլոր ստացողներին ուղարկել առանձին MMS"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Անհայտ համար"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Նոր հաղորդագրություն"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Նոր հաղորդագրություն:"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM քարտի ընտրում"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Ընտրված է <xliff:g id="SIM_0">%1$s</xliff:g>-ը, SIM քարտի ընտրում"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Խմբագրել վերնագիրը"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Ընտրել SIM քարտ կամ խմբագրել վերնագիրը"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Հպեք և պահեք՝ ձայնագրելու համար"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Սկսել նոր խոսակցություն"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Հաղորդակցում"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Հաղորդակցման ցանկ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Հաղորդակցում"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Նոր հաղորդագրություն"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Խոսակցությունների ցանկ"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Խոսակցությունները բեռնվում են"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Հաղորդագրությունների բեռնում"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Տեսնել այլ զրույցներ"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Դիտել ավելի շատ հաղորդագրություններ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Զրույցը ջնջված է"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Խոսակցությունը ջնջվել է: Հպեք՝ Հաղորդակցման մեջ այլ խոսակցություն դիտելու համար"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Արգելափակված է"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Ապաարգելափակված է"</string>
+ <string name="db_full" msgid="8459265782521418031">"Ազատ տարածքը բավարար չէ: Որոշ տվյալներ չեն պահվի:"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Ընտրել կցորդներ"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Հաստատել ընտրությունը"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ընտրված"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Հեռացրեք մեկ կամ ավելի կցորդ, ապա նորից փորձեք:"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Կարող եք փորձել ուղարկել հաղորդագրությունը, սակայն այն չի կարող առաքվել, եթե չհեռացնեք մեկ կամ ավելի կցորդ:"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Ամեն մի հաղորդագրության համար կարող եք միայն մեկ տեսանյութ ուղարկել: Հեռացրեք ավելորդ տեսանյութերը և նորից փորձեք:"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Կցորդը չբեռնվեց:"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Ուղարկել ամեն դեպքում"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Չհաջողվեց սկսել խոսակցությունը"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Ընտրված է <xliff:g id="SELECTED_SIM">%s</xliff:g> քարտը"</string>
+</resources>
diff --git a/res/values-in/arrays.xml b/res/values-in/arrays.xml
new file mode 100644
index 0000000..ce2892e
--- /dev/null
+++ b/res/values-in/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"tanpa subjek"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ya"</item>
+ <item msgid="6049132459802288033">"Tidak"</item>
+ <item msgid="3084376867445867895">"Oke"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Terima kasih"</item>
+ <item msgid="4881335087096496747">"Saya setuju"</item>
+ <item msgid="2422296858597420738">"Bagus"</item>
+ <item msgid="4805581752819452687">"Dalam perjalanan"</item>
+ <item msgid="4746700499431366214">"Oke, saya akan menghubungi Anda nanti"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..3529301
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Perpesanan"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Perpesanan"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Pilih percakapan"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Setelan"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Kirim Pesan"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Tambahkan lampiran"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Bantuan"</string>
+ <string name="welcome" msgid="2857560951820802321">"Selamat Datang"</string>
+ <string name="skip" msgid="7238879696319945853">"Lewati"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Berikutnya &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Berikutnya"</string>
+ <string name="exit" msgid="1905187380359981199">"Keluar"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Setelan &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Setelan"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Perpesanan perlu izin ke SMS, Telepon, dan Kontak"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Anda dapat mengubah izin di Setelan &gt; Aplikasi &gt; Perpesanan &gt; Izin."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Anda dapat mengubah izin di Setelan, Aplikasi, Messaging, Permissions."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Sering"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Semua kontak"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Kirim ke <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Jepret gambar atau rekam video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Pilih gambar dari perangkat ini"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Rekam audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Pilih foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Media ini dipilih."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Media ini batal dipilih."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> dipilih"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"gambar <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" msgid="8142175029053370662">"gambar"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Rekam audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Bagikan"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Baru saja"</string>
+ <string name="posted_now" msgid="867560789350406701">"Sekarang"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> mnt</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> mnt</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> jam</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> jam</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hari</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hari</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> minggu</item>
+ <item quantity="one">seminggu</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> bulan</item>
+ <item quantity="one">sebulan</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tahun</item>
+ <item quantity="one">setahun</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Pesan kelas 0"</string>
+ <string name="save" msgid="5081141452059463572">"Simpan"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Perangkat hampir kehabisan ruang. Perpesanan akan secara otomatis menghapus pesan lama untuk mengosongkan ruang."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Ruang penyimpanan hampir habis"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Perpesanan tidak dapat mengirim atau menerima pesan hingga lebih banyak ruang tersedia di perangkat Anda."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Penyimpanan SMS tinggal sedikit. Anda perlu menghapus pesan."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Konfirmasi nomor telepon Anda"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Langkah satu kali ini akan memastikan bahwa Perpesanan mengirim pesan grup dengan tepat."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Nomor telepon"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Hapus semua pesan dengan media"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Hapus pesan yang sudah lebih dari <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Hapus otomatis pesan yang sudah lebih dari <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Abaikan"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Hapus semua pesan dengan media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Hapus pesan yang sudah lebih dari <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Hapus pesan yang sudah lebih dari <xliff:g id="DURATION">%s</xliff:g> dan aktifkan fitur hapus otomatis?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> mengatakan"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Anda mengatakan"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Pesan dari <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Anda telah mengirim pesan"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Mengirim…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Tidak dikirim. Sentuh untuk mencoba lagi."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Tidak dikirim. Mencoba lagi..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Kirim ulang atau hapus"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Lakukan panggilan suara ke layanan darurat. SMS Anda tidak dapat dikirim saat ini."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Gagal"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Pesan MMS baru untuk diunduh"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Pesan MMS baru"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Tidak dapat mengunduh"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Sentuh untuk mencoba lagi"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Sentuh untuk mengunduh"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Unduh atau hapus"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Mengunduh..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Pesan sudah tidak berlaku atau tidak tersedia"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ukuran: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, waktu berakhir: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Tidak dapat mengirim. Penerima tidak valid."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Layanan tidak diaktifkan pada jaringan"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Tidak dapat mengirim karena masalah jaringan"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Pesan sudah tidak berlaku atau tidak tersedia"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Tanpa subjek)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Pengirim tidak diketahui"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Telah Diantar"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Tidak dapat mengunduh pesan <xliff:g id="SUBJECT">%1$s</xliff:g> dari <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Tidak dapat menyelesaikan operasi basis data karena memori terlalu kecil"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Pesan tidak dikirim"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Beberapa pesan tidak terkirim di Perpesanan"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> pesan dalam <xliff:g id="CONVERSATIONS">%d</xliff:g> percakapan</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> pesan dalam satu percakapan</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Pesan tidak diunduh"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Beberapa pesan tidak diunduh di Perpesanan"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> pesan dalam <xliff:g id="CONVERSATIONS">%d</xliff:g> percakapan</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> pesan dalam satu percakapan</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Pesan ke <xliff:g id="NUMBER">%1$s</xliff:g> tidak terkirim"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Lakukan panggilan suara ke layanan darurat. SMS Anda ke <xliff:g id="NUMBER">%1$s</xliff:g> tidak dapat dikirim saat ini."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> pesan baru</item>
+ <item quantity="one">Pesan baru</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Mulai"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Tidak ada kamera"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Tidak ada kamera"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Tidak ada rekaman video"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Tidak dapat menyimpan media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Tidak dapat memfoto"</string>
+ <string name="back" msgid="1477626055115561645">"Kembali"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Diarsipkan"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Hapus"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arsipkan"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Batalkan pengarsipan"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Nonaktifkan pemberitahuan"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Aktifkan pemberitahuan"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Tambahkan kontak"</string>
+ <string name="action_download" msgid="7786338136368564146">"Unduh"</string>
+ <string name="action_send" msgid="377635240181672039">"Kirim"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Hapus"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Hapus pesan ini?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Tindakan ini tidak dapat diurungkan."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Hapus"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Hapus percakapan ini?</item>
+ <item quantity="one">Hapus percakapan ini?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Hapus"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Batal"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Kepada"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Pilih beberapa gambar"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Konfirmasi pilihan"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Tidak dapat merekam audio. Coba lagi."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Tidak dapat memutar audio. Coba lagi."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Tidak dapat menyimpan audio. Coba lagi."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Sentuh &amp; tahan"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Gambar"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Klip audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kartu kontak"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Unduh"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Balas via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Balas via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Balas"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> peserta</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> peserta</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Saya"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontak diblokir &amp; diarsipkan"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontak tidak diblokir lagi &amp; batal diarsipkan"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> diarsipkan"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> batal diarsipkan"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notifikasi dinonaktifkan"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notifikasi diaktifkan"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Semua siap. Sentuh Kirim lagi."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Perpesanan berhasil disetel sebagai aplikasi SMS default."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Buang lampiran</item>
+ <item quantity="one">Buang lampiran</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Lampiran audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Putar lampiran audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Jeda"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Pesan dari <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Pesan gagal dari <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Pesan dari <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Pesan yang tidak terkirim ke <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Mengirim pesan ke <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Pesan gagal ke <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Pesan ke <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Pesan gagal dari <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Pesan dari <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Pesan yang tidak terkirim ke <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Mengirim pesan ke <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Pesan gagal ke <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Pesan ke <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Waktu: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Pesan gagal. Sentuh untuk mencoba lagi."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Percakapan dengan <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Hapus subjek"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Ambil video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Ambil gambar diam"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Ambil gambar"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Mulai merekam video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Beralih ke kamera layar penuh"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Beralih antara kamera depan dan belakang"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Hentikan perekaman dan lampirkan video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Berhenti merekam video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto Perpesanan"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foto disimpan ke album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto disimpan ke album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video disimpan ke album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video disimpan ke album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> lampiran disimpan ke album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> lampiran disimpan ke album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> lampiran disimpan ke \"Unduhan\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> lampiran disimpan ke \"Unduhan\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> lampiran disimpan</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> lampiran disimpan</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Tidak dapat menyimpan <xliff:g id="QUANTITY_1">%d</xliff:g> lampiran</item>
+ <item quantity="one">Tidak dapat menyimpan <xliff:g id="QUANTITY_0">%d</xliff:g> lampiran</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Lampiran MMS yang disimpan"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Setelan"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Diarsipkan"</string>
+ <string name="action_close" msgid="1840519376200478419">"Tutup"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Lanjutan"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Debug"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifikasi"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Suara"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Senyap"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Getar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Diblokir"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Laporan pengiriman SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Minta laporan pengiriman dari setiap SMS yang Anda kirimkan"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Ambil otomatis"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Otomatis ambil pesan MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Ambil otomatis saat roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Otomatis ambil MMS saat roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Perpesanan grup"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Gunakan MMS untuk mengirimkan pesan tunggal saat ada beberapa penerima"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplikasi SMS default"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplikasi SMS default"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Nomor telepon Anda"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Tidak dikenal"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Pesan suara keluar"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Buang SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Buang data mentah SMS yang diterima ke dalam file penyimpanan eksternal"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Buang MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Buang data mentah MMS yang diterima ke file penyimpanan eksternal"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Lansiran nirkabel"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opsi pesan"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Salin teks"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Lihat detail"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Hapus"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Teruskan"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detail pesan"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Jenis: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Pesan teks"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Pesan multimedia"</string>
+ <string name="from_label" msgid="1947831848146564875">"Dari: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kepada: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Dikirim: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Diterima: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subjek: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Ukuran: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritas: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Tinggi"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Rendah"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Alamat pengirim tersembunyi"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Tidak dapat mengirim pesan saat memuat lampiran."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Tidak dapat memuat lampiran. Coba lagi."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Jaringan tidak siap. Coba lagi."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Hapus teks"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Beralih antara memasukkan teks dan angka"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Tambahkan peserta lainnya"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Konfirmasi peserta"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Mulai percakapan baru"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Pilih item ini"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Putar video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Orang &amp; opsi"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debug"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Orang &amp; opsi"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Umum"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Orang dalam percakapan ini"</string>
+ <string name="action_call" msgid="6596167921517350362">"Lakukan panggilan telepon"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Kirim pesan"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Kirim pesan&lt;br/&gt;&lt;small&gt;dari <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Kirim foto</item>
+ <item quantity="one">Kirim foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Kirim audio</item>
+ <item quantity="one">Kirim audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Kirim video</item>
+ <item quantity="one">Kirim video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Kirim kartu kontak</item>
+ <item quantity="one">Kirim kartu kontak</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Kirim lampiran</item>
+ <item quantity="one">Kirim lampiran</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> lampiran siap dikirim</item>
+ <item quantity="one">Satu lampiran siap dikirim</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Kirim masukan"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Lihat di Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Info versi"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versi %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Lisensi sumber terbuka"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifikasi"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Batas lampiran tercapai"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Gagal memuat lampiran."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Tambahkan ke Kontak?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Tambahkan Kontak"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subjek"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subjek: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Memuat kartu kontak"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Tidak dapat memuat kartu kontak"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Lihat kartu kontak"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontak</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontak</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kartu kontak"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Ulang Tahun"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Catatan"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Teruskan pesan"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Balas"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS dinonaktifkan"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Untuk mengirim, setel Perpesanan sebagai aplikasi SMS default"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Setel Perpesanan sebagai aplikasi SMS default"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Ganti"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Untuk menerima pesan, setel Perpesanan sebagai aplikasi SMS default."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Tidak ada SIM yang dipilih untuk mengirim pesan SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Aplikasi ini tidak diizinkan oleh pemilik perangkat."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Oke"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Terlalu banyak peserta dalam percakapan"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Kontak tidak valid</item>
+ <item quantity="one">Kontak tidak valid</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Tidak dapat memuat gambar kamera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Anda: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draf"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Setelah memulai percakapan baru, Anda akan melihatnya tercantum di sini"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arsip percakapan muncul di sini"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Memuat percakapan..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Gambar"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Klip audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kartu kontak"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Urungkan"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Coba lagi"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Masukkan nama atau nomor telepon yang dapat dihubungi untuk memulai pesan baru"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokir"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokir <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Bebaskan <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blokir <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Anda akan terus menerima pesan dari nomor ini, tetapi tidak akan diberi tahu lagi. Percakapan ini akan diarsipkan."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Kontak yang diblokir"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"BEBASKAN"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Kontak yang diblokir"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Pilih gambar dari perpustakaan dokumen"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Mengirim pesan"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Pesan terkirim"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Data seluler dinonaktifkan. Periksa setelan."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Tidak dapat mengirim pesan dalam mode Pesawat"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Pesan tidak dapat dikirim"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Pesan diunduh"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Data seluler dinonaktifkan. Periksa setelan."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Tidak dapat mengunduh pesan dalam mode Pesawat"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Pesan tidak dapat diunduh"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nol"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Satu"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dua"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tiga"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Empat"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Lima"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Enam"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Tujuh"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Delapan"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Sembilan"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Tidak dapat mengirim pesan dengan <xliff:g id="CARRIERNAME">%1$s</xliff:g>, kesalahan <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Tidak dapat mengirim pesan dengan operator tak dikenal, kesalahan <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Trs: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Pesan tidak terkirim: layanan tidak diaktifkan di jaringan"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Pesan tidak terkirim: alamat tujuan tidak valid"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Pesan tidak terkirim: pesan tidak valid"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Pesan tidak terkirim: konten tidak didukung"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Pesan tidak terkirim: pesan tidak didukung"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Pesan tidak terkirim: terlalu besar"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Pesan baru"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Lihat"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Gambar"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Aplikasi yang sesuai tidak dapat ditemukan"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Hapus penerima"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Pesan baru"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Batal"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edit titik akses"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Belum disetel"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nama"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Jenis APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Hapus APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN baru"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Simpan"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Buang"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Bidang Nama wajib diisi."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN wajib diisi."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Bidang MCC harus terdiri dari 3 digit."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Bidang MNC harus 2 atau 3 digit."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Memulihkan setelan APN default."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Setel ulang ke default"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Penyetelan ulang setelan APN default selesai."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Tanpa judul"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nama Titik Akses"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN baru"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Setelan Nama Titik Akses tidak tersedia untuk pengguna ini"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Salin ke papan klip?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Salin"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"kepada <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Umum"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Lanjutan"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Setelan umum"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Setelan lanjutan"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Kirimkan pesan SMS individual ke semua penerima. Hanya Anda yang akan mendapatkan balasan"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Kirimkan satu MMS ke semua penerima"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nomor tidak dikenal"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Pesan baru"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Pesan baru."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Pemilih SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> dipilih, pemilih SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edit subjek"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Pilih SIM atau edit subjek"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Sentuh dan tahan untuk merekam audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Mulai percakapan baru"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Perpesanan"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Daftar Perpesanan"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Perpesanan"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Pesan baru"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Daftar percakapan"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Memuat percakapan"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Memuat pesan"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Lihat percakapan lain"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Lihat pesan lainnya"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Percakapan telah dihapus"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Percakapan dihapus. Sentuh untuk menampilkan percakapan Perpesanan yang berbeda"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Diblokir"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Dibebaskan"</string>
+ <string name="db_full" msgid="8459265782521418031">"Sedikit ruang penyimpanan. Beberapa data mungkin hilang."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Pilih lampiran"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Konfirmasi pilihan"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> dipilih"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Hapus satu atau beberapa lampiran dan coba lagi."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Anda dapat mencoba mengirim pesan, tetapi pesan tersebut mungkin tidak terkirim, kecuali Anda menghapus satu atau beberapa lampiran."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Anda hanya dapat mengirim satu video per pesan. Hapus video tambahan dan coba lagi."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Perpesanan gagal memuat lampiran."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Tetap kirim"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Tidak dapat memulai percakapan"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> dipilih"</string>
+</resources>
diff --git a/res/values-is-rIS/arrays.xml b/res/values-is-rIS/arrays.xml
new file mode 100644
index 0000000..f8d4803
--- /dev/null
+++ b/res/values-is-rIS/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ekkert efni"</item>
+ <item msgid="272485471009191934">"ekkertefni"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Já"</item>
+ <item msgid="6049132459802288033">"Nei"</item>
+ <item msgid="3084376867445867895">"Í lagi"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Takk"</item>
+ <item msgid="4881335087096496747">"Ég er sammála"</item>
+ <item msgid="2422296858597420738">"Flott"</item>
+ <item msgid="4805581752819452687">"Er á leiðinni"</item>
+ <item msgid="4746700499431366214">"Ókei, ég fæ að hafa samband síðar"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
new file mode 100644
index 0000000..4a54e14
--- /dev/null
+++ b/res/values-is-rIS/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Skilaboð"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Skilaboð"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Veldu samtal"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Stillingar"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Senda skilaboð"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Bæta viðhengi við"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Hjálp"</string>
+ <string name="welcome" msgid="2857560951820802321">"Komdu fagnandi"</string>
+ <string name="skip" msgid="7238879696319945853">"Sleppa"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Áfram &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Áfram"</string>
+ <string name="exit" msgid="1905187380359981199">"Hætta"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Stillingar &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Stillingar"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Skilaboð þurfa heimild til að fá aðgang að SMS-skilaboðum, síma og tengiliðum."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Þú getur breytt heimildum undir Stillingar &gt; Forrit &gt; Skilaboð &gt; Heimildir."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Þú getur breytt heimildum undir Stillingar, Forrit, Skilaboð, Heimildir."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Algengir"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Allir tengiliðir"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Senda í <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Taka myndir eða myndskeið"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Velja myndir úr þessu tæki"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Taka upp hljóð"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Velja mynd"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Efnið er valið."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Efnið er ekki valið."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> valin"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"mynd <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" msgid="8142175029053370662">"mynd"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Taka upp hljóð"</string>
+ <string name="action_share" msgid="2143483844803153871">"Deila"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Rétt í þessu"</string>
+ <string name="posted_now" msgid="867560789350406701">"Núna"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> mín.</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> mín.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> klukkustund</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> klukkustundir</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dagur</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dagar</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> vika</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> vikur</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mánuður</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mánuðir</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ár</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ár</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Einnota skilaboð"</string>
+ <string name="save" msgid="5081141452059463572">"Vista"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Lítið pláss er í tækinu. Skilaboðaforritið mun sjálfkrafa eyða gömlum skilaboðum til að losa um pláss."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Geymslurýmið er senn á þrotum"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Ekki er víst að Skilaboð geti sent eða tekið á móti skilaboðum fyrr en meira pláss er til staðar í tækinu."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS-geymsla að fyllast. Þú gætir þurft að eyða skilaboðum."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Staðfestu símanúmerið þitt"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Þetta þarf að gera í eitt skipti til að tryggja að Skilaboð komi hópskilaboðunum þínum til skila á réttan hátt."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Símanúmer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Eyða öllum skilaboðum með margmiðlunarefni"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Eyða skilaboðum sem eru eldri en <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Eyða sjálfkrafa skilaboðum sem eru eldri en <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Hunsa"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Eyða öllum skilaboðum með margmiðlunarefni?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Eyða skilaboðum sem eru eldri en <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Eyða skilaboðum sem eru eldri en <xliff:g id="DURATION">%s</xliff:g> og kveikja á sjálfvirkri eyðingu?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> sagði"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Þú sagðir"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Skilaboð sem <xliff:g id="SENDER">%s</xliff:g> sendi"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Þú sendir skilaboð"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Sendir…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ekki sent. Snertu til að reyna aftur."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ekki sent. Reynir aftur…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Endursenda eða eyða"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Hringdu í neyðarþjónustuna. Ekki tókst að senda textaskilaboðin þín á þessari stundu."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Mistókst"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Ný MMS-skilaboð til að sækja"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Ný MMS-skilaboð"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Ekki hægt að sækja"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Snertu til að reyna aftur"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Snertu til að sækja"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Sækja eða eyða"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Sækir…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Skilaboð útrunnin eða ekki aðgengileg"</string>
+ <string name="mms_info" msgid="3402311750134118165">"stærð: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, útrunnið: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Ekki er hægt að senda. Viðtakandinn er ekki gildur."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Símkerfið hefur ekki gert þjónustuna virka"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Sending mistókst vegna símkerfisvandamáls"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Skilaboð útrunnin eða ekki aðgengileg"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Ekkert efni)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Óþekktur sendandi"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Sent"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Ekki var hægt að sækja skilaboðin <xliff:g id="SUBJECT">%1$s</xliff:g> sem <xliff:g id="FROM">%2$s</xliff:g> sendi."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Lítið minni varð til þess að ekki var hægt að ljúka gagnagrunnsaðgerðinni"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Skilaboð ekki send"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Einhver skilaboð voru ekki send í Skilaboðum"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> skilaboð í <xliff:g id="CONVERSATIONS">%d</xliff:g> samtali</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> skilaboð í <xliff:g id="CONVERSATIONS">%d</xliff:g> samtölum</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Skilaboð ekki sótt"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Einhver skilaboð voru ekki sótt í Skilaboð"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> skilaboð í <xliff:g id="CONVERSATIONS">%d</xliff:g> samtali</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> skilaboð í <xliff:g id="CONVERSATIONS">%d</xliff:g> samtölum</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Skilaboð til <xliff:g id="NUMBER">%1$s</xliff:g> voru ekki send"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Hringdu í neyðarþjónustuna. Ekki tókst að senda textaskilaboðin þín í númerið <xliff:g id="NUMBER">%1$s</xliff:g> á þessari stundu."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> ný skilaboð</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ný skilaboð</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Byrja"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Myndavélin er ekki tiltæk"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Myndavélin er ekki tiltæk"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Upptaka myndskeiða er ekki tiltæk"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Ekki er hægt að vista efni"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Ekki er hægt að taka mynd"</string>
+ <string name="back" msgid="1477626055115561645">"Til baka"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Geymsla"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Eyða"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Setja í geymslu"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Taka úr geymslu"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Slökkva á tilkynningum"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Kveikja á tilkynningum"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Bæta tengilið við"</string>
+ <string name="action_download" msgid="7786338136368564146">"Sækja"</string>
+ <string name="action_send" msgid="377635240181672039">"Senda"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Eyða"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Eyða þessum skilaboðum?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Ekki er hægt að afturkalla þessa aðgerð."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Eyða"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Eyða þessum samtölum?</item>
+ <item quantity="other">Eyða þessum samtölum?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Eyða"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Hætta við"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Til"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Velja margar myndir"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Staðfesta val"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Ekki er hægt að taka upp hljóð. Reyndu aftur."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Ekki er hægt að spila hljóð. Reyndu aftur."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Ekki var hægt að vista hljóð. Reyndu aftur."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Haltu inni"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Mynd"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Hljóðbútur"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Myndskeið"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Tengiliðaspjald"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Sækja"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Svara með SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Svara með MMS-skilaboðum"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Svara"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> þátttakandi</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> þátttakendur</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ég"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Tengiliður settur á bannlista og í geymslu"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Tengiliður tekinn af bannlista og úr geymslu"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> sett í geymslu"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> tekin úr geymslu"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Slökkt á tilkynningum"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Kveikt á tilkynningum"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Allt til reiðu. Snertu Senda aftur."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Skilaboð voru valin sem sjálfgefið SMS-forrit."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Fleygja viðhengjum</item>
+ <item quantity="other">Fleygja viðhengjum</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Hljóðviðhengi"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Spila hljóðviðhengi"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Hlé"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Skilaboð sem <xliff:g id="SENDER">%s</xliff:g> sendi: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Misheppnuð skilaboð sem <xliff:g id="SENDER">%s</xliff:g> sendi: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Skilaboð sem <xliff:g id="SENDER">%s</xliff:g> sendi: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Ósend skilaboð til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sendir skilaboð til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Misheppnuð skilaboð til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Skilaboð til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Misheppnuð skilaboð sem <xliff:g id="SENDER">%s</xliff:g> sendi: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Skilaboð sem <xliff:g id="SENDER">%s</xliff:g> sendi: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Ósend skilaboð til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sendir skilaboð til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Misheppnuð skilaboð til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Skilaboð til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tími: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Misheppnuð skilaboð. Snertu til að reyna aftur."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Samtal við <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Eyða efni"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Taka upp myndskeið"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Taka kyrrmynd"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Taka mynd"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Byrja að taka upp myndskeið"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Skipta yfir í myndavél á öllum skjánum"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Skipt á milli fremri og aftari myndavélar"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stöðva upptöku og hengja myndskeið við"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Stöðva upptöku myndskeiðs"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Skilaboðamyndir"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> mynd vistuð í „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“ möppu</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> myndir vistaðar í „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“ möppu</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> myndskeið vistað í „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“ möppu</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> myndskeið vistuð í „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“ möppu</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> viðhengi vistað í „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“ möppu</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> viðhengi vistuð í „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“ möppu</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> viðhengi vistað í „Niðurhal“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> viðhengi vistuð í „Niðurhal“</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> viðhengi vistað</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> viðhengi vistuð</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Ekki tókst að vista <xliff:g id="QUANTITY_1">%d</xliff:g> viðhengi</item>
+ <item quantity="other">Ekki tókst að vista <xliff:g id="QUANTITY_1">%d</xliff:g> viðhengi</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS-viðhengi vistað"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Stillingar"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Geymsla"</string>
+ <string name="action_close" msgid="1840519376200478419">"Loka"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Ítarlegt"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Villuleita"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Tilkynningar"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Hljóð"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Hljóðlaust"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Titringur"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Á bannlista"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS-skilatilkynningar"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Óska eftir skilatilkynningu fyrir hver send SMS-skilaboð"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Sækja sjálfkrafa"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Sækja MMS-skilaboð sjálfkrafa"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Sækja sjálfkrafa í reiki"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Sækja MMS sjálfkrafa í reiki"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Hópskilaboð"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Nota MMS til að senda ein skilaboð þegar viðtakendur eru margir"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Sjálfgefið SMS-forrit"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Sjálfgefið SMS-forrit"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Símanúmerið þitt"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Óþekkt"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Hljóð fyrir send skilaboð"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Vista SMS-gögn"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Vista ósniðin SMS-gögn í ytri geymsluskrá"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Vista MMS-gögn"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Vista ósniðin MMS-gögn í ytri geymsluskrá"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Þráðlausar viðvaranir"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Skilaboðavalkostir"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Afrita texta"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Skoða upplýsingar"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Eyða"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Áframsenda"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Skilaboðaupplýsingar"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Gerð: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Textaskilaboð"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Margmiðlunarskilaboð"</string>
+ <string name="from_label" msgid="1947831848146564875">"Frá: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Til: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Sent: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Móttekið: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Efni: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Stærð: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Forgangur: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-kort: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Hár"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Venjulegur"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Lágur"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM-kort <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Falið netfang sendanda"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Ekki er hægt að senda skilaboð á meðan verið er að hlaða viðhengi."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Ekki er hægt að hlaða viðhengi. Reyndu aftur."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Netið er ekki tilbúið. Reyndu aftur."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Eyða texta"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Skipta á milli innsláttar texta og talna"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Bæta við fleiri þátttakendum"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Staðfesta þátttakendur"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Byrja nýtt samtal"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Velja þetta atriði"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Spila myndskeið"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Fólk og valkostir"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Villuleita"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Fólk og valkostir"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Almennt"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Fólk í þessu samtali"</string>
+ <string name="action_call" msgid="6596167921517350362">"Hringja símtal"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Senda skilaboð"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Senda skilaboð&lt;br/&gt;&lt;small&gt;úr <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Senda myndir</item>
+ <item quantity="other">Senda myndir</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Senda hljóð</item>
+ <item quantity="other">Senda hljóð</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Senda myndskeið</item>
+ <item quantity="other">Senda myndskeið</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Senda tengiliðaspjöld</item>
+ <item quantity="other">Senda tengiliðaspjöld</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Senda viðhengi</item>
+ <item quantity="other">Senda viðhengi</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> viðhengi tilbúið til sendingar</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> viðhengi tilbúin til sendingar</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Senda ábendingu"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Skoða í Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Útgáfuupplýsingar"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Útgáfa %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Leyfi opins kóða"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Tilkynningar"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Hámarki viðhengja náð"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Mistókst að hlaða viðhengið."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Bæta við tengiliði?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Bæta tengilið við"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Efni"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Efni: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Hleður tengiliðaspjald"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Ekki var hægt að hlaða tengiliðaspjald"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Skoða tengiliðaspjald"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> tengiliður</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> tengiliðir</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Tengiliðaspjöld"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Fæðingardagur"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Athugasemdir"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Áframsenda skilaboð"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Svara"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Slökkt á SMS"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Til að senda skaltu velja Skilaboð sem sjálfgefið SMS-forrit"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Veldu Skilaboð sem sjálfgefið SMS-forrit"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Breyta"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Til að fá skilaboð skaltu velja Skilaboð sem sjálfgefið SMS-forrit"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Ekkert SIM-kort valið fyrir sendingu SMS-skilaboða"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Eigandi tækisins leyfir ekki þetta forrit."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Í lagi"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Of margir þátttakendur í samtali"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Ógildir tengiliðir</item>
+ <item quantity="other">Ógildir tengiliðir</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Ekki var hægt að hlaða mynd frá myndavél"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Þú: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Drög"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Þegar þú hefur nýtt samtal birtist það hér"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Samtöl í geymslu birtast hér"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Hleður samtöl…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Mynd"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Hljóðbútur"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Myndskeið"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Tengiliðaspjald"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Afturkalla"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Reyna aftur"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Sláðu inn nafn eða símanúmer tengiliðar til að búa til ný skilaboð"</string>
+ <string name="action_block" msgid="9032076625645190136">"Setja á bannlista"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Útiloka <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Taka <xliff:g id="DESTINATION">%s</xliff:g> af bannlista"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Loka á <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Þú heldur áfram að fá skilaboð úr þessu númeri en færð ekki lengur tilkynningar. Þetta samtal verður sett í geymslu."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Útilokaðir tengiliðir"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"TAKA AF BANNLISTA"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Útilokaðir tengiliðir"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Velja mynd úr skjalasafni"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sendir skilaboð"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Skilaboð send"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Slökkt er á farsímagögnum. Athugaðu stillingarnar."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Ekki er hægt að senda skilaboð þegar flugstilling er virk"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Ekki var hægt að senda skilaboðin"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Skilaboð sótt"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Slökkt er á farsímagögnum. Athugaðu stillingarnar."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Ekki er hægt að sækja skilaboð þegar flugstilling er virk"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Ekki var hægt að sækja skilaboðin"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Núll"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Einn"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Tveir"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Þrír"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Fjórir"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Fimm"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sex"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sjö"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Átta"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Níu"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Ekki er hægt að senda skilaboð í gegnum <xliff:g id="CARRIERNAME">%1$s</xliff:g>, villa <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Ekki er hægt að senda skilaboð í gegnum óþekkt símafyrirtæki, villa <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Áfrs.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Skilaboðin voru ekki send. Símkerfið hefur ekki gert þjónustuna virka"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Skilaboðin voru ekki send: Ógildur áfangastaður"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Skilaboðin voru ekki send: Ógild skilaboð"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Skilaboðin voru ekki send: Óstutt innihald"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Skilaboðin voru ekki send: Óstudd skilaboð"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Skilaboðin voru ekki send: Of stór"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Ný skilaboð"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Skoða"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Mynd"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Viðeigandi forrit fannst ekki"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Fjarlægja viðtakanda"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Ný skilaboð"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Hætta við"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Breyta aðgangsstað"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ekki stillt"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Heiti"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Aðgangsstaður"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy-þjónn fyrir MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-gátt"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tegund aðgangsstaðar"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Eyða aðgangsstað"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nýr aðgangsstaður"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Vista"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Fleygja"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Reiturinn fyrir heiti má ekki vera auður."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Heiti aðgangsstaðar má ekki vera autt."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-reitur verður að innihalda 3 tölustafi."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC-reitur verður að innihalda 2 eða 3 tölustafi."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Endurheimtir sjálfgefnar stillingar aðgangsstaðar."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Núllstilla"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Sjálfgefnar stillingar aðgangsstaðar endurheimtar."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Ónefnt"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Heiti aðgangsstaða"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Aðgangsstaðir"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nýr aðgangsstaður"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Stillingar fyrir heiti aðgangsstaðar eru ekki í boði fyrir þennan notanda"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Afrita á klippiborð?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Afrita"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"á <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Almennt"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Ítarlegt"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Almennar stillingar"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Ítarlegar stillingar"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM-kortið „<xliff:g id="SIM_NAME">%s</xliff:g>“"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Senda sérstök SMS-skilaboð til hvers viðtakanda fyrir sig. Svör verða aðeins send til þín"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Senda ein MMS-skilaboð til allra viðtakenda"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Óþekkt númer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Ný skilaboð"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Ný skilaboð."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Val á SIM-korti"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> valið, val á SIM-korti"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Breyta efni"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Velja SIM-kort eða breyta efni"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Haltu inni til að taka upp hljóð"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Hefja nýtt samtal"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Skilaboð"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Skilaboðalisti"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Skilaboð"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Ný skilaboð"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Samtalalisti"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Hleður samtöl"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Hleður skilaboð"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Skoða fleiri samtöl"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Skoða fleiri skilaboð"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Samtali eytt"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Samtali eytt. Snertu til að sýna annað samtal í Skilaboðum"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Á bannlista"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Tekin(n) af bannlista"</string>
+ <string name="db_full" msgid="8459265782521418031">"Geymslupláss er á þrotum. Einhver gögn kunna að glatast."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Veldu viðhengi"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Staðfesta val"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> valin"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Fjarlægðu eitt eða fleiri viðhengi og reyndu aftur."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Þú getur prófað að senda skilaboðin en þau komast ef til vill ekki til skila nema þú fjarlægir eitt eða fleiri viðhengi."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Þú getur aðeins sent eitt myndskeið með hverjum skilaboðum. Fjarlægðu myndskeiðin sem eru umfram og reyndu aftur."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Skilaboð gátu ekki hlaðið viðhengið."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Senda samt"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Ekki tókst að hefja samtal"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> valið"</string>
+</resources>
diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml
new file mode 100644
index 0000000..16f7792
--- /dev/null
+++ b/res/values-it/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"nessun oggetto"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Sì"</item>
+ <item msgid="6049132459802288033">"No"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"He he"</item>
+ <item msgid="2611328818571146775">"Grazie"</item>
+ <item msgid="4881335087096496747">"Accetto"</item>
+ <item msgid="2422296858597420738">"Bene"</item>
+ <item msgid="4805581752819452687">"Sto arrivando"</item>
+ <item msgid="4746700499431366214">"OK, ti ricontatto più tardi"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..7261f25
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Messaggi"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Messaggi"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Seleziona conversazione"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Impostazioni"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Invia messaggio"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Aggiungi un allegato"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Guida"</string>
+ <string name="welcome" msgid="2857560951820802321">"Benvenuto"</string>
+ <string name="skip" msgid="7238879696319945853">"Ignora"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Avanti &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Avanti"</string>
+ <string name="exit" msgid="1905187380359981199">"Esci"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Impostazioni &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Impostazioni"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaggi richiede l\'autorizzazione per SMS, Telefono e Contatti."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Puoi modificare le autorizzazioni in Impostazioni &gt; Applicazioni &gt; Messaggi &gt; Autorizzazioni."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Puoi modificare le autorizzazioni in Impostazioni &gt; Applicazioni &gt; Messaggi &gt; Autorizzazioni."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frequenti"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Tutti i contatti"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Invia a <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Scatta foto o registra video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Scegli immagini su questo dispositivo"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Registra audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Scegli foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Il contenuto multimediale è selezionato."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Il contenuto multimediale è deselezionato."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> selezionati"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"immagine: <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" msgid="8142175029053370662">"immagine"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Registrazione audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Condividi"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Adesso"</string>
+ <string name="posted_now" msgid="867560789350406701">"Ora"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ore</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> giorni</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> giorno</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> settimane</item>
+ <item quantity="one">una settimana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mesi</item>
+ <item quantity="one">un mese</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> anni</item>
+ <item quantity="one">un anno</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Messaggio classe 0"</string>
+ <string name="save" msgid="5081141452059463572">"Salva"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Spazio insufficiente sul dispositivo. Messaggi eliminerà automaticamente i messaggi meno recenti per liberare spazio."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Spazio di archiviazione in esaurimento"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Messaggi potrebbe non inviare o ricevere messaggi finché non si libererà spazio sul dispositivo."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Memoria per gli SMS scarsa. Potresti dover eliminare alcuni messaggi."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Conferma il tuo numero di telefono"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Con questo passaggio da eseguire una sola volta, puoi assicurarti che Messaggi invii correttamente i messaggi di gruppo."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Numero di telefono"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Elimina tutti i messaggi con contenuti multimediali"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Elimina messaggi più vecchi di <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Elimina automaticamente messaggi più vecchi di <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignora"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Eliminare tutti i messaggi con contenuti multimediali?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Eliminare i messaggi più vecchi di <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Eliminare i messaggi più vecchi di <xliff:g id="DURATION">%s</xliff:g> e attivare l\'eliminazione automatica?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"Messaggio di <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Il tuo messaggio"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Messaggio di <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Hai inviato un messaggio"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Invio…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Non inviato. Tocca per riprovare."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Non inviato. Nuovo tentativo…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Invia di nuovo o elimina"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Effettua una chiamata vocale al numero di emergenza. Al momento non è possibile recapitare l\'SMS."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Operazione non riuscita"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nuovo messaggio MMS da scaricare"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nuovo messaggio MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Download non riuscito"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tocca per riprovare"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Tocca per scaricare"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Scarica o elimina"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Download..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Messaggio scaduto o non disponibile"</string>
+ <string name="mms_info" msgid="3402311750134118165">"dimensioni: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, scadenza: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Impossibile inviare. Destinatario non valido."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Servizio non attivato sulla rete"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Invio non riuscito a causa di problemi di rete"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Messaggio scaduto o non disponibile"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Nessun oggetto)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Mittente sconosciuto"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Recapitato"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Download del messaggio <xliff:g id="SUBJECT">%1$s</xliff:g> inviato da <xliff:g id="FROM">%2$s</xliff:g> non riuscito."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Completamento dell\'operazione nel database non riuscito a causa di memoria insufficiente"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Messaggio non inviato"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Alcuni messaggi non inviati in Messaggi"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messaggi in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversazioni</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messaggi in una conversazione</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Messaggio non scaricato"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Alcuni messaggi non scaricati in Messaggi"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> messaggi in <xliff:g id="CONVERSATIONS">%d</xliff:g> conversazioni</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> messaggi in una conversazione</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Messaggio al numero <xliff:g id="NUMBER">%1$s</xliff:g> non inviato"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Effettua una chiamata vocale al numero di emergenza. Al momento non è possibile recapitare l\'SMS al numero <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nuovi messaggi</item>
+ <item quantity="one">Nuovo messaggio</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Avvia"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Fotocamera non disponibile"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Fotocamera non disponibile"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Acquisizione video non disponibile"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Impossibile salvare i contenuti multimediali"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Impossibile scattare una foto"</string>
+ <string name="back" msgid="1477626055115561645">"Indietro"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archiviate"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Elimina"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archivio"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Annulla archiviazione"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Disattiva notifiche"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Attiva notifiche"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Aggiungi contatto"</string>
+ <string name="action_download" msgid="7786338136368564146">"Scarica"</string>
+ <string name="action_send" msgid="377635240181672039">"Invia"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Elimina"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Eliminare questo messaggio?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"L\'operazione non può essere annullata."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Elimina"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Eliminare queste conversazioni?</item>
+ <item quantity="one">Eliminare questa conversazione?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Elimina"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Annulla"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"A"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Seleziona diverse immagini"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Conferma selezione"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"e altri <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Impossibile registrare l\'audio. Riprova."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Impossibile riprodurre l\'audio. Riprova."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Impossibile salvare l\'audio. Riprova."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Tocca e tieni premuto"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Immagine"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clip audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Scheda contatto"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Scarica"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Rispondi tramite SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Rispondi con MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Rispondi"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> partecipanti</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participante</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Io"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contatto bloccato e archiviato"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contatto sbloccato e non archiviato"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> archiviate"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> rimosse dall\'archivio"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notifiche disattivate"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notifiche attivate"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Fatto. Tocca di nuovo Invia."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Messaggi impostato correttamente come app predefinita per gli SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Elimina allegati</item>
+ <item quantity="one">Elimina allegato</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Allegato audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Riproduci allegato audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Metti in pausa"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Messaggio da <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Messaggio di <xliff:g id="SENDER">%s</xliff:g> non recapitato: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Messaggio di <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Messaggio per <xliff:g id="CONTACT">%s</xliff:g> non inviato: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Invio del messaggio a <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Messaggio per <xliff:g id="CONTACT">%s</xliff:g> non recapitato: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Messaggio per <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Messaggio di <xliff:g id="SENDER">%s</xliff:g> non recapitato: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Messaggio di <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Messaggio per <xliff:g id="GROUP">%s</xliff:g> non inviato: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Invio del messaggio a <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Messaggio per <xliff:g id="GROUP">%s</xliff:g> non recapitato: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Messaggio per <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Data/ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Messaggio non recapitato. Tocca per riprovare."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversazione con <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Elimina oggetto"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Registra video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Scatta una foto in posa"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Scatta una foto"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Inizia a registrare il video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Passa alla fotocamera a schermo intero"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Passa dalla fotocamera frontale a quella posteriore e viceversa"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Interrompi registrazione e aggiungi video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Interrompi registrazione video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto in Messaggi"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foto salvate nell\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto salvata nell\'album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video salvati nell\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video salvato nell\'album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> allegati salvati nell\'album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> allegato salvato nell\'album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> allegati salvati in \"Download\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> allegato salvato in \"Download\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> allegati salvati</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> allegato salvato</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Impossibile salvare <xliff:g id="QUANTITY_1">%d</xliff:g> allegati</item>
+ <item quantity="one">Impossibile salvare <xliff:g id="QUANTITY_0">%d</xliff:g> allegato</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Allegato MMS salvato"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Impostazioni"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archiviate"</string>
+ <string name="action_close" msgid="1840519376200478419">"Chiudi"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avanzate"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Debug"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notifiche"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Suono"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silenzioso"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrazione"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloccato"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Rapporti di consegna SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Richiedi conferma di recapito per ogni SMS inviato"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recupero automatico"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recupera messaggi MMS automaticamente"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Recupero automatico in roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Recupero automatico di MMS in roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Messaggi di gruppo"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utilizza gli MMS per inviare un unico messaggio a più destinatari"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"App SMS predefinita"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"App SMS predefinita"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Il tuo numero di telefono"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Sconosciuto"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Riproduzione audio per messaggi in uscita"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Scarica SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Scarica dati non elaborati SMS ricevuti in file di archiviazione esterna"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Scarica MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Scarica dati non elaborati MMS ricevuti in file di archiviazione esterna"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Avvisi wireless"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opzioni messaggio"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copia testo"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Visualizza dettagli"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Elimina"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Inoltra"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Dettagli messaggio"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Messaggio multimediale"</string>
+ <string name="from_label" msgid="1947831848146564875">"Da: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"A: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Inviato: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Ricevuto in data: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Oggetto: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Dimensioni: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorità: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normale"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Bassa"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Indirizzo mittente nascosto"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Impossibile inviare il messaggio durante il caricamento degli allegati."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Impossibile caricare l\'allegato. Riprova."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"La rete non è pronta. Riprova."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Elimina testo"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Passa tra immissione testo e numeri"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Aggiungi altri partecipanti"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Conferma partecipanti"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Inizia nuova conversazione"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Seleziona questo elemento"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Riproduci video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Persone e opzioni"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debug"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Persone e opzioni"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Generali"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Persone presenti nella conversazione"</string>
+ <string name="action_call" msgid="6596167921517350362">"Fai una chiamata"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Invia messaggio"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Invia messaggio&lt;br/&gt;&lt;small&gt;da <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Invia foto</item>
+ <item quantity="one">Invia foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Invia audio</item>
+ <item quantity="one">Invia audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Invia video</item>
+ <item quantity="one">Invia video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Invia schede contatti</item>
+ <item quantity="one">Invia scheda contatto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Invia allegati</item>
+ <item quantity="one">Invia allegato</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> allegati pronti da inviare</item>
+ <item quantity="one">Un allegato pronto da inviare</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Invia feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Visualizza nel Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Info versione"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versione %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licenze open source"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notifiche"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Limite di allegati raggiunto"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Impossibile caricare l\'allegato."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Aggiungere a Contatti?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Aggiungi contatto"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Oggetto"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Oggetto: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Caricamento scheda contatto in corso"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Impossibile caricare la scheda del contatto"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Visualizza scheda contatto"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contatti</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contatto</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Schede contatti"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Compleanno"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Note"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Inoltra messaggio"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Rispondi"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS disattivati"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Per inviare messaggi, imposta Messaggi come app predefinita per gli SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Imposta Messaggi come app predefinita per gli SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Modifica"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Per ricevere messaggi, imposta Messaggi come app predefinita per gli SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nessuna SIM preferita selezionata per l\'invio di messaggi SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Questa app non è consentita dal proprietario del dispositivo."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Troppi partecipanti alla conversazione"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Contatti non validi</item>
+ <item quantity="one">Contatto non valido</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Impossibile caricare l\'immagine della fotocamera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Tu: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Bozza"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Le nuove conversazioni che inizi verranno elencate qui"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Le conversazioni archiviate vengono visualizzate qui"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Caricamento conversazioni in corso..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Immagine"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clip audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Scheda contatto"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Annulla"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Riprova"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Inserisci il nome o il numero di telefono di un contatto per iniziare un nuovo messaggio"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blocca"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blocca <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Sblocca <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Bloccare <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Continuerai a ricevere messaggi da questo numero ma non riceverai più notifiche. Questa conversazione verrà archiviata."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contatti bloccati"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"SBLOCCA"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contatti bloccati"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Scegli immagine da raccolta documenti"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Invio messaggio"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Messaggio inviato"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Dati mobili non attivi. Controlla le impostazioni."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Impossibile inviare messaggi in modalità Aereo"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Messaggio non inviato"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Messaggio scaricato"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Dati mobili non attivi. Controlla le impostazioni."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Impossibile scaricare messaggi in modalità aereo"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Impossibile scaricare il messaggio"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Uno"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Due"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tre"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Quattro"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinque"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sei"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sette"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Otto"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nove"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Impossibile inviare il messaggio tramite <xliff:g id="CARRIERNAME">%1$s</xliff:g>. Errore <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Impossibile inviare il messaggio tramite un operatore sconosciuto. Errore <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Messaggio non inviato: servizio non attivato nella rete"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Messaggio non inviato: indirizzo di destinazione non valido"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Messaggio non inviato: messaggio non valido"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Messaggio non inviato: contenuti non supportati"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Messaggio non inviato: messaggio non supportato"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Messaggio non inviato: troppo grande"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nuovo messaggio"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Visualizza"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Immagine"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Impossibile trovare un\'applicazione adeguata"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Rimuovi destinatario"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nuovo messaggio"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Annulla"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Modifica punto di accesso"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Non impostato"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nome"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Porta MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipo APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Elimina APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nuovo APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Salva"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Elimina"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Il campo Nome non può essere vuoto."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Il campo APN non può essere vuoto."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Il campo MCC deve contenere 3 cifre."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Il campo MNC deve contenere 2 o 3 cifre."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Ripristino delle impostazioni APN predefinite."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Ripristina impostazioni predefinite"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Ripristino delle impostazioni APN predefinite completato."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Senza titolo"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nomi punti di accesso"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nuovo APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Le impostazioni del nome punto di accesso non sono disponibili per questo utente"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copiare negli appunti?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copia"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"a <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Generali"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avanzate"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Impostazioni generali"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Impostazioni avanzate"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Invia SMS singoli a tutti i destinatari. Soltanto tu riceverai le risposte"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Invia un singolo MMS a tutti i destinatari"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Numero sconosciuto"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nuovo messaggio"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nuovo messaggio."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Selettore SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Scheda <xliff:g id="SIM_0">%1$s</xliff:g> selezionata, selettore SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Modifica oggetto"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Seleziona la SIM o modifica l\'oggetto"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Tocca e tieni premuto per registrare l\'audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Inizia nuova conversazione"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaggi"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Elenco messaggi"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaggi"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nuovo messaggio"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Elenco conversazioni"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Caricamento conversazioni..."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Caricamento dei messaggi in corso"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Visualizza altre conversazioni"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Mostra altri messaggi"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversazione eliminata"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversazione eliminata. Tocca per vedere un\'altra conversazione di Messaggi"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloccato"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Sbloccato"</string>
+ <string name="db_full" msgid="8459265782521418031">"Lo spazio di archiviazione è quasi esaurito. Alcuni dati potrebbero andare perduti."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Seleziona allegati"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Conferma selezione"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> selezionati"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Rimuovi uno o più allegati e riprova."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Puoi provare a inviare il messaggio, che però potrebbe non essere recapitato se non rimuovi uno o più allegati."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Puoi inviare un solo video per messaggio. Rimuovi i video aggiuntivi e riprova."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaggi non ha potuto caricare l\'allegato."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Invia comunque"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Impossibile avviare la conversazione"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"SIM selezionata: <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-iw/arrays.xml b/res/values-iw/arrays.xml
new file mode 100644
index 0000000..91bd7db
--- /dev/null
+++ b/res/values-iw/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ללא נושא"</item>
+ <item msgid="272485471009191934">"ללא נושא"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"כן"</item>
+ <item msgid="6049132459802288033">"לא"</item>
+ <item msgid="3084376867445867895">"אישור"</item>
+ <item msgid="3155097332660174689">"חחח"</item>
+ <item msgid="2611328818571146775">"תודה"</item>
+ <item msgid="4881335087096496747">"אני מסכים"</item>
+ <item msgid="2422296858597420738">"נחמד"</item>
+ <item msgid="4805581752819452687">"אני בדרך"</item>
+ <item msgid="4746700499431366214">"אוקיי, נדבר מאוחר יותר"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..74e46bf
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"העברת הודעות"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"העברת הודעות"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"בחר שיחה"</string>
+ <string name="action_settings" msgid="1329008122345201684">"הגדרות"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"שלח הודעה"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"הוסף קובץ מצורף"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"עזרה"</string>
+ <string name="welcome" msgid="2857560951820802321">"ברוכים הבאים!"</string>
+ <string name="skip" msgid="7238879696319945853">"דלג"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"‏הבא &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"הבא"</string>
+ <string name="exit" msgid="1905187380359981199">"צא"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"‏הגדרות &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"הגדרות"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"‏\'העברת הודעות\' זקוק להרשאות SMS, טלפון ואנשי קשר."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"‏תוכל לשנות את ההרשאות ב\'הגדרות\' &gt; \'אפליקציות\' &gt;‏ \'העברת הודעות\' ‏&gt; \'הרשאות\'."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"תוכל לשנות את ההרשאות ב\'הגדרות\', \'אפליקציות\', \'העברת הודעות\', \'הרשאות\'."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"בתדירות גבוהה"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"כל אנשי הקשר"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"שלח אל <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"צלם תמונות או סרטון"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"בחר תמונות ממכשיר זה"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"הקלט אודיו"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"בחר תמונה"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"בחרת במדיה."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"ביטלת את הבחירה במדיה."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> נבחרו"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"תמונה <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" msgid="8142175029053370662">"תמונה"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"הקלט אודיו"</string>
+ <string name="action_share" msgid="2143483844803153871">"שיתוף"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ממש עכשיו"</string>
+ <string name="posted_now" msgid="867560789350406701">"עכשיו"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> דקות</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> דקות</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> דקות</item>
+ <item quantity="one">דקה <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> שעות</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> שעות</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> שעות</item>
+ <item quantity="one">שעה <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> ימים</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> ימים</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ימים</item>
+ <item quantity="one">יום <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="two"> שבועות</item>
+ <item quantity="many"> שבועות<xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="other"> שבועות</item>
+ <item quantity="one">שבוע</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="two"><xliff:g id="COUNT">%d</xliff:g> חודשים</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> חודשים</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> חודשים</item>
+ <item quantity="one">חודש</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="two"> שנים</item>
+ <item quantity="many"> שנים<xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="other"> שנים</item>
+ <item quantity="one">שנה</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"הודעה ברמה 0"</string>
+ <string name="save" msgid="5081141452059463572">"שמור"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"נותר מעט שטח פנוי במכשיר. \'העברת הודעות\' ימחק הודעות ישנות באופן אוטומטי כדי לפנות שטח."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"שטח האחסון אוזל"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"לא תוכל לשלוח או לקבל הודעות ב\'העברת הודעות\' עד שתפנה שטח במכשיר."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"‏אין מספיק שטח אחסון להודעות SMS. ייתכן שתצטרך למחוק הודעות."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"אשר את מספר הטלפון שלך"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"שלב חד-פעמי זה יבטיח שהאפליקציה \'העברת הודעות\' תעביר כראוי את ההודעות הקבוצתיות שלך."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"מספר טלפון"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"מחק את כל ההודעות עם מדיה"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"מחק הודעות בנות יותר מ-<xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"מחק אוטומטית הודעות שגילן עולה על <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"התעלם"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"למחוק את כל ההודעות עם המדיה?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"למחוק הודעות שגילן עולה על <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"למחוק הודעות שגילן עולה על <xliff:g id="DURATION">%s</xliff:g> ולהפעיל מחיקה אוטומטית?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> אמר"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"אמרת"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"הודעה מאת <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"שלחת הודעה"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"שולח..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"ההודעה לא נשלחה. גע כדי לנסות שוב."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"ההודעה לא נשלחה. מנסה שוב…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"שלח שוב או מחק"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"בצע שיחה קולית לשירותי חירום. לא ניתן היה לשלוח את הודעת הטקסט."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"נכשל"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"‏הודעת MMS חדשה להורדה"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"‏הודעת MMS חדשה"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"לא ניתן לבצע את ההורדה"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"גע כדי לנסות שוב"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"גע כדי להוריד"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"הורד או מחק"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"מוריד…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"תוקף ההודעה פג או שהיא לא זמינה"</string>
+ <string name="mms_info" msgid="3402311750134118165">"גודל: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, תפוגה: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"לא ניתן לשלוח. הנמען אינו חוקי."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"השירות לא הופעל ברשת"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"לא ניתן לשלוח בגלל בעיה ברשת"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"התוקף של ההודעה פג או שהיא לא זמינה"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(ללא נושא)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"שולח לא ידוע"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"נמסר"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"לא ניתן היה להוריד את ההודעה <xliff:g id="SUBJECT">%1$s</xliff:g> מ-<xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"לא ניתן היה להשלים את הפעולה של מסד הנתונים עקב זיכרון לא מספיק"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"ההודעה לא נשלחה"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"\'העברת הודעות\' לא שלח חלק מההודעות"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="two"><xliff:g id="MESSAGES_1">%d</xliff:g> הודעות ב-<xliff:g id="CONVERSATIONS">%d</xliff:g> שיחות</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> הודעות ב-<xliff:g id="CONVERSATIONS">%d</xliff:g> שיחות</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> הודעות ב-<xliff:g id="CONVERSATIONS">%d</xliff:g> שיחות</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> הודעות בשיחה אחת</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"ההודעה לא הורדה"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"\'העברת הודעות\' לא הוריד חלק מההודעות"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="two"><xliff:g id="MESSAGES_1">%d</xliff:g> הודעות ב-<xliff:g id="CONVERSATIONS">%d</xliff:g> שיחות</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> הודעות ב-<xliff:g id="CONVERSATIONS">%d</xliff:g> שיחות</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> הודעות ב-<xliff:g id="CONVERSATIONS">%d</xliff:g> שיחות</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> הודעות בשיחה אחת</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"ההודעה אל <xliff:g id="NUMBER">%1$s</xliff:g> לא נשלחה"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"התקשר לשירותי חירום. לא ניתן היה לשלוח את הודעת הטקסט אל <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="two"><xliff:g id="MESSAGES">%d</xliff:g> הודעות חדשות</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> הודעות חדשות</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> הודעות חדשות</item>
+ <item quantity="one">הודעה חדשה</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"התחל"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"המצלמה לא זמינה"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"המצלמה לא זמינה"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"לכידת וידאו אינה זמינה"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"לא ניתן לשמור מדיה"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"לא ניתן לצלם את התמונה"</string>
+ <string name="back" msgid="1477626055115561645">"הקודם"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"הועברו לארכיון"</string>
+ <string name="action_delete" msgid="4076795795307486019">"מחק"</string>
+ <string name="action_archive" msgid="5437034800324083170">"העבר לארכיון"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"הוצאה מארכיון"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"כבה התראות"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"הפעל התראות"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"הוסף איש קשר"</string>
+ <string name="action_download" msgid="7786338136368564146">"הורד"</string>
+ <string name="action_send" msgid="377635240181672039">"שלח"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"מחק"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"למחוק הודעה זו?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"פעולה זו אינה הפיכה."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"מחק"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="two">האם למחוק את השיחות האלו?</item>
+ <item quantity="many">האם למחוק את השיחות האלו?</item>
+ <item quantity="other">האם למחוק את השיחות האלו?</item>
+ <item quantity="one">האם למחוק את השיחה הזו?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"מחק"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"בטל"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"אל"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"בחר תמונות מרובות"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"אשר את הבחירה"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"לא ניתן להקליט אודיו. נסה שוב."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"לא ניתן להפעיל אודיו. נסה שוב."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"לא ניתן לשמור אודיו. נסה שוב."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"גע והחזק"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"תמונה"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"קטע אודיו"</string>
+ <string name="notification_video" msgid="4331423498662606204">"סרטון"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"כרטיס איש קשר"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"הורד"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"‏השב באמצעות SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"‏השב באמצעות MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"השב"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> משתתפים</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> משתתפים</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> משתתפים</item>
+ <item quantity="one">משתתף <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"אני"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"איש הקשר נחסם והועבר לארכיון"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"חסימת איש הקשר בוטלה והוא הוצא מהארכיון"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> הועברו לארכיון"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> הוצאו מהארכיון"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"ההודעות כבויות"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"ההודעות פועלות"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"הכל מוכן. גע באפשרות \'שלח שוב\'."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"‏האפליקציה \'העברת הודעות\' הוגדרה בהצלחה כאפליקציית ברירת המחדל ל-SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="two">מחק את הקבצים המצורפים</item>
+ <item quantity="many">מחק את הקבצים המצורפים</item>
+ <item quantity="other">מחק את הקבצים המצורפים</item>
+ <item quantity="one">מחק את הקובץ המצורף</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"קובץ אודיו מצורף"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"הפעל את קובץ האודיו המצורף"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"השהה"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"הודעה מאת <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"הודעה מאת <xliff:g id="SENDER">%s</xliff:g> שנכשלה: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"הודעה מאת <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"הודעה אל <xliff:g id="CONTACT">%s</xliff:g> שלא נשלחה: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"שולח הודעה אל <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"הודעה אל <xliff:g id="CONTACT">%s</xliff:g> שנכשלה: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"הודעה אל <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"הודעה שנכשלה מאת <xliff:g id="SENDER">%s</xliff:g>‏: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"הודעה מאת <xliff:g id="SENDER">%s</xliff:g>‏: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"הודעה אל <xliff:g id="GROUP">%s</xliff:g> שלא נשלחה: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"שולח הודעה אל <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"הודעה אל <xliff:g id="GROUP">%s</xliff:g> שנכשלה: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"הודעה אל <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. שעה: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ההודעה נכשלה. גע כדי לנסות שוב."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"שיחה עם <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"מחק נושא"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"צלם סרטון"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"צלם תמונת סטילס"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"צלם תמונה"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"התחל הקלטת סרטון"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"עבור למצלמה במסך מלא"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"החלף בין המצלמה הקדמית לאחורית"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"הפסק להקליט וצרף סרטון"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"עצור הקלטת וידאו"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"תמונות ב\'העברת הודעות\'"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="two"><xliff:g id="QUANTITY_2">%d</xliff:g> תמונות נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> תמונות נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> תמונות נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">תמונה <xliff:g id="QUANTITY_0">%d</xliff:g> נשמרה באלבום \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="two"><xliff:g id="QUANTITY_2">%d</xliff:g> סרטונים נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> סרטונים נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> סרטונים נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">סרטון <xliff:g id="QUANTITY_0">%d</xliff:g> נשמר באלבום \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="two"><xliff:g id="QUANTITY_2">%d</xliff:g> קבצים מצורפים נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> קבצים מצורפים נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> קבצים מצורפים נשמרו באלבום \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">קובץ מצורף <xliff:g id="QUANTITY_0">%d</xliff:g> נשמר באלבום \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="two"><xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים נשמרו ב\"הורדות\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים נשמרו ב\"הורדות\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים נשמרו ב\"הורדות\"</item>
+ <item quantity="one">קובץ מצורף <xliff:g id="QUANTITY_0">%d</xliff:g> נשמר ב\"הורדות\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="two"><xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים נשמרו</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים נשמרו</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים נשמרו</item>
+ <item quantity="one">קובץ מצורף <xliff:g id="QUANTITY_0">%d</xliff:g> נשמר</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="two">לא ניתן היה לשמור <xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים</item>
+ <item quantity="many">לא ניתן היה לשמור <xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים</item>
+ <item quantity="other">לא ניתן היה לשמור <xliff:g id="QUANTITY_1">%d</xliff:g> קבצים מצורפים</item>
+ <item quantity="one">לא ניתן היה לשמור קובץ מצורף <xliff:g id="QUANTITY_0">%d</xliff:g></item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"‏קובץ ה-MMS המצורף נשמר"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"הגדרות"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"הועברו לארכיון"</string>
+ <string name="action_close" msgid="1840519376200478419">"סגור"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"מתקדם"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ניפוי באגים"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"הודעות"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"צליל"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"מצב שקט"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"רטט"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"חסום"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"‏דוחות מסירת SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"‏בקש דוח מסירה לכל SMS שאתה שולח"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"אחזור אוטומטי"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"‏אחזור אוטומטי של הודעות MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"אחזור אוטומטי בנדידה"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"‏אחזר הודעות MMS באופן אוטומטי בזמן נדידה"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"העברת הודעות לקבוצה"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"‏השתמש ב-MMS לשליחת הודעה אחת לנמענים מרובים"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"‏אפליקציית SMS המוגדרת כברירת מחדל"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"‏אפליקציית SMS המוגדרת כברירת מחדל"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"מספר הטלפון שלך"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"לא ידוע"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"צלילים של הודעות יוצאות"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"‏צור Dump של SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"‏צור Dump בקובץ אחסון חיצוני עבור נתוני SMS גולמיים שהתקבלו"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"‏צור Dump של MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"‏צור Dump בקובץ אחסון חיצוני עבור נתוני MMS גולמיים שהתקבלו"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"התראות אלחוטיות"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"אפשרויות הודעה"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"העתק טקסט"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"הצגת פרטים"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"מחיקה"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"העבר"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"פרטי הודעה"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"סוג: "</string>
+ <string name="text_message" msgid="7415419755252205721">"הודעת טקסט"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"הודעת מולטימדיה"</string>
+ <string name="from_label" msgid="1947831848146564875">"מאת: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"אל: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"נשלחה ב: "</string>
+ <string name="received_label" msgid="4442494712757995203">"התקבלה ב: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"נושא: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"גודל: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"עדיפות: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"גבוהה"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"רגילה"</string>
+ <string name="priority_low" msgid="7398724779026801851">"נמוכה"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"‏SIM‏ <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"כתובת שולח מוסתרת"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"לא ניתן לשלוח הודעה בעת טעינת קבצים מצורפים."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"לא ניתן לטעון קובץ מצורף. נסה שוב."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"הרשת לא מוכנה. נסה שוב."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"מחק טקסט"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"עבור בין הזנה של אותיות וספרות"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"הוסף עוד משתמשים"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"אשר משתתפים"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"התחל שיחה חדשה"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"בחר בפריט הזה"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"הפעל סרטון"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"אנשים ואפשרויות"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ניפוי באגים"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"אנשים ואפשרויות"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"כללי"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"אנשים בשיחה הזו"</string>
+ <string name="action_call" msgid="6596167921517350362">"התקשר"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"שלח הודעה"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"‏שלח הודעה&lt;br/&gt;&lt;small&gt;מ-<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="two">שלח את התמונות</item>
+ <item quantity="many">שלח את התמונות</item>
+ <item quantity="other">שלח את התמונות</item>
+ <item quantity="one">שלח את התמונה</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="two">שלח את הקלטות האודיו</item>
+ <item quantity="many">שלח את הקלטות האודיו</item>
+ <item quantity="other">שלח את הקלטות האודיו</item>
+ <item quantity="one">שלח את הקלטת האודיו</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="two">שלח את הסרטונים</item>
+ <item quantity="many">שלח את הסרטונים</item>
+ <item quantity="other">שלח את הסרטונים</item>
+ <item quantity="one">שלח את הסרטון</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="two">שלח את כרטיסי אנשי הקשר</item>
+ <item quantity="many">שלח את כרטיסי אנשי הקשר</item>
+ <item quantity="other">שלח את כרטיסי אנשי הקשר</item>
+ <item quantity="one">שלח את כרטיס איש הקשר</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="two">שלח את הקבצים המצורפים</item>
+ <item quantity="many">שלח את הקבצים המצורפים</item>
+ <item quantity="other">שלח את הקבצים המצורפים</item>
+ <item quantity="one">שלח את הקובץ המצורף</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="two">יש <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> קבצים מצורפים המוכנים לשליחה</item>
+ <item quantity="many">יש <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> קבצים מצורפים המוכנים לשליחה</item>
+ <item quantity="other">יש <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> קבצים מצורפים המוכנים לשליחה</item>
+ <item quantity="one">יש קובץ מצורף אחד המוכן לשליחה</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"שלח משוב"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"‏הצג בחנות Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"פרטי גרסה"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"‏גרסה %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"רישיונות קוד פתוח"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"הודעות"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"צירפת את מספר הקבצים המקסימלי"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"טעינת הקובץ המצורף נכשלה."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"האם להוסיף לאנשי הקשר?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"הוסף איש קשר"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"נושא"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"נושא: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"טוען את כרטיס איש הקשר"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"לא ניתן לטעון כרטיס איש קשר"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"הצג את כרטיס איש הקשר"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> אנשי קשר</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> אנשי קשר</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> אנשי קשר</item>
+ <item quantity="one">איש קשר <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"כרטיסי אנשי קשר"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"יום הולדת"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"הערות"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"העבר הודעה"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"השב"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"‏SMS מושבת"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"‏כדי לשלוח, הגדר את האפליקציה \'העברת הודעות\' כאפליקציית ברירת המחדל ל-SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"‏הגדר את האפליקציה \'העברת הודעות\' כאפליקציית ברירת המחדל ל-SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"שנה"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"‏כדי לקבל הודעות, הגדר את \'העברת הודעות\' כאפליקציית ברירת המחדל ל-SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"‏לא נבחר SIM מועדף עבור שליחה של הודעות SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"אפליקציה זו אינה מורשית על ידי בעל המכשיר."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"אישור"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"משתתפים רבים מדי בשיחה"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="two">אנשי קשר לא חוקיים</item>
+ <item quantity="many">אנשי קשר לא חוקיים</item>
+ <item quantity="other">אנשי קשר לא חוקיים</item>
+ <item quantity="one">איש קשר לא חוקי</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"לא ניתן לטעון תמונת מצלמה"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"אתה: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"טיוטה"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"כשתתחיל שיחה חדשה, תראה אותה ברשימה כאן"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"שיחות שהועברו לארכיון מופיעות כאן"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"טוען שיחות..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"תמונה"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"קטע אודיו"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"סרטון"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"כרטיס איש קשר"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"בטל"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"נסה שוב"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"הזן שם איש קשר או מספר טלפון כדי להתחיל הודעה חדשה"</string>
+ <string name="action_block" msgid="9032076625645190136">"חסום"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"חסום את <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"בטל חסימה של <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"לחסום את <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"תמשיך לקבל הודעות ממספר זה אך לא יוצגו לך עוד דיווחים. שיחה זו תועבר לארכיון."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"אנשי קשר חסומים"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"אפשר"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"אנשי קשר חסומים"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"בחר תמונה מספריית המסמכים"</string>
+ <string name="sending_message" msgid="6363584950085384929">"שולח את ההודעה"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"ההודעה נשלחה"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"נתונים סלולריים כבויים. בדוק את ההגדרות."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"לא ניתן לשלוח הודעות במצב טיסה"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"לא ניתן היה לשלוח את ההודעה"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"הורדת ההודעה בוצעה"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"נתונים סלולריים כבויים. בדוק את ההגדרות."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"לא ניתן להוריד הודעות במצב טיסה"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"לא ניתן היה להוריד את ההודעה"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"אפס"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"אחת"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"שתיים"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"שלוש"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"ארבע"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"חמש"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"שש"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"שבע"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"שמונה"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"תשע"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"לא ניתן לשלוח הודעה דרך <xliff:g id="CARRIERNAME">%1$s</xliff:g>, שגיאה <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"לא ניתן לשלוח הודעה דרך ספק לא ידוע, שגיאה <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"העבר: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"ההודעה לא נשלחה: השירות לא הופעל ברשת."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"ההודעה לא נשלחה: כתובת היעד לא חוקית"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"ההודעה לא נשלחה: הודעה לא חוקית"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"ההודעה לא נשלחה: תוכן לא נתמך"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"ההודעה לא נשלחה: הודעה לא נתמכת"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"ההודעה לא נשלחה מפני שהיא גדולה מדי"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"הודעה חדשה"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"הצג"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"תמונה"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"לא נמצא יישום מתאים"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"הסר נמען"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"הודעה חדשה"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"בטל"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ערוך נקודת גישה"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"לא מוגדרת"</string>
+ <string name="apn_name" msgid="1572691851070894985">"שם"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"‏שרת Proxy של MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"‏יציאת MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"‏סוג APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"‏מחק APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"‏APN חדש"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"שמור"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"מחק"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"השדה \'שם\' לא יכול להיות ריק."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"‏ה-APN לא יכול להיות ריק."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"‏שדה MCC חייב להכיל 3 ספרות."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"‏שדה MNC חייב להכיל 2 או 3 ספרות."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"‏משחזר את הגדרות ברירת המחדל של APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"אפס לברירת מחדל"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"‏איפוס ההגדרות עבור ברירת המחדל של APN הושלם."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ללא שם"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"שמות של נקודות גישה"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"‏APN חדש"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"‏הגדרות עבור שם נקודת גישה (APN) אינן זמינות עבור המשתמש הזה"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"האם להעתיק אל לוח העריכה?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"העתק"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"אל <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"כללי"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"מתקדם"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"הגדרות כלליות"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"הגדרות מתקדמות"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"‏שלח הודעות SMS אישיות לכל הנמענים. רק אתה תקבל תשובות"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"‏שלח הודעת MMS אחת לכל הנמענים"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"מספר לא ידוע"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"הודעה חדשה"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"הודעה חדשה."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"‏בורר כרטיסי SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"‏<xliff:g id="SIM_0">%1$s</xliff:g> נבחר, בוחר כרטיסי SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"ערוך נושא"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"‏בחר כרטיס SIM או ערוך את הנושא"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"גע נגיעה ממושכת כדי להקליט אודיו"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"התחל שיחה חדשה"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"\'העברת הודעות\'"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"רשימת \'העברת הודעות\'"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"\'העברת הודעות\'"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"הודעה חדשה"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"רשימת שיחות"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"טוען שיחות"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"טוען הודעות"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"הצג שיחות נוספות"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"הצג עוד הודעות"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"השיחה נמחקה"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"השיחה נמחקה. גע כדי להציג שיחה אחרת ב\'העברת הודעות\'"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"חסום"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"חסימה בוטלה"</string>
+ <string name="db_full" msgid="8459265782521418031">"אין מספיק שטח אחסון. ייתכן שתאבד חלק מהנתונים."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"בחר קבצים מצורפים"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"אשר את הבחירה"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"בחרת <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"הסר קובץ מצורף אחד או יותר ונסה שוב."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"תוכל לנסות לשלוח את ההודעה, אך ייתכן שהיא לא תימסר אלא אם תסיר קובץ מצורף אחד או יותר."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"ניתן לשלוח רק סרטון אחד בכל הודעה. הסר סרטונים נוספים ונסה שוב."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"טעינת הקובץ המצורף נכשלה ב\'העברת הודעות\'."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"שלח בכל זאת"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"לא ניתן היה להתחיל את השיחה"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> נבחר"</string>
+</resources>
diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml
new file mode 100644
index 0000000..cf9e1a6
--- /dev/null
+++ b/res/values-ja/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"件名なし"</item>
+ <item msgid="272485471009191934">"件名なし"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"はい"</item>
+ <item msgid="6049132459802288033">"いいえ"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"笑"</item>
+ <item msgid="2611328818571146775">"ありがとう"</item>
+ <item msgid="4881335087096496747">"賛成"</item>
+ <item msgid="2422296858597420738">"ナイス"</item>
+ <item msgid="4805581752819452687">"すぐに連絡します"</item>
+ <item msgid="4746700499431366214">"後で連絡します"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..49df2fc
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"メッセージ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"メッセージ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"会話の選択"</string>
+ <string name="action_settings" msgid="1329008122345201684">"設定"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"メッセージを送信"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"添付ファイルを追加"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"ヘルプ"</string>
+ <string name="welcome" msgid="2857560951820802321">"ようこそ"</string>
+ <string name="skip" msgid="7238879696319945853">"スキップ"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"次へ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"次へ"</string>
+ <string name="exit" msgid="1905187380359981199">"終了"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"設定 &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"設定"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"SMSには、SMS、電話、連絡先に対する許可が必要です。"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"許可の変更は、[設定]&gt;[アプリ]&gt;[SMS]&gt;[許可]で行えます。"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"許可の変更は、[設定]、[アプリ]、[SMS]、[許可]で行えます。"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"よく使う連絡先"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"すべての連絡先"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g>に送信"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"写真や動画を撮影"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"この端末から画像を選択"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"音声の録音"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"写真を選択"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"メディアを選択しました。"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"メディアの選択を解除しました。"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g>件選択済み"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"画像<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" msgid="8142175029053370662">"画像"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"音声の録音"</string>
+ <string name="action_share" msgid="2143483844803153871">"共有"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"たった今"</string>
+ <string name="posted_now" msgid="867560789350406701">"今"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>分</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>分</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>時間</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>時間</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>日</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>日</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>週間</item>
+ <item quantity="one">1週間</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>か月</item>
+ <item quantity="one">1か月</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>年</item>
+ <item quantity="one">1年</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"クラス0メッセージ"</string>
+ <string name="save" msgid="5081141452059463572">"保存"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"端末の空き容量が少なくなっています。SMSでは空き容量を確保するため古いメッセージが自動的に削除されます。"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"空き容量が少なくなっています"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"端末上の空き容量が増えるまで、SMSでメッセージの送受信ができない場合があります。"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMSの空き容量が少なくなっています。メッセージの削除が必要な場合があります。"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"電話番号の確認"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"この1回の手順で、SMSはグループにメッセージを確実に配信します。"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"電話番号"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"メディア付きのメッセージをすべて削除"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g>より前のメッセージを削除"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g>より前のメッセージを自動削除"</string>
+ <string name="ignore" msgid="7063392681130898793">"無視"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"メディア付きのメッセージをすべて削除しますか?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g>より前のメッセージを削除しますか?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g>より前のメッセージを削除し、自動削除をONにしますか?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g>さんからのメッセージ"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"メッセージ内容"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g>さんからのメッセージ"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"メッセージを送信しました"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"送信しています…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"送信されていません。タップしてもう一度お試しください。"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"送信されていません。もう一度送信しています…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"再送信または削除"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"緊急通報には音声通話を使用してください。テキストメッセージを配信できませんでした。"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"失敗しました"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ダウンロードする新しいMMSメッセージ"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"新しいMMSメッセージ"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ダウンロードできませんでした"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"再試行するにはタップしてください"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ダウンロードするにはタップしてください"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ダウンロードまたは削除"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ダウンロードしています…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"メッセージが期限切れか参照できなくなっています"</string>
+ <string name="mms_info" msgid="3402311750134118165">"サイズ: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>、期限: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"送信できません。受信者が無効です。"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"サービスがネットワークで有効になっていません"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ネットワークに問題があるため送信できませんでした"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"メッセージが期限切れか参照できなくなっています"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(無題)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"送信者不明"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"配信済み"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"件名「<xliff:g id="SUBJECT">%1$s</xliff:g>」、送信者「<xliff:g id="FROM">%2$s</xliff:g>」のメッセージをダウンロードできませんでした。"</string>
+ <string name="low_memory" msgid="5300743415198486619">"メモリ不足のためデータベース操作を完了できませんでした"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"メッセージが送信されていません"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"SMSの一部のメッセージが送信されていません"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g>件のスレッドのメッセージ<xliff:g id="MESSAGES_1">%d</xliff:g>件</item>
+ <item quantity="one">1件のスレッドのメッセージ<xliff:g id="MESSAGES_0">%d</xliff:g>件</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"メッセージがダウンロードされていません"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"SMSの一部のメッセージがダウンロードされていません"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g>件のスレッドのメッセージ<xliff:g id="MESSAGES_1">%d</xliff:g>件</item>
+ <item quantity="one">1件のスレッドのメッセージ<xliff:g id="MESSAGES_0">%d</xliff:g>件</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g>へのメッセージは送信されませんでした"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"緊急通報には音声通話を使用してください。<xliff:g id="NUMBER">%1$s</xliff:g>へのテキストメッセージを配信できませんでした。"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g>件の新しいメッセージ</item>
+ <item quantity="one">新しいメッセージ</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"開始"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"カメラは利用できません"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"カメラは利用できません"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"動画撮影は利用できません"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"メディアを保存できません"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"写真を撮ることはできません"</string>
+ <string name="back" msgid="1477626055115561645">"戻る"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"アーカイブ済み"</string>
+ <string name="action_delete" msgid="4076795795307486019">"削除"</string>
+ <string name="action_archive" msgid="5437034800324083170">"アーカイブ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"アーカイブ解除"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"通知を無効化"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"通知を有効化"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"連絡先を追加"</string>
+ <string name="action_download" msgid="7786338136368564146">"ダウンロード"</string>
+ <string name="action_send" msgid="377635240181672039">"送信"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"削除"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"このメッセージを削除しますか?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"この操作は元に戻せません。"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"削除"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">これらのスレッドを削除しますか?</item>
+ <item quantity="one">このスレッドを削除しますか?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"削除"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"キャンセル"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"宛先"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"複数の画像を選択"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"選択の確認"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"音声を録音できません。もう一度お試しください。"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"音声を再生できません。もう一度お試しください。"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"音声を保存できませんでした。もう一度お試しください。"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"長押し"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"、 "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"写真"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"音声クリップ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"動画"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"連絡先カード"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ダウンロード"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMSで返信"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMSで返信"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"返信"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other">参加者<xliff:g id="COUNT_1">%d</xliff:g>人</item>
+ <item quantity="one">参加者<xliff:g id="COUNT_0">%d</xliff:g>人</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"自分"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"連絡先がブロックされ、アーカイブされました"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"連絡先をブロック解除し、アーカイブを解除しました"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g>件をアーカイブしました"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g>件のアーカイブを解除しました"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"通知をオフにしました"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"通知をオンにしました"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"すべて設定されました。もう一度[送信]をタップしてください。"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"SMSを既定のSMSアプリとして設定しました。"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">添付ファイルを破棄</item>
+ <item quantity="one">添付ファイルを破棄</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"音声添付ファイル"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"音声添付ファイルの再生"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"一時停止"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g>さんからのメッセージは「<xliff:g id="MESSAGE">%s</xliff:g>」です。"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>からのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を受信できませんでした(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>からのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を受信しました(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」が送信されていません(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を送信しています(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を送信できませんでした(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を送信しました(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>からのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を受信できませんでした(時間: <xliff:g id="TIME">%s</xliff:g>、<xliff:g id="GROUPINFO">%s</xliff:g>)。"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>からのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を受信しました(時間: <xliff:g id="TIME">%s</xliff:g>、<xliff:g id="GROUPINFO">%s</xliff:g>)。"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」が送信されていません(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を送信しています(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を送信できませんでした(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>へのメッセージ「<xliff:g id="MESSAGE">%s</xliff:g>」を送信しました(時間: <xliff:g id="TIME">%s</xliff:g>)。"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"失敗したメッセージ。再試行するにはタップしてください。"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g>とのスレッド"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"件名を削除"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"動画を撮影"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"静止画を撮影"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"画像を撮影"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"動画の録画を開始"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"全画面カメラに切り替える"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"前面カメラと背面カメラを切り替える"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"録画を停止し、動画を添付する"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"動画の録画を停止"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"SMSの写真"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g>枚の写真を「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」アルバムに保存しました</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g>枚の写真を「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」アルバムに保存しました</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g>本の動画を「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」アルバムに保存しました</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g>本の動画を「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」アルバムに保存しました</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g>個の添付ファイルを「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」アルバムに保存しました</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g>個の添付ファイルを「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」アルバムに保存しました</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>個の添付ファイルを「ダウンロード」に保存しました</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g>個の添付ファイルを「ダウンロード」に保存しました</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>個の添付ファイルを保存しました</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g>個の添付ファイルを保存しました</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>個の添付ファイルを保存できませんでした</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g>個の添付ファイルを保存できませんでした</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS添付ファイルを保存しました"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"設定"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"アーカイブ済み"</string>
+ <string name="action_close" msgid="1840519376200478419">"閉じる"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"詳細設定"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"デバッグ"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"通知"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"通知音"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"マナーモード"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"バイブレーション"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ブロック済み"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS受取確認通知"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"送信するSMSの配信レポートを毎回リクエストする"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"自動取得"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMSメッセージを自動的に取得する"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ローミング時に自動取得"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ローミング時にMMSを自動取得"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"グループメッセージング"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"宛先が複数ある場合にMMSを使用して1通のメッセージとして送信する"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"既定のSMSアプリ"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"既定のSMSアプリ"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"電話番号"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"不明"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"メッセージ送信音"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMSのダンプ"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"受信したSMS生データを外部ストレージファイルにダンプする"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMSのダンプ"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"受信したMMS生データを外部ストレージファイルにダンプする"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"無線警報"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"メッセージオプション"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"テキストをコピー"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"詳細を表示"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"削除"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"転送"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"メッセージの詳細"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"形式: "</string>
+ <string name="text_message" msgid="7415419755252205721">"テキストメッセージ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"マルチメディアメッセージ"</string>
+ <string name="from_label" msgid="1947831848146564875">"From: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"To: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"送信: "</string>
+ <string name="received_label" msgid="4442494712757995203">"受信: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"件名: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"サイズ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"優先度: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"高"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"標準"</string>
+ <string name="priority_low" msgid="7398724779026801851">"低"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"差出人アドレスは非表示"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"添付ファイルの読み込み中はメッセージを送信できません。"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"添付ファイルを読み込めません。もう一度お試しください。"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ネットワークの準備ができていません。もう一度お試しください。"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"テキストを削除"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"文字と数字の入力を切り替える"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"参加者を追加"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"参加者を確認"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"新しいスレッドを開始"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"この項目を選択する"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"動画を再生"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ユーザーとオプション"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"デバッグ"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ユーザーとオプション"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"全般"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"このスレッドのユーザー"</string>
+ <string name="action_call" msgid="6596167921517350362">"発信"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"メッセージの送信"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>から&lt;/small&gt;&amp;lt:br/&gt;メッセージを送信"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">写真を送信します</item>
+ <item quantity="one">写真を送信します</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">音声を送信します</item>
+ <item quantity="one">音声を送信します</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">動画を送信します</item>
+ <item quantity="one">動画を送信します</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">連絡先カードを送信します</item>
+ <item quantity="one">連絡先カードを送信します</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">添付ファイルを送信します</item>
+ <item quantity="one">添付ファイルを送信します</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g>個の添付ファイルを送信する準備ができました</item>
+ <item quantity="one">1個の添付ファイルを送信する準備ができました</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"フィードバックを送信"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Playストアで表示"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"バージョン情報"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"バージョン%1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"オープンソースライセンス"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"通知"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"添付ファイルの上限に達しました"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"添付ファイルを読み込めませんでした。"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"連絡先に追加しますか?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"連絡先を追加"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"件名"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"件名: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"連絡先カードを読み込んでいます"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"連絡先カードを読み込めませんでした"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"連絡先カードを表示"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>件の連絡先</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>件の連絡先</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"連絡先カード"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"誕生日"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"メモ"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"メールを転送"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"返信"</string>
+ <string name="plus_one" msgid="9010288417554932581">"、他1人"</string>
+ <string name="plus_n" msgid="8961547034116059566">"、他%d人"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMSは無効になっています"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"送信するには、SMSを既定のSMSアプリとして設定してください"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"SMSを既定のSMSアプリとして設定してください"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"変更"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"メッセージを受信するには、SMSを既定のSMSアプリとして設定してください"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMSメッセージ送信用の優先SIMが選択されていません"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"このアプリは端末の所有者によって許可されていません。"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"スレッドへの参加者が多すぎます"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">連絡先が無効です</item>
+ <item quantity="one">連絡先が無効です</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"カメラ画像を読み込めませんでした"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"あなた: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"下書き"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"新しいスレッドを開始すると、ここにリストが表示されます"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"アーカイブされたスレットがここに表示されます"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"スレッドを読み込んでいます…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"画像"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"音声クリップ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"動画"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"連絡先カード"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"元に戻す"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"再試行"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"新しいメッセージを開始するには連絡先の名前か電話番号を入力してください"</string>
+ <string name="action_block" msgid="9032076625645190136">"ブロック"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>さんをブロック"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g>さんのブロックを解除"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>をブロックしますか?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"この番号からのメッセージは引き続き受信しますが、通知は表示されなくなります。このスレッドはアーカイブされます。"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"ブロック中の連絡先"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ブロックを解除"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"ブロック中の連絡先"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ドキュメントライブラリから画像を選択"</string>
+ <string name="sending_message" msgid="6363584950085384929">"メールを送信しています"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"メッセージを送信しました"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"モバイルデータがOFFになっています。設定を確認してください。"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"機内モードではメッセージを送信できません"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"メッセージを送信できませんでした"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"メッセージをダウンロードしました"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"モバイルデータがOFFになっています。設定を確認してください。"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"機内モードではメッセージをダウンロードできません"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"メッセージをダウンロードできませんでした"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"0"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"1"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"2"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"3"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"4"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"5"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"6"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"7"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"8"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"9"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"メッセージを送信できません(<xliff:g id="CARRIERNAME">%1$s</xliff:g>)、エラー<xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"メッセージを送信できません(不明な携帯通信会社)、エラー<xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"メッセージの送信失敗: サービスがネットワークで有効になっていません"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"メッセージの送信失敗: 無効な宛先アドレスです"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"メッセージの送信失敗: 無効なメッセージです"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"メッセージの送信失敗: サポートされていないコンテンツです"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"メッセージの送信失敗: サポートされていないメッセージです"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"メッセージの送信失敗: 大きすぎます"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"新しいメッセージ"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"表示"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"画像"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"適切なアプリが見つかりませんでした"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"宛先を削除"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"新しいメッセージ"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"キャンセル"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"アクセスポイントの編集"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"未設定"</string>
+ <string name="apn_name" msgid="1572691851070894985">"名前"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMSプロキシ"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMSポート"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APNタイプ"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APNを削除"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"新しいAPN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"保存"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"破棄"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"[名前]欄は必須です。"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APNは必須です。"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC欄は3桁で指定してください。"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC欄は2桁か3桁で指定してください。"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"デフォルトのAPN設定を復元しています。"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"デフォルトにリセット"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"デフォルトのAPN設定にリセットしました。"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"無題"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"アクセスポイント名"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"新しいAPN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"このユーザーはアクセスポイント名設定を利用できません"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"クリップボードにコピーしますか?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"コピー"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"宛先: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"全般"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"詳細設定"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"全般設定"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"詳細設定"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"「<xliff:g id="SIM_NAME">%s</xliff:g>」SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"SMSメッセージを全員に個別に送信し、返信があなただけに返されるようにします"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"1つのMMSを全員に送信します"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"不明な番号"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"新しいメッセージ"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"新しいメッセージがあります。"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIMの切り替え"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"SIMの切り替え(<xliff:g id="SIM_0">%1$s</xliff:g>を選択済み)"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"件名を編集"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIMを選択するか件名を編集します"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"録音するには押し続けます"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"新しいスレッドを開始"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"SMSリスト"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"新しいメッセージ"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"スレッドリスト"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"スレッドを読み込んでいます"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"メールを読み込んでいます"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"他のスレッドを見る"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"他のメールを見る"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"スレッドを削除しました"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"スレッドを削除しました。タップしてSMSの別のスレッドを表示します"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ブロックしました"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"ブロックを解除しました"</string>
+ <string name="db_full" msgid="8459265782521418031">"空き容量が少なくなっています。一部のデータが失われる可能性があります。"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"添付ファイルの選択"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"選択の確認"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g>件選択済み"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"添付ファイルを1つ以上削除してから、もう一度お試しください。"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"メッセージの送信を試すことはできますが、添付ファイルを1つ以上削除しないと配信されない可能性があります。"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"送信できる動画は1件のメッセージにつき1本までです。追加した動画を削除してもう一度お試しください。"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"SMSで添付ファイルを読み込めませんでした。"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"このまま送信"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"スレッドを開始できませんでした"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g>(<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g>が選択されています"</string>
+</resources>
diff --git a/res/values-ka-rGE/arrays.xml b/res/values-ka-rGE/arrays.xml
new file mode 100644
index 0000000..0f50610
--- /dev/null
+++ b/res/values-ka-rGE/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"უსათაურო"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"კი"</item>
+ <item msgid="6049132459802288033">"არა"</item>
+ <item msgid="3084376867445867895">"კარგი"</item>
+ <item msgid="3155097332660174689">"ჰე-ჰე"</item>
+ <item msgid="2611328818571146775">"დიდი მადლობა"</item>
+ <item msgid="4881335087096496747">"თანახმა ვარ"</item>
+ <item msgid="2422296858597420738">"მშვენიერია"</item>
+ <item msgid="4805581752819452687">"მოვდივარ"</item>
+ <item msgid="4746700499431366214">"კარგი, მოგვიანებით შეგეხმიანები"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..9605c6b
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"შეტყობინებები"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"შეტყობინებები"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"მიმოწერის არჩევა"</string>
+ <string name="action_settings" msgid="1329008122345201684">"პარამეტრები"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"წერილის გაგზავნა"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"დანართის დამატება"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"დახმარება"</string>
+ <string name="welcome" msgid="2857560951820802321">"მოგესალმებით"</string>
+ <string name="skip" msgid="7238879696319945853">"გამოტოვება"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"შემდეგი &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"შემდეგი"</string>
+ <string name="exit" msgid="1905187380359981199">"გასვლა"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"პარამეტრები &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"პარამეტრები"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"შეტყობინებები საჭიროებს ნებართვას SMS-ზე, ტელეფონზე და კონტაქტებზე."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ნებართვების შეცვლა შეგიძლიათ აქ: პარამეტრები &gt; აპები &gt; შეტყობინებები &gt; ნებართვები."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ნებართვების შეცვლა შეგიძლიათ აქ: პარამეტრები, აპები, შეტყობინებები, ნებართვები."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"ხშირად გამოყენებული კონტაქტები"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ყველა კონტაქტი"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g>-ზე გაგზავნა"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"სურათების ან ვიდეოს გადაღება"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ამ მოწყობილობიდან სურათების არჩევა"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"აუდიოს ჩაწერა"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ფოტოს არჩევა"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"მედია შერჩეულია."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"მედია არ არის შერჩეული."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> შერჩეულია"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"სურათის <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" msgid="8142175029053370662">"სურათი"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"აუდიოს ჩაწერა"</string>
+ <string name="action_share" msgid="2143483844803153871">"გაზიარება"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ახლახანს"</string>
+ <string name="posted_now" msgid="867560789350406701">"ახლა"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> წთ.</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> წთ.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> საათის</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> საათის</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> დღის</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> დღის</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> კვირის</item>
+ <item quantity="one">კვირის</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> თვის</item>
+ <item quantity="one">თვის</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> წლის</item>
+ <item quantity="one">წლის</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Class 0 შეტყობინება"</string>
+ <string name="save" msgid="5081141452059463572">"შენახვა"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"მოწყობილობის მეხსიერებაში თავისუფალი სივრცე ცოტაა. შეტყობინებები ავტომატურად წაშლის ძველ შეტყობინებებს მეხსიერების გასათავისუფლებლად."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"თავისუფალი ადგილი იწურება"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"შეტყობინებებმა შესაძლოა ვერ მოახერხოს შეტყობინებების გაგზავნა ან მიღება მანამ, სანამ თქვენს მოწყობილობაზე ხელმისაწვდომი არ გახდება მეტი სივრცე."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS სივრცე იწურება. შესაძლოა, მოგიწიოთ ძველი შეტყობინებების წაშლა"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"დაადასტურეთ თქვენი ტელეფონის ნომერი"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ეს ერთჯერადი ნაბიჯი უზრუნველყოფს, რომ შეტყობინებებმა სწორად მოგაწოდოთ ჯგუფური შეტყობინებები."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ტელეფონის ნომერი"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"ყველა შეტყობინების წაშლა, რომელსაც მედია აქვს"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g>-ზე ძველი შეტყობინებების წაშლა"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g>-ზე ძველი შეტყობინებების ავტომატური წაშლა"</string>
+ <string name="ignore" msgid="7063392681130898793">"იგნორირება"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"წაიშალოს ყველა შეტყობინება, რომელსაც მედია აქვს?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"წაიშალოს <xliff:g id="DURATION">%s</xliff:g>-ზე ძველი შეტყობინებები?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"გსურთ <xliff:g id="DURATION">%s</xliff:g>-ზე უფრო ძველი შეტყობინებების წაშლა და ავტომატური წაშლის ჩართვა?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> თქვა"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"თქვენ სთქვით,"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"შეტყობინება <xliff:g id="SENDER">%s</xliff:g> -საგან"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"თქვენ გააგზავნეთ შეტყობინება"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"იგზავნება..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"არ გაიგზავნა. შეეხეთ ისევ საცდელად."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"არ გაიგზავნა. მიმდინარეობს ხელახლა ცდა…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ხელახლა გაგზავნა ან წაშლა"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"გამოიძახეთ საგანგებო სამსახურები ხმოვანი ზარით. ტექსტური შეტყობინების გაგზავნა ამჟამად შეუძლებელია."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"ვერ განხორციელდა"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ახალი ჩამოსატვირთი MMS შეტყობინება"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"ახალი MMS შეტ"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ჩამოტვირთვა ვერ მოხერხდა"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ხელახლა საცდელად შეეხეთ"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"შეეხეთ ჩამოსატვირთად"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ჩამოტვირთვა ან წაშლა"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"მიმდინარეობს ჩამოტვირთვა…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"შეტყობინებას ვადა გაუვიდა ან მიუწვდომელია."</string>
+ <string name="mms_info" msgid="3402311750134118165">"ზომა: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, ვადა: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"გაგზავნა ვერ ხერხდება. მიმღები არასწორია."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"სერვისი არ გააქტიურებულა ქსელში"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ქსელის პრობლემის გამო ვერ გაიგზავნა"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"შეტყობინებას ვადა გაუვიდა ან მიუწვდომელია."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(თემის გარეშე)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"უცნობი გამგზავნი"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"მიწოდებულია"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="SUBJECT">%1$s</xliff:g> შეტყობინების ჩამოტვირთვა ვერ მოხერხდა <xliff:g id="FROM">%2$s</xliff:g>-დან."</string>
+ <string name="low_memory" msgid="5300743415198486619">"მცირე მეხსიერების გამო მონაცემთა ბაზასთან დაკავშირებული ოპერაციის დასრულება ვერ მოხერხდა"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"შეტყობინება ვერ გაიგზავნა"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"შეტყობინებებში ზოგიერთი შეტყობინება ვერ გაიგზავნა"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> შეტყობინება <xliff:g id="CONVERSATIONS">%d</xliff:g> მიმოწერაში</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> შეტყობინება ერთ მიმოწერაში</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"შეტყობინება არ ჩამოტვირთულა"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"შეტყობინებებში ზოგიერთი შეტყობინება ვერ ჩამოიტვირთა"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> შეტყობინება <xliff:g id="CONVERSATIONS">%d</xliff:g> მიმოწერაში</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> შეტყობინება ერთ მიმოწერაში</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"შეტყობინება ნომერზე <xliff:g id="NUMBER">%1$s</xliff:g> ვერ გაიგზავნა"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"გამოიძახეთ საგანგებო სამსახურები ხმოვანი ზარით. ტექსტური შეტყობინების გაგზავნა ნომერზე <xliff:g id="NUMBER">%1$s</xliff:g> ამჟამად შეუძლებელია."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ახალი შეტყობინება</item>
+ <item quantity="one">ახალი შეტყობინება</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"დაწყება"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"კამერა მიუწვდომელია"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"კამერა მიუწვდომელია"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ვიდეო ჩაწერა მიუწვდომელია"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"მედიას შენახვა ვერ ვერხდება"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ვერ ხორციელდება სურათის გადაღება"</string>
+ <string name="back" msgid="1477626055115561645">"უკან"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"დაარქივებული"</string>
+ <string name="action_delete" msgid="4076795795307486019">"წაშლა"</string>
+ <string name="action_archive" msgid="5437034800324083170">"დაარქივება"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"დეარქივაცია"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"შეტყობინებების გამორთვა"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"შეტყობინებების ჩართვა"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"კონტაქტის დამატება"</string>
+ <string name="action_download" msgid="7786338136368564146">"ჩამოტვირთვა"</string>
+ <string name="action_send" msgid="377635240181672039">"გაგზავნა"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"წაშლა"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"წავშალო ეს შეტყობინება?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ამ ქმედებას ვერ გააუქმებთ."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"წაშლა"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">წაიშალოს ეს მიმოწერები?</item>
+ <item quantity="one">წაიშალოს ეს მიმოწერა?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"წაშლა"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"გაუქმება"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"მიმღები:"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"მრავალი სურათის არჩევა"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"არჩევანის დადასტურება"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ხმის ჩაწერა ვერ ხერხდენა. სცადეთ ისევ."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ხმის დაკვრა ვერ ხერხდება. სცადეთ ისევ."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ხმის ჩაწერა ვერ მოხერხდა. სცადეთ ისევ."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"შეეხეთ &amp; დააყოვნეთ"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"სურათი"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"აუდიო კლიპი"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ვიდეო"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"კონტაქტის ბარათი"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ჩამოტვირთვა"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS-ით პასუხი"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS-ით პასუხი"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"პასუხი"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> მონაწილე</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> მონაწილე</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"მე"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"კონტაქტი დაბლოკილია &amp; დაარქივებულია"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"კონტაქტი განბლოკილია &amp; amp; დაუარქივებელი"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> დაარქივებული"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> დაუარქივებელი"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"შეტყობინებები გამორთულია"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"შეტყობინებები ჩართულია"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"მზად არის. შეეხეთ ხელახლა გაგზავნას."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"შეტყობინებები წარმატებით დაყენებულია ნაგულისხმევ SMS აპად."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">დანართების გაუქმება</item>
+ <item quantity="one">დანართის გაუქმება</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"აუდიო დანართი"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"აუდიო დანართის ჩართვა"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"პაუზა"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"შეტყობინება <xliff:g id="SENDER">%s</xliff:g>-საგან: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>-საგან შეტყობინების შეცდომა: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"შეტყობინება <xliff:g id="SENDER">%s</xliff:g>-საგან: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>-სთვის გაუგზავნელი შეტყობინება: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"იგზავნება შეტყობინება <xliff:g id="CONTACT">%s</xliff:g>-სთვის: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>-სთვის შეტყობინების შეცდომა: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"შეტყობინება <xliff:g id="CONTACT">%s</xliff:g>-სთვის: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>-საგან შეტყობინების შეცდომა: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"შეტყობინება <xliff:g id="SENDER">%s</xliff:g>-საგან: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"გაუგზავნელი შეტყობინება <xliff:g id="GROUP">%s</xliff:g>-სთვის: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"იგზავნება შეტყობინება <xliff:g id="GROUP">%s</xliff:g>-სთვის: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>-სთვის შეტყობინების შეცდომა: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"შეტყობინება <xliff:g id="GROUP">%s</xliff:g>-სთვის: <xliff:g id="MESSAGE">%s</xliff:g>. დრო: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"შეტყობინების შეცდომა. შეეხეთ, რათა ხელახლა სცადოთ."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"საუბარი <xliff:g id="PARTICIPANTS">%s</xliff:g> -სთან"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"თემის წაშლა"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ვიდეოს გადაღება"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ფოტოსურათის აღბეჭდვა"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"სურათის გადაღება"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ვიდეოს ჩაწერის დაწყება"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"სრულეკრანიან კამერაზე გადართვა"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"წინა და უკანა კამერებს შორის გადართვა"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"ჩაწერის შეჩერება და ვიდეოს მიბმა"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ვიდეოს ჩაწერის შეჩერება"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"შეტყობინებების ფოტოები"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ფოტო შენახულია ალბომში „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ფოტო შენახულია ალბომში „<xliff:g id="ALBUMNAME_1">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ვიდეო შენახულია ალბომში „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ვიდეო შენახულია ალბომში „<xliff:g id="ALBUMNAME_1">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> მიმაგრებული ფაილი შენახულია ალბომში „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> მიმაგრებული ფაილი შენახულია ალბომში „<xliff:g id="ALBUMNAME_1">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> მიმაგრებული ფაილი შენახულია „ჩამოტვირთვებში“</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> მიმაგრებული ფაილი შენახულია „ჩამოტვირთვებში“</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">შენახულია <xliff:g id="QUANTITY_1">%d</xliff:g> დანართი</item>
+ <item quantity="one">შენახულია <xliff:g id="QUANTITY_0">%d</xliff:g> დანართი</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> დანართის შენახვა ვერ მოხერხდა</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> დანართის შენახვა ვერ მოხერხდა</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS დანართი შენახულია"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"პარამეტრები"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"დაარქივებული"</string>
+ <string name="action_close" msgid="1840519376200478419">"დახურვა"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"გაფართოებული"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"გამართვა"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"შეტყობინებები"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ხმა"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"დადუმებული"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"ვიბრაცია"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"დაბლოკილი"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS მისვლის შეტყობინებები"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ყოველ გაგზავნილ SMS-ზე მიწოდების დადასტურების მოთხოვნა"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"ავტო-მოძიება"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS შეტყობინებების ავტომატურად ჩამოტვირთვა"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ავტო ჩამოტვირთვა როუმინგში"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"როუმინგის დროს MMS-ის ავტომატური ჩამოტვირთვა"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"ჯგუფური შეტყობინება"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"მრავალი მიმღების შემთხვევაში ერთიანი შეტყობინების გასაგზავნად MMS-ის გამოყენება"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ნაგულისხმევი SMS აპლიკაცია"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ნაგულისხმევი SMS აპლიკაცია"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"თქვენი ტელეფონის ნომერი"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"უცნობი"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"გამავალი შეტყობინების ჟღერადობები"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS-ის ჩაწერა"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"მიღებული SMS-ის დაუმუშავებელი მონაცემების გარე საცავის ფაილში ჩაწერა"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS-ის ჩაწერა"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"მიღებული MMS-ის დაუმუშავებელი მონაცემების გარე საცავის ფაილში ჩაწერა"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"უსადენო გაფრთხილებები"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"შეტყობინების პარამეტრები"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ტექსტის კოპირება"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"დეტალების ნახვა"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"წაშლა"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"გადაგზავნა"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"შეტყობინების მონაცემები"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ტიპი: "</string>
+ <string name="text_message" msgid="7415419755252205721">"ტექსტური შეტყობინება"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"მულტიმედია შეტყობინება"</string>
+ <string name="from_label" msgid="1947831848146564875">"გამგზავნი: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"მიმღები: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"გაიგზავნა: "</string>
+ <string name="received_label" msgid="4442494712757995203">"მიღებულია: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"თემა: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"ზომა: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"პრიორიტეტი: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"მაღალი"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"ჩვეულებრივი"</string>
+ <string name="priority_low" msgid="7398724779026801851">"დაბალი"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"გამგზავნის მისამართი დამალულია"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"დანართების ჩატვირთვისას შეტყობინება ვერ გაიგზავნება."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"დანართი ვერ იტვირთება. სცადეთ ისევ."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ქსელი მზად არ არის. ისევ სცადეთ."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"შემდეგის წაშლა"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ტექსტისა და რიცხვების შეყვანას შორის გადართვა"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"მეტი მონაწილის დამატება"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"მონაწილეების დადასტურება"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"ახალი მიმოწერის დაწყება"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ამ ერთეულის არჩევა"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ვიდეოს დაკვრა"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ხალხი &amp; ვარიანტები"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"გამართვა"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ხალხი &amp; ვარიანტები"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"ზოგადი"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ამ საუბრის მონაწილეები"</string>
+ <string name="action_call" msgid="6596167921517350362">"დარეკვა"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"შეტყობინების გაგზავნა"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"შეტყობინების გაგზავნა&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>-დან&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">ფოტოების გაგზავნა</item>
+ <item quantity="one">ფოტოს გაგზავნა</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">აუდიო ჩანაწერების გაგზავნა</item>
+ <item quantity="one">აუდიო ჩანაწერის გაგზავნა</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">ვიდეოების გაგზავნა</item>
+ <item quantity="one">ვიდეოს გაგზავნა</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">კონტაქტის ბარათების გაგზავნა</item>
+ <item quantity="one">კონტაქტის ბარათის გაგზავნა</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">დანართების გაგზავნა</item>
+ <item quantity="one">დანართის გაგზავნა</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> დანართი მზად არის გასაგზავნად</item>
+ <item quantity="one">გასაგზავნად მზად არის ერთი დანართი</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"უკუკავშირის გაგზავნა"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store-ში ნახვა"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ვერსიის ინფორმაცია"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"ვერსია %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ღია კოდის ლიცენზიები"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"შეტყობინებები"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"მიღწეულია დანართების ლიმიტი"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"დანართი ვერ ჩაიტვირთა."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"დაემატოს კონტაქტებში?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"კონტაქტის დამატება"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"თემა"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"თემა: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"იტვირთება კონტაქტის ბარათი"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"საკონტაქტო ბარათი ვერ იტვირთება"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"კონტაქტის ბარათის ნახვა"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> კონტაქტი</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> კონტაქტი</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"კონტაქტის ბარათები"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"დაბადების თარიღი"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ჩანიშვნები"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"შეტყობინების გადაგზავნა"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"პასუხი"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS გამორთულია"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"გასაგზავნად დააყენეთ შეტყობინებები ნაგულისხმევ SMS აპად"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"დააყენეთ შეტყობინებები ნაგულისხმევ SMS აპად"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"შეცვლა"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"შეტყობინებების მისაღებად დააყენეთ შეტყობინებები ნაგულისხმევ SMS აპად"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"მოკლე ტექსტური შეტყობინებების გაგზავნისათვის, არ არის არჩეული უპირატესი SIM ბარათი"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ეს აპი მოწყობილობის მფლობელის მიერ ნებადართული არ არის."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"კარგი"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"მიმოწერაში ძალიან ბევრი მონაწილეა"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">კონტაქტები არასწორია</item>
+ <item quantity="one">კონტაქტი არასწორია</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"კამერის სურათი ვერ ჩაიტვირთა"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"თქვენ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"მონახაზი"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"როდესაც საუბარს წამოიწყებთ, ისინი აქ გამოჩნდება"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"დაარქივებული მიმოწერები აქ გამოჩნდება"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"მიმოწერები იტვირთება…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"სურათი"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"აუდიო კლიპი"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ვიდეო"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"კონტაქტის ბარათი"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"დაბრუნება"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ხელახლა ცდა"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"ახალი შეტყობინების დასაწყებად შეიყვანეთ საკონტაქტო სახელი ან ტელეფონის ნომერი"</string>
+ <string name="action_block" msgid="9032076625645190136">"დაბლოკვა"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>-ის დაბლოკვა"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g>-ის განბლოკვა"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>-ის დაბლოკვა?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ამ ნომრიდან შეტყობინებებს კვლავ მიიღებთ, თუმცა შეხსენებები აღარ გამოგეგზავნებათ. ეს მიმოწერა დაარქივდება."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"დაბლოკილი კონტაქტები"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"განბლოკვა"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"დაბლოკილი კონტაქტები"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"აირჩიეთ სურათი დოკუმენტების ბიბლიოთეკიდან"</string>
+ <string name="sending_message" msgid="6363584950085384929">"შეტყობინება იგზავნება"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"შეტყობინება გაიგზავნა"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"მობილური ინტერნეტი გამორთულია. გადაამოწმეთ პარამეტრები."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"თვითმფრინავის რეჟიმში შეტყობინებების გაგზავნა ვერ ხერხდება"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"შეტყობინება ვერ გაიგზავნა"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"შეტყობინება გადმოწერილია"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"მობილური ინტერნეტი გამორთულია. გადაამოწმეთ პარამეტრები."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"თვითმფრინავის რეჟიმში შეტყობინების ჩამოტვირთვა ვერ ხორციელდება"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"შეტყობინების ჩამოტვირთვა ვერ ხორციელდება"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"ნული"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ერთი"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"ორი"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"სამი"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"ოთხი"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ხუთი"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ექვსი"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"შვიდი"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"რვა"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ცხრა"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"შეტყობინება ვერ იგზავნება: <xliff:g id="CARRIERNAME">%1$s</xliff:g>-ის შეცდომა <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"შეტყობინება ვერ იგზავნება უცნობი ოპერატორით, შეცდომა <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"შეტყობინება ვერ გაიგზავნა: სერვისი ქსელში გააქტიურებელი არ არის"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"შეტყობინება ვერ გაიგზავნა: სამიზნე მისამართი არასწორია"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"შეტყობინება ვერ გაიგზავნა: შეტყობინება არასწორია"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"შეტყობინება ვერ გაიგზავნა: კონტენტი მხარდაჭერილი არ არის"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"შეტყობინება ვერ გაიგზავნა: შეტყობინება მხარდაჭერილი არ არის"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"შეტყობინება არ გაიგზავნა: ის ძალიან დიდია"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"ახალი შეტყობინება"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"ნახვა"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"სურათი"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"შესაბამისი აპლიკაცია ვერ მოიძებნა"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"მიმღების ამოღება"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"ახალი შეტყობინება"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"გაუქმება"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"წვდომის წერტილის რედაქტირება"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"არ არის დაყენებული"</string>
+ <string name="apn_name" msgid="1572691851070894985">"სახელი"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS პროქსი-სერვერი"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS პორტი"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN ტიპი"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN-ის წაშლა"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"ახალი APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"შენახვა"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"გაუქმება"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"სახელის ველი არ შეიძლება იყოს ცარიელი."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN არ უნდა იყოს ცარიელი."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ველი უნდა შეიცავდეს 3 ციფრს."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ველი უნდა შეიცავდეს ორ ან სამ ციფრს."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ნაგულისხმევი APN პარამეტრების აღდგენა."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"საწყის პარამეტრებზე ჩამოყრა"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"ნაგულისხმევი APN პარამეტრების აღდგენა დასრულებულია."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"უსათაურო"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"წვდომის წერტილის სახელები (APNs)"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-ები"</string>
+ <string name="menu_new" msgid="8286285392706532511">"ახალი APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"წვდომის წერტილის (APN) პარამეტრები ამ მომხმარებლისათვის მიუწვდომელია"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"გსურთ ბუფერში კოპირება?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"კოპირება"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g>-ზე"</string>
+ <string name="general_settings" msgid="5409336577057897710">"ზოგადი"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"გაფართოებული"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"ძირითადი პარამეტრები"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"დამატებითი პარამეტრები"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"„<xliff:g id="SIM_NAME">%s</xliff:g>“ SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ყველა მიმღებთან ინდივიდუალური SMS შეტყობინების გაგზავნა. პასუხებს მხოლოდ თქვენ მიიღებთ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"ყველა მიმღებთან ერთი MMS-ის გაგზავნა"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"უცნობი ნომერი"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"ახალი შეტყობინება"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"ახალი შეტყობინება."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM სელექტორი"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"არჩეულია <xliff:g id="SIM_0">%1$s</xliff:g>, SIM სელექტორი"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"თემის რედაქტირება"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"აირჩიეთ SIM ან შეცვალეთ სათაური"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"აუდიო ჩაწერისათვის შეეხეთ და დააყოვნეთ"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"ახალი მიმოწერის დაწყება"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"შეტყობინებები"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"შეტყობინებების სია"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"შეტყობინებები"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"ახალი შეტყობინება"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"მიმოწერათა სია"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"მიმოწერები იტვირთება…"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"მიმდინარეობს შეტყობინებების ჩატვირთვა"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"მეტი მიმოწერის ნახვა"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"იხილეთ სხვა შეტყობინებები"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"მიმოწერა წაშლილია"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"მიმოწერა წაშლილია. შეეხეთ შეტყობინებების სხვა მიმოწერის საჩვენებლად"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"დაბლოკილი"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"განბლოკილი"</string>
+ <string name="db_full" msgid="8459265782521418031">"შენახვის სივრცეში შემცირებულია. ზოგიერთი მონაცემი შეიძლება დაიკარგოს."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"დანართის არჩევა"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"არჩევანის დადასტურება"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> შერჩეულია"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"წაშალეთ ერთი ან რამდენიმე დანართი და ხელახლა სცადეთ."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"შეგიძლიათ სცადოთ შეტყობინების გაგზავნა, მაგრამ შეიძლება მისი მიწოდება ვერ მოხერხდეს, თუ არ წაშლით ერთ ან რამდენიმე დანართს."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"შეტყობინებით თქვენ შეგიძლიათ გააგზავნოთ მხოლოდ ერთი ვიდეო. გთხოვთ ამოშალეთ დამატებითი ვიდეოები და სცადეთ კიდევ ერთხელ."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"შეტყობინებებში დანართის ჩატვირთვა ვერ მოხერხდა."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"მაინც გაგზავნა"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"საუბრის დაწყება ვერ მოხერხდა"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"შერჩეულია <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-kk-rKZ/arrays.xml b/res/values-kk-rKZ/arrays.xml
new file mode 100644
index 0000000..b49d0cc
--- /dev/null
+++ b/res/values-kk-rKZ/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"тақырыпсыз"</item>
+ <item msgid="272485471009191934">"тақырып жоқ"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Иә"</item>
+ <item msgid="6049132459802288033">"Жоқ"</item>
+ <item msgid="3084376867445867895">"Жарайды"</item>
+ <item msgid="3155097332660174689">"Хехе"</item>
+ <item msgid="2611328818571146775">"Рақмет"</item>
+ <item msgid="4881335087096496747">"Келісемін"</item>
+ <item msgid="2422296858597420738">"Жақсы"</item>
+ <item msgid="4805581752819452687">"Жолдамын"</item>
+ <item msgid="4746700499431366214">"Жарайды, кейінірек хабарласамын"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
new file mode 100644
index 0000000..5164592
--- /dev/null
+++ b/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Хабар алмасу"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Хабар алмасу"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Сөйлесуді таңдау"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Параметрлер"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Хабар жіберу"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Тіркеме қосу"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Анықтама"</string>
+ <string name="welcome" msgid="2857560951820802321">"Қош келдіңіз"</string>
+ <string name="skip" msgid="7238879696319945853">"Өткізіп жіберу"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Келесі &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Келесі"</string>
+ <string name="exit" msgid="1905187380359981199">"Шығу"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Параметрлер &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Параметрлер"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaging қолданбасына SMS, \"Телефон\" және \"Контактілер\" қолданбаларына рұқсат қажет."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Рұқсаттарды \"Параметрлер\" &gt; \"Қолданбалар\" &gt; \"Messaging\" &gt; \"Рұқсаттар\" тармағында өзгертуге болады."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Рұқсаттарды \"Параметрлер\", \"Қолданбалар\", \"Messaging\", \"Рұқсаттар\" тармағында өзгерте аласыз."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Жиі қолданылатын"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Барлық контактілер"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> орнына жіберу"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Суреттер немесе бейне түсіру"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Осы құрылғыдан кескіндерді таңдау"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Аудио жазу"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Фотосуретті таңдау"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Медиа таңдалды."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Медиадан таңдау алынды."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> таңдалды"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"кескін <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" msgid="8142175029053370662">"кескін"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Аудио жазу"</string>
+ <string name="action_share" msgid="2143483844803153871">"Бөлісу"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Қазір ғана"</string>
+ <string name="posted_now" msgid="867560789350406701">"Қазір"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> мин.</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> мин.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> сағат</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> сағат</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> күн</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> күн</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> апта</item>
+ <item quantity="one">апта</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ай</item>
+ <item quantity="one">ай</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> жыл</item>
+ <item quantity="one">жыл</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0 сыныпты хабар"</string>
+ <string name="save" msgid="5081141452059463572">"Сақтау"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Құрылғыдағы бос орын аз. \"Хабар алмасу\" орын босату үшін ескі хабарларды автоматты түрде жояды."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Жадта бос орын бітуде"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"\"Хабар алмасу\" қолданбасы құрылғыда көбірек бос орын қол жетімді болғанша хабарлар жібере немесе ала алмауы мүмкін."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS жадында орын аз. Хабарларды жою керек болуы мүмкін."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Телефон нөміріңізді растау"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Бұл бір реттік қадам Messaging қолданбасының топтық хабарларды тиісті түрде жеткізуін қамтамасыз етеді."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Телефон нөмірі"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Тасушы арқылы барлық хабарларды жою"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> уақыттан асқан хабарларды жою"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> ескірек хабарларды автоматты түрде жою"</string>
+ <string name="ignore" msgid="7063392681130898793">"Елемеу"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Медиа бар барлық хабарларды жою керек пе?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> ескірек хабарларды жою керек пе?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> ескірек хабарларды жою және автоматты түрде жоюды қосу керек пе?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> айтты"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Сіз айттыңыз"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Хабар, кімнен: <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Сіз хабар жібердіңіз"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Жіберілуде…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Жіберілмеді. Әрекетті қайталау үшін түртіңіз."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Жіберілмеді. Әрекет қайталануда…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Қайта жіберу немесе жою"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Жедел қызметтерге дауыстық қоңырау соғыңыз. Бұл ретте мәтіндік хабарды жеткізу мүмкін болмады."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Сәтсіз аяқталды"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Жүктейтін жаңа MMS хабары бар"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Жаңа MMS хабары"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Жүктеу мүмкін болмады"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Әрекетті қайталау үшін түртіңіз"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Жүктеу үшін түртіңіз"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Жүктеу немесе жою"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Жүктелуде…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Хабар мерзімі бітті немесе ол қол жетімді емес"</string>
+ <string name="mms_info" msgid="3402311750134118165">"өлшемі: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, мерзімі бітетін уақыт: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Жіберу мүмкін емес. Алушы жарамсыз."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Қызмет желіде белсендірілмеген"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Желілік мәселеге байланысты жіберу мүмкін болмады"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Хабар мерзімі бітті немесе ол қол жетімді емес"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Тақырыпсыз)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Белгісіз хабар жіберуші"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Жеткізілді"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="SUBJECT">%1$s</xliff:g> хабарын <xliff:g id="FROM">%2$s</xliff:g> арқылы жүктеу мүмкін болмады."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Жад көлемінің аз болуына байланысты дерекқор әрекетін аяқтау мүмкін болмады"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Хабар жіберілмеді"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Messaging қолданбасында кейбір хабарлар жіберілмеген"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> сөйлесуде <xliff:g id="MESSAGES_1">%d</xliff:g> хабар</item>
+ <item quantity="one">бір сөйлесуде <xliff:g id="MESSAGES_0">%d</xliff:g> хабар</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Хабар жүктелмеген"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Messaging қолданбасында кейбір хабарлар жүктелмеген"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> сөйлесуде <xliff:g id="MESSAGES_1">%d</xliff:g> хабар</item>
+ <item quantity="one">бір сөйлесуде <xliff:g id="MESSAGES_0">%d</xliff:g> хабар</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> нөміріне хабар жіберілмеді"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Жедел қызметтерге дауыстық қоңырау соғыңыз. Бұл ретте <xliff:g id="NUMBER">%1$s</xliff:g> нөміріне мәтіндік хабарды жеткізу мүмкін болмады."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> жаңа хабар</item>
+ <item quantity="one">Жаңа хабар</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Бастау"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Камера қол жетімді емес"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Камера қол жетімді емес"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Бейне түсіру қол жетімді емес"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Медианы сақтау мүмкін емес"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Сурет түсіру мүмкін емес"</string>
+ <string name="back" msgid="1477626055115561645">"Артқа"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Мұрағатталды"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Жою"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Мұрағаттау"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Мұрағаттан шығару"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Хабарландыруларды өшіру"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Хабарландыруларды қосу"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Контакт қосу"</string>
+ <string name="action_download" msgid="7786338136368564146">"Жүктеу"</string>
+ <string name="action_send" msgid="377635240181672039">"Жіберу"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Жою"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Бұл хабарды жою қажет пе?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Бұл әрекетті болдырмау мүмкін емес."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Жою"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Осы сөйлесулерді жою қажет пе?</item>
+ <item quantity="one">Осы сөйлесуді жою қажет пе?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Жою"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Бас тарту"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Кімге"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Бірнеше кескінді таңдау"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Таңдауды растау"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Аудионы жазу мүмкін емес. Әрекетті қайталаңыз."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Аудионы ойнату мүмкін емес. Әрекетті қайталаңыз."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Аудионы сақтау мүмкін болмады. Әрекетті қайталаңыз."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Түрту және ұстап тұру"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Фотосурет"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудиоклип"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Бейне"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Контакт карточкасы"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Жүктеу"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS арқылы жауап беру"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS ар. жау. б."</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Жауап беру"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> қатысушы</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> қатысушы</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Мен"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Контакт бөгелді және мұрағатталды"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Контакт құлыптан босатылды және мұрағаттан шығарылды"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> мұрағатталды"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> мұрағаттан шығарылды"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Хабарландырулар өшірілген"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Хабарландырулар қосылған"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Бәрі орнатылды. «Қайта жіберу» пәрменін түртіңіз."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"\"Хабар алмасу\" қолданбасы әдепкі SMS қолданбасы ретінде сәтті орнатылды."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Тіркемелерді алып тастау</item>
+ <item quantity="one">Тіркемені алып тастау</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Аудио тіркеме"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Аудио тіркемені ойнату"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Кідірту"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> хабары: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> сәтсіз хабары: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> хабары: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> тобына жіберілмеген хабар: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> тобына хабар жіберілуде: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> тобына сәтсіз хабар: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> тобына хабар: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> сәтсіз хабары: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> хабары: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> тобына жіберілмеген хабар: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> тобына хабар жіберілуде: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> тобына сәтсіз хабар: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> тобына хабар: <xliff:g id="MESSAGE">%s</xliff:g>. Уақыт: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Сәтсіз хабар. Қайталау үшін түртіңіз."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> адаммен сөйлесу"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Тақырыпты жою"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Бейне түсіру"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Фотосурет түсіру"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Сурет түсіру"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Бейне жазуды бастау"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Толық экранды камераға ауысу"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Алдыңғы және артқы камера арасында ауысу"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Жазуды тоқтату және бейне тіркеу"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Бейне жазуды тоқтату"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging фотосуреттері"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> фотосурет «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» альбомына сақталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> фотосурет «<xliff:g id="ALBUMNAME_1">%s</xliff:g>» альбомына сақталды</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> бейне «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» альбомына сақталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> бейне «<xliff:g id="ALBUMNAME_1">%s</xliff:g>» альбомына сақталды</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> тіркеме «<xliff:g id="ALBUMNAME_3">%s</xliff:g>» альбомына сақталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тіркеме «<xliff:g id="ALBUMNAME_1">%s</xliff:g>» альбомына сақталды</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> тіркеме «Жүктеулер» ішіне сақталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тіркеме «Жүктеулер» ішіне сақталды</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> тіркеме сақталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тіркеме сақталды</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> тіркемені сақтау мүмкін болмады</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тіркемені сақтау мүмкін болмады</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS тіркемесі сақталды"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Параметрлер"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Мұрағатталды"</string>
+ <string name="action_close" msgid="1840519376200478419">"Жабу"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Кеңейтілген"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Күйін келтіру"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Хабарландырулар"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Дыбыс"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Үнсіз"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Дірілдеу"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Бөгелген"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS жеткізу туралы есептер"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Әр жіберілген SMS үшін жеткізу туралы есепті сұрау"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Авто шығарып алу"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS хабарларын автоматты түрде шығарып алу"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Роуминг кезінде авто-шығарып алу"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Роумингте MMS автоматты түрде шығарып алу"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Топтық хабар алмасу"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Бірнеше алушыға бір хабар жіберу үшін MMS пайдалану"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Әдепкі SMS қолданбасы"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Әдепкі SMS қолданбасы"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Телефон нөміріңіз"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Белгісіз"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Шығыс хабар дыбыстары"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS дампын сақтау"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Алынған SMS өңделмеген деректерінің дампын сыртқы қойма файлына сақтау"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS дампын сақтау"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Алынған MMS өңделмеген деректерінің дампын сыртқы қойма файлына сақтау"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Сымсыз дабылдар"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Хабар опциялары"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Мәтінді көшіру"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Мәліметтерді көру"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Жою"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Қайта жіберу"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Хабар мәліметтері"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tүрі: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Мәтіндік-хабар"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mультимедиалық хабар"</string>
+ <string name="from_label" msgid="1947831848146564875">"Кімнен: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Кімге: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Жіберілді: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Қабылданды: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Тақырып: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Өлшем: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Басымдық: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Жоғары"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Қалыпты"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Төмен"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Жіберуші мекенжайы жасырын"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Тіркемелерді жүктеу кезінде хабар жіберу мүмкін емес."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Тіркемені жүктеу мүмкін емес. Әрекетті қайталаңыз."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Желі дайын емес. Әрекетті қайталаңыз."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Мәтінді жою"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Мәтінді және сандарды енгізу арасында ауысу"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Қосымша қатысушыларды қосу"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Қатысушыларды растау"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Жаңа сөйлесуді бастау"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Осы элементті таңдау"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Бейнені ойнату"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Адамдар және опциялар"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Күйін келтіру"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Адамдар және опциялар"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Жалпы"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Осы сөйлесудегі адамдар"</string>
+ <string name="action_call" msgid="6596167921517350362">"Қоңырау шалу"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Хабарды жіберу"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; картасынан хабар жіберу"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Фотосуреттер жіберу</item>
+ <item quantity="one">Фотосурет жіберу</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Аудиолар жіберу</item>
+ <item quantity="one">Аудио жіберу</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Бейнелер жіберу</item>
+ <item quantity="one">Бейне жіберу</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Контакті карточкаларын жіберу</item>
+ <item quantity="one">Контакті карточкасын жіберу</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Тіркемелер жіберу</item>
+ <item quantity="one">Тіркеме жіберу</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> тіркеме жіберуге дайын</item>
+ <item quantity="one">Бір тіркеме жіберуге дайын</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Пікір жіберу"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store дүкенінде көру"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Нұсқа туралы ақпарат"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Нұсқа: %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Ашық бастапқы код лицензиялары"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Хабарландырулар"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Тіркеме шегіне жеттіңіз"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Тіркемені жүктеу сәтсіз аяқталды."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Контактілерге қосу керек пе?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Контакт қосу"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Тақырып"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Тақырып: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Контакт карточкасы жүктелуде"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Контакт картасын жүктеу мүмкін болмады"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Контакт карточкасын көру"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> контакт</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> контакт</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Контакт карталары"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Туған күн"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Ескертпелер"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Хабарды қайта жіберу"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Жауап беру"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS өшірілген"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Жіберу үшін \"Хабар алмасу\" қолданбасын әдепкі SMS қолданбасы ретінде орнатыңыз"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"\"Хабар алмасу\" қолданбасын әдепкі SMS қолданбасы ретінде орнату"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Өзгерту"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Хабарлар алу үшін Messaging қолданбасын әдепкі SMS қолданбасы ретінде орнатыңыз"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS хабарларын жіберу үшін таңдаулы SIM таңдалмаған"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Құрылғы иесі бұл қолданбаға рұқсат етпеген."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Жарайды"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Сөйлесуде қатысушылар тым көп"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Жарамсыз контактілер</item>
+ <item quantity="one">Жарамсыз контакті</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Камера кескінін жүктеу мүмкін болмады"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Сіз: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Жоба"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Басталған жаңа сөйлесуді осы жерде тізімнен көресіз"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Мұнда мұрағатталған сөйлесулер көрсетіледі"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Сөйлесулер жүктелуде…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Сурет"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудиоклип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Бейне"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Контакт карточкасы"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Болдырмау"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Қайталау"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Жаңа хабарды бастау үшін контакт атын немесе телефон нөмірін енгізіңіз"</string>
+ <string name="action_block" msgid="9032076625645190136">"Бөгеу"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> бөгеу"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> бөгеуден шығару"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> бөгеу керек пе?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Сіз осы нөмірден хабарлар алуды жалғастырасыз, бірақ енді хабардар етілмейсіз. Бұл сөйлесу мұрағатталады."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Бөгелген контактілер"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"БӨГЕУДЕН ШЫҒАРУ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Бөгелген контактілер"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Құжаттар кітапханасынан кескінді таңдау"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Хабар жіберілуде"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Хабар жіберілді"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Ұялы деректер өшірілген. Параметрлерді тексеріңіз."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Ұшақ режимінде хабарлар жіберу мүмкін емес"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Хабарды жіберу мүмкін болмады"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Хабар жүктелді"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Ұялы деректер өшірілген. Параметрлерді тексеріңіз."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Ұшақ режимінде хабарларды жүктеу мүмкін емес"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Хабарды жүктеу мүмкін болмады"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Нөл"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Бір"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Екі"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Үш"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Төрт"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Бес"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Алты"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Жеті"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Сегіз"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Тоғыз"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> арқылы хабар жіберілмейді, <xliff:g id="ERRORCODE">%2$d</xliff:g> қатесі"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Белгісіз оператормен хабар жіберілмейді, <xliff:g id="ERRORCODE">%1$d</xliff:g> қатесі"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Қайта жіберілген: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Хабар жіберілмеді, желіде қызмет белсендірілмеген"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Хабар жіберілмеді: жарамсыз межелі мекенжай"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Хабар жіберілмеді: жарамсыз хабар"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Хабар жіберілмеді: қолдау көрсетілмейтін мазмұн"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Хабар жіберілмеді: қолдау көрсетілмейтін хабар"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Хабар жіберілмеді: тым үлкен"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Жаңа хабар"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Көру"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Кескін"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Тиісті қолданбаны табу мүмкін болмады"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Алушыны жою"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Жаңа хабар"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Бас тарту"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Кіру нүктесін өңдеу"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Орнатылмаған"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Аты"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS прокси"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS порты"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN түрі"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN жою"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Жаңа APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Сақтау"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Алып тастау"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Атау өрісі бос болмауы тиіс."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN бос болмауы тиіс."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC өрісі 3 цифрдан тұруы тиіс."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC өрісі 2 немесе 3 цифрдан тұруы тиіс."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Әдепкі APN параметрлерін қалпына келтіру."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Әдепкі параметрлерге қалпына келтіру"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Әдепкі APN параметрлерін қалпына келтіру аяқталды."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Атаусыз"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Кіру нүктесі атаулары"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs (Кіру нүктесі атаулары)"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Жаңа APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Кіру нүктесі атауының параметрлері осы пайдаланушы үшін қол жетімді емес"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Аралық сақтағышқа көшіру керек пе?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Көшіру"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> картасына"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Жалпы"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Кеңейтілген"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Жалпы параметрлер"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Қосымша параметрлер"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"«<xliff:g id="SIM_NAME">%s</xliff:g>» SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Барлық алушыларға SMS хабарларын жіберіңіз. Жауаптарды тек сіз аласыз"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Барлық алушыларға бір MMS жіберу"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Белгісіз нөмір"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Жаңа хабар"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Жаңа хабар"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM таңдағыш"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> таңдалды, SIM таңдағыш"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Тақырыпты өңдеу"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM картасын таңдау немесе тақырыпты өңдеу"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Аудио жазу үшін түртіп, ұстап тұрыңыз"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Жаңа сөйлесуді бастау"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Хабар алмасу"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Хабар алмасу тізімі"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Хабар алмасу"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Жаңа хабар"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Сөйлесімдер тізімі"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Сөйлесулер жүктелуде"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Хабарлар жүктелуде"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Көбірек сөйлесулерді көру"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Қосымша хабарларды көру"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Сөйлесу жойылды"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Сөйлесу жойылды. Басқа \"Хабар алмасу\" сөйлесуін көрсету үшін түртіңіз"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Бөгелген"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Бөгеуден шығарылған"</string>
+ <string name="db_full" msgid="8459265782521418031">"Жадтағы бос орын аз. Кейбір деректер жоғалуы мүмкін."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Тіркемелерді таңдау"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Таңдауды растау"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> таңдалды"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Бір немесе бірнеше тіркемені жойып, әрекетті қайталаңыз."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Хабарды жіберіп көре аласыз, бірақ бір немесе бірнеше тіркемені жоймасаңыз, ол жеткізілмеуі мүмкін."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Әр хабарламада бір ғана бейне жіберуге болады. Қосымша бейнелерді жойып, әрекетті қайталаңыз."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging тіркемені жүктей алмады."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Бәрібір жіберу"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Сөйлесуді бастау мүмкін болмады"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> таңдалған"</string>
+</resources>
diff --git a/res/values-km-rKH/arrays.xml b/res/values-km-rKH/arrays.xml
new file mode 100644
index 0000000..69f6e0d
--- /dev/null
+++ b/res/values-km-rKH/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"គ្មាន​ប្រធានបទ"</item>
+ <item msgid="272485471009191934">"គ្មាន​ប្រធាន​បទ"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"បាទ/ចាស"</item>
+ <item msgid="6049132459802288033">"ទេ"</item>
+ <item msgid="3084376867445867895">"យល់ព្រម"</item>
+ <item msgid="3155097332660174689">"ហេហេ"</item>
+ <item msgid="2611328818571146775">"អរគុណ"</item>
+ <item msgid="4881335087096496747">"ខ្ញុំ​យល់​ព្រម"</item>
+ <item msgid="2422296858597420738">"ល្អ"</item>
+ <item msgid="4805581752819452687">"នៅ​តាម​ផ្លូវ​"</item>
+ <item msgid="4746700499431366214">"យល់ព្រម ខ្ញុំ​នឹង​ទាក់ទង​អ្នក​ពេល​ក្រោយ"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..1806cc7
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"​ការ​ផ្ញើ​សារ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"ការ​​ផ្ញើ​សារ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"ជ្រើសរើសការសន្ទនា"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ការកំណត់"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"ផ្ញើ​សារ"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"បន្ថែម​ឯកសារ​ភ្ជាប់"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"ជំនួយ"</string>
+ <string name="welcome" msgid="2857560951820802321">"សូមស្វាគមន៍"</string>
+ <string name="skip" msgid="7238879696319945853">"រំលង"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"បន្ទាប់ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"បន្ទាប់"</string>
+ <string name="exit" msgid="1905187380359981199">"ចេញ"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ការកំណត់ &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ការកំណត់"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"ការផ្ញើសារត្រូវការសិទ្ធិអនុញ្ញាតដើម្បីប្រើសារ SMS, ទូរស័ព្ទ និងទំនាក់ទំនង។"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"អ្នកអាចប្តូរសិទ្ធិអនុញ្ញាតនៅក្នុង ការកំណត់ &gt; កម្មវិធី &gt; ការផ្ញើសារ &gt; សិទិ្ធអនុញ្ញាត។"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"អ្នកអាចប្តូរសិទ្ធិអនុញ្ញាតនៅក្នុង ការកំណត់ , កម្មវិធី, ការផ្ញើសារ, សិទ្ធិអនុញ្ញាត។"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"ញឹកញាប់"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ទំនាក់ទំនងទាំងអស់"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"ផ្ញើ​ទៅ <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ថត​រូបភាព ឬ​វីដេអូ"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ជ្រើស​រូបភាព​ពី​ឧបករណ៍​នេះ"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ថត​សំឡេង"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ជ្រើស​រូបថត"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"បានជ្រើសឯកសារកំសាន្ត។"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"បានលុបការជ្រើសឯកសារកំសាន្ត។"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"បាន​ជ្រើស <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"រូបភាព <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" msgid="8142175029053370662">"រូបភាព"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ថត​សំឡេង"</string>
+ <string name="action_share" msgid="2143483844803153871">"ចែករំលែក​"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ថ្មីៗ​នេះ"</string>
+ <string name="posted_now" msgid="867560789350406701">"ឥឡូវ"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> នាទី</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> នាទី</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ម៉ោង</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ម៉ោង</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ថ្ងៃ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ថ្ងៃ</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> សប្តាហ៍</item>
+ <item quantity="one">មួយសប្តាហ៍</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ខែ</item>
+ <item quantity="one">មួយខែ</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ឆ្នាំ</item>
+ <item quantity="one">មួយឆ្នាំ</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"សារ​ថ្នាក់ ០"</string>
+ <string name="save" msgid="5081141452059463572">"រក្សាទុក"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ឧបករណ៍នៅសល់ទំហំផ្ទុកតិច។ ការផ្ញើសារនឹងលុបសារចាស់ៗដើម្បីធ្វើឲ្យមានទំហំផ្ទុកទំនេរ។"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"អស់​ទំហំ​ផ្ទុក"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"ការផ្ញើសារអាចនឹងមិនផ្ញើ ឬទទួលសារទេ រហូតដល់មានទំហំផ្ទុកបន្ថែមទៀតនៅលើឧបករណ៍របស់អ្នក។"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"ជិត​អស់​ទំហំ​ផ្ទុក​សារ SMS ។ អ្នក​អាច​​​លុប​សារ​មួយ​ចំនួន។"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"បញ្ជាក់លេខទូរស័ព្ទរបស់អ្នក"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ជំហានមួយលើកនេះធានាថាការផ្ញើសារនឹងបញ្ជូនសារក្រុមរបស់អ្នកបានត្រឹមត្រូវ។"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"លេខទូរស័ព្ទ"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"លុប​សារ​​​​ដែល​មាន​មេឌៀ​ទាំងអស់"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"លុប​សារ​ចាស់​ជាង <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"លុប​សារ​ចាស់​ជាង <xliff:g id="DURATION">%s</xliff:g> ដោយ​ស្វ័យ​ប្រវត្តិ"</string>
+ <string name="ignore" msgid="7063392681130898793">"មិន​អើពើ"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"លុប​សារ​ទាំងអស់​មាន​ក្នុង​មេឌៀ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"លុប​សារ​ចាស់​ជាង <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"លុប​សារ​ចាស់​ជាង <xliff:g id="DURATION">%s</xliff:g> និង​បើក​ការ​លុប​ស្វ័យ​ប្រវត្តិ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> និយាយថា"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"អ្នកនិយាយថា"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"សារពី <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"អ្នកបានផ្ញើសារមួយ"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"កំពុង​ផ្ញើ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"មិន​បាន​ផ្ញើ។ ប៉ះ​ដើម្បី​ព្យាយាម​ម្ដងទៀត។"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"មិន​បាន​ផ្ញើ។ កំពុង​ព្យាយាម​ម្ដងទៀត..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ផ្ញើ​ម្ដង​ទៀត ឬ​លុប"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"សូមធ្វើការហៅជាសម្លេងទៅកាន់សេវាកម្មសង្គ្រោះបន្ទាន់។ សារជាអក្សររបស់អ្នកមិនអាចបញ្ជូនបានទេនៅពេលនេះ។"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"បាន​បរាជ័យ"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"សារ MMS ថ្មី​ត្រូវ​ទាញ​យក"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"សារ MMS ថ្មី"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"មិន​អាច​ទាញ​យក"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ប៉ះ​ ដើម្បី​ព្យាយាម​ម្តង​ទៀត"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ប៉ះ​ ដើម្បី​ទាញ​យក"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ទាញ​យក ​ឬ​លុប"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"កំពុង​ទាញ​យក..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"សារ​បាន​​ផុត​កំណត់​ ឬ​មិន​អាច​ប្រើ​បាន"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ទំហំ៖ <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, ផុត​កំណត់៖ <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"មិន​អាច​ផ្ញើ។ អ្នក​ទទួល​មិន​ត្រឹមត្រូវ។"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"សេវា​មិន​សកម្ម​នៅ​លើ​បណ្ដាញ"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"មិន​អាច​ផ្ញើ​ដោយសារ​បញ្ហា​បណ្ដាញ"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"សារ​បាន​​ផុត​កំណត់​ ឬ​មិន​អាច​ប្រើ​បាន"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(គ្មាន​ប្រធាន​បទ)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"មិន​ស្គាល់​អ្នក​ផ្ញើ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"បាន​បញ្ជូន"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"មិន​អាច​ទាញ​យក​សារ <xliff:g id="SUBJECT">%1$s</xliff:g> ពី <xliff:g id="FROM">%2$s</xliff:g> បាន​ទេ។"</string>
+ <string name="low_memory" msgid="5300743415198486619">"មិន​អាច​​បញ្ចប់​ប្រតិបត្តិការ​​មូលដ្ឋាន​ទិន្នន័យ​ដោយសារ​អង្គ​ចងចាំ​មិន​គ្រប់គ្រាន់​​"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"មិន​បាន​ផ្ញើ​សារ"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"សារមួយចំនួនមិនបានផ្ញើនៅក្នុងការផ្ញើសារទេ"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other">សារ <xliff:g id="MESSAGES_1">%d</xliff:g> នៅក្នុងការសន្ទនា <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="one">សារ <xliff:g id="MESSAGES_0">%d</xliff:g> នៅក្នុងការសន្ទនាមួយ</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"មិនបានទាញយកសារ"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"សារមួយចំនួនមិនបានទាញយកនៅក្នុងការផ្ញើសារទេ"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other">សារ <xliff:g id="MESSAGES_1">%d</xliff:g> នៅក្នុងការសន្ទនា <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="one">សារ <xliff:g id="MESSAGES_0">%d</xliff:g> នៅក្នុងការសន្ទនាមួយ</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"សារទៅកាន់ <xliff:g id="NUMBER">%1$s</xliff:g> មិនត្រូវបានផ្ញើទេ"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"សូមធ្វើការហៅជាសម្លេងទៅកាន់សេវាកម្មសង្គ្រោះបន្ទាន់។ សារជាអក្សររបស់អ្នកទៅ <xliff:g id="NUMBER">%1$s</xliff:g> មិនអាចបញ្ជូនបានទេនៅពេលនេះ។"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other">សារថ្មី <xliff:g id="MESSAGES">%d</xliff:g></item>
+ <item quantity="one">សារថ្មី</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ចាប់ផ្ដើម"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"មិន​អាច​ប្រើ​ម៉ាស៊ីន​ថត​បាន"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"មិន​អាច​ប្រើ​ម៉ាស៊ីន​ថត​បាន"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"មិន​អាច​ថត​វីដេអូ​បាន"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"មិន​អាច​រក្សាទុក​​មេឌៀ"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"មិនអាចថតរូបភាព"</string>
+ <string name="back" msgid="1477626055115561645">"ថយក្រោយ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"ទុក​ក្នុង​ប័ណ្ណសារ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"លុប"</string>
+ <string name="action_archive" msgid="5437034800324083170">"ប័ណ្ណសារ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"មិន​ទុក​ក្នុង​ប័ណ្ណសារ"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"បិទ​ការ​ជូនដំណឹង"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"បើក​ការ​ជូនដំណឹង​"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"បន្ថែម​ទំនាក់ទំនង"</string>
+ <string name="action_download" msgid="7786338136368564146">"ទាញ​យក"</string>
+ <string name="action_send" msgid="377635240181672039">"ផ្ញើ"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"លុប"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"លុប​សារ​នេះ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"សកម្មភាព​នេះ​មិន​អាច​ធ្វើ​វិញ​បាន​ទេ។"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"លុប"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">លុបការសន្ទនាទាំងនេះ?</item>
+ <item quantity="one">លុបការសន្ទនានេះ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"លុប"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"បោះបង់"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"ជូន​ចំពោះ"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"ជ្រើស​រូបភាព​ច្រើន"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"បញ្ជាក់​​ការ​ជ្រើស"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"មិន​អាច​ថត​សំឡេង។ ព្យាយាម​ម្ដង​ទៀត។"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"មិន​អាច​ចាក់​សំឡេង។ ព្យាយាម​ម្ដងទៀត។"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"មិន​អាច​រក្សាទុក​សំឡេង។ ព្យាយាម​ម្ដងទៀត។"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ប៉ះ &amp; សង្កត់"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"រូបភាព"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ឈុត​សំឡេង"</string>
+ <string name="notification_video" msgid="4331423498662606204">"វីដេអូ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"កាត​ទំនាក់ទំនង"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ទាញ​យក"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"ឆ្លើយតប​តាម​សារ SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"ឆ្លើយតបតាម MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ឆ្លើយតប"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other">អ្នកចូលរួម <xliff:g id="COUNT_1">%d</xliff:g> នាក់</item>
+ <item quantity="one">អ្នកចូលរួម <xliff:g id="COUNT_0">%d</xliff:g> នាក់</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ខ្ញុំ"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"ទំនាក់ទំនង​ត្រូវ​បាន​ទប់ស្កាត់ &amp; ទុក​ក្នុង​ប័ណ្ណសារ"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"បានរារាំង និងបានដកទំនាក់ទំនងចេញពីបណ្ណ័សារ"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"បានទុកក្នុងបណ្ណសារចំនួន<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"មិនបានទុកក្នុងប័ណ្ណសារចំនួន <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"បានបិទការជូនដំណឹង"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"បានបើកការជូនដំណឹង"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"កំណត់​រួចរាល់។ ប៉ះ​ដើម្បី​ផ្ញើ​ម្ដងទៀត។"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"បានកំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើមដោយជោគជ័យ។"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">បោះបង់ឯកសារភ្ជាប់</item>
+ <item quantity="one">បោះបង់ឯកសារភ្ជាប់</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ឯកសារភ្ជាប់សម្លេង"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ឯកសារភ្ជាប់សម្លេង Play"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"ផ្អាក"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"សារពី <xliff:g id="SENDER">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"សារបរាជ័យពី <xliff:g id="SENDER">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"សារពី <xliff:g id="SENDER">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"សារមិនបានផ្ញើទៅ <xliff:g id="CONTACT">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"កំពុងផ្ញើសារទៅ <xliff:g id="CONTACT">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"សារបរាជ័យទៅ <xliff:g id="CONTACT">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"សារទៅ <xliff:g id="CONTACT">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"សារបរាជ័យពី <xliff:g id="SENDER">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។ <xliff:g id="GROUPINFO">%s</xliff:g>។"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"សារពី <xliff:g id="SENDER">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។ <xliff:g id="GROUPINFO">%s</xliff:g>។"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"សារមិនបានផ្ញើទៅ <xliff:g id="GROUP">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"កំពុងផ្ញើសារទៅ <xliff:g id="GROUP">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"សារបរាជ័យទៅ <xliff:g id="GROUP">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"សារទៅ <xliff:g id="GROUP">%s</xliff:g>៖ <xliff:g id="MESSAGE">%s</xliff:g>។ ម៉ោង៖ <xliff:g id="TIME">%s</xliff:g>។"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"សារបរាជ័យ។ ប៉ះដើម្បីព្យាយាមម្តងទៀត។"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"ការសន្ទនាជាមួយ <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"លុប​ប្រធានបទ"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ថត​វីដេអូ"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ថត​រូបភាព​ថេរ"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ថតរូប"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ចាប់ផ្ដើម​ថត​វីដេអូ"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"ប្ដូរ​ទៅ​ម៉ាស៊ីន​ថត​ពេញ​អេក្រង់"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"ប្ដូរ​រវាង​ម៉ាស៊ីន​ថត​មុន និង​ក្រោយ"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"បញ្ឈប់​ការ​ថត និង​ភ្ជាប់​វីដេអូ"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"បញ្ឈប់​ការ​ថត​វីដេអូ"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"រូបភាពសារ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">បានរក្សាទុករូបថត <xliff:g id="QUANTITY_2">%d</xliff:g> ទៅក្នុងអាល់ប៊ុម \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">បានរក្សាទុករូបថត <xliff:g id="QUANTITY_0">%d</xliff:g> ទៅក្នុងអាល់ប៊ុម \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">បានរក្សាទុកវីដេអូ <xliff:g id="QUANTITY_2">%d</xliff:g> ទៅក្នុងអាល់ប៊ុម \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">បានរក្សាទុកវីដេអូ <xliff:g id="QUANTITY_0">%d</xliff:g> ទៅក្នុងអាល់ប៊ុម \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">បានរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_2">%d</xliff:g> ទៅក្នុងអាល់ប៊ុម \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">បានរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_0">%d</xliff:g> ទៅក្នុងអាល់ប៊ុម \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">បានរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_1">%d</xliff:g> ទៅក្នុង \"ទាញយក\"</item>
+ <item quantity="one">បានរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_0">%d</xliff:g> ទៅក្នុង \"ទាញយក\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">បានរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_1">%d</xliff:g></item>
+ <item quantity="one">បានរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">មិនអាចរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_1">%d</xliff:g></item>
+ <item quantity="one">មិនអាចរក្សាទុកឯកសារភ្ជាប់ <xliff:g id="QUANTITY_0">%d</xliff:g></item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"បាន​រក្សាទុក​ឯកសារ​ភ្ជាប់​ជា​សារ MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ការកំណត់"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"ទុក​ក្នុង​ប័ណ្ណសារ"</string>
+ <string name="action_close" msgid="1840519376200478419">"បិទ"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"សារ MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"កម្រិត​ខ្ពស់"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"កែ​កំហុស"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"ការ​ជូនដំណឹង"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"សំឡេង"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"ស្ងាត់"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"ញ័រ"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"បាន​ទប់ស្កាត់"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"របាយការណ៍​បញ្ជូន​សារ SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ស្នើ​​​របាយ​ការណ៍​​បញ្ជូន​សម្រាប់​សារ SMS ​នីមួយៗ​ដែល​អ្នក​ផ្ញើ"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"​យក​ស្វ័យ​ប្រវត្តិ"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"ទាញ​យក​សារ MMS ដោយ​ស្វ័យ​ប្រវត្តិ​"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ទាញ​យក​ដោយ​ស្វ័យប្រវត្តិ​ពេល​រ៉ូមីង"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ទាញសារ MMS ត្រឡប់មកវិញដោយស្វ័យប្រវត្តិនៅពេលរ៉ូមីង"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"ការ​ផ្ញើ​សារ​​ជា​ក្រុម"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ប្រើ MMS ដើម្បី​ផ្ញើ​សារ នៅ​ពេល​មាន​អ្នក​ទទួល​ច្រើន"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"កម្មវិធី​ផ្ញើ​សារ SMS ​លំនាំ​ដើម"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"កម្មវិធី​ផ្ញើ​សារ SMS ​លំនាំ​ដើម"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"លេខ​ទូរសព្ទ​របស់​អ្នក"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"មិន​ស្គាល់"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"សម្លេងសារផ្ញើចេញ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"បោះបង់​សារ SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ដាក់​ទិន្នន័យ​ដើម​នៃ​សារ SMS បាន​ទទួល​ក្នុង​ឯកសារ​ឧបករណ៍​ផ្ទុក​ខាង​ក្រៅ"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"បោះបង់​សារ MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ដាក់​ទិន្នន័យ​ដើម​នៃ​សារ MMS បាន​ទទួល​ក្នុង​ឯកសារ​ឧបករណ៍​ផ្ទុក​ខាង​ក្រៅ"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ការ​ជូនដំណឹង​​ឥត​​​ខ្សែ"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"ជម្រើស​សារ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ចម្លង​អត្ថបទ"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"មើល​​ព័ត៌មាន​លម្អិត"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"លុប"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"បញ្ជូន​បន្ត"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"ព័ត៌មាន​លម្អិត​សារ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ប្រភេទ៖ "</string>
+ <string name="text_message" msgid="7415419755252205721">"សារ​​អត្ថបទ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"សារ​ពហុ​​មេឌៀ"</string>
+ <string name="from_label" msgid="1947831848146564875">"ពី៖ "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"ជូន​ចំពោះ៖ "</string>
+ <string name="sent_label" msgid="5186286057597137301">"បាន​ផ្ញើ៖ "</string>
+ <string name="received_label" msgid="4442494712757995203">"បាន​ទទួល៖ "</string>
+ <string name="subject_label" msgid="1887378451808609152">"ប្រធានបទ៖ "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"ទំហំ៖ "</string>
+ <string name="priority_label" msgid="5029073794896222902">"អាទិភាព៖ "</string>
+ <string name="sim_label" msgid="2706003016582772108">"ស៊ីម៖ "</string>
+ <string name="priority_high" msgid="728836357310908368">"ខ្ពស់"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"ធម្មតា"</string>
+ <string name="priority_low" msgid="7398724779026801851">"ទាប"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"ស៊ីម <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"​អាសយដ្ឋាន​​អ្នក​ផ្ញើ​ដែល​បាន​លាក់"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"មិន​អាច​ផ្ញើ​សារ​ខណៈ​ពេល​ផ្ទុក​ឯកសារ​ភ្ជាប់។"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"មិន​អាច​ផ្ទុក​ឯកសារ​ភ្ជាប់។ ព្យាយាម​ម្ដង​ទៀត។"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"បណ្តាញមិនទាន់រួចរាល់ទេ។ ព្យាយាមម្តងទៀត។"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"លុប​អត្ថបទ"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ប្ដូរ​​រវាង​លេខ និង​អត្ថបទ​បញ្ចូល"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"បន្ថែម​អ្នក​ចូលរួម​ច្រើន​ទៀត"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"បញ្ជាក់​អ្នក​ចូលរួម"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"ចាប់ផ្ដើមការសន្ទនាថ្មី"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ជ្រើស​ធាតុ​នេះ"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ចាក់​វីដេអូ"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"មនុស្ស &amp; ជម្រើស"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"កែ​កំហុស"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"មនុស្ស &amp; ជម្រើស"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"ទូទៅ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"មនុស្ស​ក្នុង​ការ​សន្ទនា​នេះ"</string>
+ <string name="action_call" msgid="6596167921517350362">"ហៅ"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"ផ្ញើ​សារ"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"ផ្ញើ​សារ&lt;br/&gt;&lt;small&gt;ពី <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">ផ្ញើរូបថត</item>
+ <item quantity="one">ផ្ញើរូបថត</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">ផ្ញើសំឡេង</item>
+ <item quantity="one">ផ្ញើសំឡេង</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">ផ្ញើវីដេអូ</item>
+ <item quantity="one">ផ្ញើវីដេអូ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">ផ្ញើកាតទំនាក់ទំនង</item>
+ <item quantity="one">ផ្ញើកាតទំនាក់ទំនង</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">ផ្ញើឯកសារភ្ជាប់</item>
+ <item quantity="one">ផ្ញើឯកសារភ្ជាប់</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other">ឯកសារភ្ជាប់ <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ត្រៀមសម្រាប់ផ្ញើ</item>
+ <item quantity="one">ឯកសារភ្ជាប់មួយត្រៀមសម្រាប់ផ្ញើ</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ផ្ដល់​មតិ​ស្ថាបនា"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"មើល​នៅ​ក្នុង​​ Google ឃ្លាំង​កម្សាន្ត"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ព័ត៌មាន​កំណែ"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"កំណែ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"អាជ្ញាប័ណ្ណ​កម្មវិធី​ប្រភព​កូដ​ចំហ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"ការ​ជូនដំណឹង"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ឯកសារភ្ជាប់ឈានដល់កំណត់"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ការផ្ទុកឯកសារភ្ជាប់បានបរាជ័យ។"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"បន្ថែម​ទៅ​ទំនាក់ទំនង​?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"បន្ថែម​ទំនាក់ទំនង"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"ប្រធានបទ"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"ប្រធានបទ៖ "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"ផ្ទុក​កាត​ទំនាក់ទំនង"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"មិន​អាច​ផ្ទុក​កាត​ទំនាក់ទំនង"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"មើល​កាត​ទំនាក់ទំនង"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other">ទំនាក់ទំនង <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="one">ទំនាក់ទំនង <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"កាត​ទំនាក់ទំនង"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"ថ្ងៃ​កំណើត"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ចំណាំ"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"បញ្ជូន​សារ​បន្ត"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ឆ្លើយតប"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"បាន​បិទ​សារ SMS"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"សូមកំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើមដើម្បីផ្ញើ"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"កំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើម"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"ផ្លាស់ប្ដូរ"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"សូមកំណត់ការផ្ញើសារជាកម្មវិធីសារ SMS លំនាំដើមដើម្បីទទួលសារ"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"គ្មានស៊ីមកាតដែលបានប្រើត្រូវបានជ្រើសរើសឲ្យផ្ញើសារ SMS ទេ"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"កម្មវិធី​នេះ​មិន​បាន​អនុញ្ញាត​ដោយ​ម្ចាស់​ឧបករណ៍។"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"យល់ព្រម"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"មាន​អ្នក​ចូលរួម​ច្រើន​ពេក​ក្នុង​ការ​សន្ទនា"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">ទំនាក់ទំនងមិនត្រឹមត្រូវ</item>
+ <item quantity="one">ទំនាក់ទំនងមិនត្រឹមត្រូវ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"មិន​អាច​ផ្ទុក​រូបភាព​ម៉ាស៊ីន​ថត"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"អ្នក​៖ "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"សេចក្ដី​ព្រាង"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"នៅ​ពេល​ដែល​អ្នក​ចាប់ផ្ដើម​​ការ​សន្ទនា​ថ្មី ​អ្នក​នឹង​ឃើញ​វា​​​បង្ហាញ​នៅ​ទីនេះ"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"ការ​សន្ទនា​បាន​ទុក​ក្នុង​ប័ណ្ណសារ​បង្ហាញ​នៅ​ទីនេះ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"កំពុង​ផ្ទុក​ការ​សន្ទនា…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"រូបភាព"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ឃ្លីបសម្លេង"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"វីដេអូ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"កាត​ទំនាក់ទំនង"</string>
+ <string name="mms_text" msgid="1528791558806015806">"សារ MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"មិនធ្វើវិញ"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ព្យាយាមម្ដងទៀត"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"បញ្ចូល​ឈ្មោះ​ទំនាក់ទំនង​ ឬ​លេខ​ទូរសព្ទ​ដើម្បី​ចាប់ផ្ដើម​សារ​ថ្មី"</string>
+ <string name="action_block" msgid="9032076625645190136">"ទប់ស្កាត់"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"ទប់ស្កាត់ <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"មិន​ទប់ស្កាត់ <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"ទប់ស្កាត់ <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"អ្នក​នឹង​បន្ត​ទទួល​សារ​ពី​លេខ​នេះ ប៉ុន្តែ​នឹង​មិន​ត្រូវ​បាន​ជូន​ដំណឹង​ទៀត​ទេ។ ការ​សន្ទនា​នេះ​នឹង​ត្រូវ​បាន​ទុក​ក្នុង​ប័ណ្ណសារ។"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"បាន​ទប់ស្កាត់​ទំនាក់ទំនង"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"មិន​ទប់ស្កាត់"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"បាន​ទប់ស្កាត់​ទំនាក់ទំនង​"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ជ្រើស​រូបភាព​ពី​បណ្ណាល័យ​ឯកសារ"</string>
+ <string name="sending_message" msgid="6363584950085384929">"កំពុងផ្ញើសារ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"បានផ្ញើសារ"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"ទិន្នន័យ​ចល័ត​ត្រូវ​បាន​បិទ។ ពិនិត្យ​ការ​កំណត់​របស់​អ្នក។"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"មិន​អាច​ផ្ញើ​សារ​ក្នុង​របៀប​ពេល​ជិះ​យន្ត​ហោះ"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"មិនអាចផ្ញើសារបានទេ"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"បានទាញយកសារ"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"ទិន្នន័យចល័តត្រូវបានបិទ។ ពិនិត្យការកំណត់របស់អ្នក។"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"មិនអាចទាញយកសារនៅក្នុង របៀបយន្តហោះ ទេ។"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"មិនអាចទាញយកសារបានទេ"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"សូន្យ"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"មួយ"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"ពីរ"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"​បី"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"បួន"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ប្រាំ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ប្រាំមួយ"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ប្រាំពីរ"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ប្រាំបី"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ប្រាំបួន"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"មិនអាចផ្ញើសារដោយប្រើ <xliff:g id="CARRIERNAME">%1$s</xliff:g> នេះបានទេ។ កំហុស <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"មិនអាចផ្ញើសារដោយប្រើក្រុមហ៊ុនផ្តល់សេវាដែលមិនស្គាល់បានទេ។ កំហុស <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"បញ្ជូន​បន្ត៖ <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"សារ​មិន​បាន​ផ្ញើ៖ សេវាកម្ម​មិន​សកម្ម​លើ​បណ្ដាញ"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"មិន​បាន​ផ្ញើសារ៖ អាសយដ្ឋាន​គោលដៅ​មិន​ត្រឹមត្រូវ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"មិន​បាន​ផ្ញើ​សារ៖ សារ​មិន​ត្រឹមត្រូវ"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"មិន​បាន​ផ្ញើ​សារ៖ មិន​គាំទ្រ​មាតិកា"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"មិន​បាន​ផ្ញើសារ៖ មិន​គាំទ្រ​សារ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"មិនបានផ្ញើសារ៖ ធំពេក"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"សារ​ថ្មី"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"មើល"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"រូបភាព"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"មិន​អាច​រក​ឃើញ​កម្មវិធី​ដែល​សមរម្យ"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"លុប​អ្នក​ទទួល"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"សារ​ថ្មី"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"បោះបង់"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"កែ​ចំណុច​ចូល​ដំណើរការ"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"មិនបាន​កំណត់"</string>
+ <string name="apn_name" msgid="1572691851070894985">"ឈ្មោះ"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"ប្រូកស៊ី MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"ច្រក MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"ប្រភេទ APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"លុប APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN ថ្មី"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"រក្សាទុក"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"បោះបង់"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"វាល​ឈ្មោះ​មិន​អាច​ទទេ។"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN មិន​អាច​ទទេ។"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"វាល MCC ត្រូវ​តែ​មាន​ ៣ តួ។"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"វាល MNC ត្រូវ​តែ​មាន​ពីរ ឬ​បី​តួ។"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ស្ដារ​ការ​កំណត់ APN លំនាំដើម​ឡើងវិញ។"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"កំណត់​ទៅ​លំនាំដើម​ឡើងវិញ"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"បាន​បញ្ចប់​ការ​កំណត់​នៃ​ការកំណត់​ APN លំនាំដើម។"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"គ្មាន​ចំណង​ជើង"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ឈ្មោះ​ចំណុច​ចូល​ដំណើរការ"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN ថ្មី"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ការ​កំណត់​ឈ្មោះ​ចូល​ដំណើរការ​មិន​អាច​ប្រើ​បាន​សម្រាប់​អ្នក​ប្រើ​​នេះ"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ចម្លង​ទៅ​ក្ដារ​តម្បៀត​ខ្ទាស់​?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"ចម្លង"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ទៅ <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"ទូទៅ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"កម្រិត​ខ្ពស់"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"ការ​កំណត់​ទូទៅ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"ការ​កំណត់​កម្រិត​ខ្ពស់"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" ស៊ីម"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ផ្ញើ​សារ SMS នីមួយៗ​ទៅ​អ្នក​ទទួល​ទាំង​អស់។ មាន​តែ​អ្នក​ប៉ុណ្ណោះ​​ទទួល​បាន​ការ​ឆ្លើយតប"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"ផ្ញើ​សារ MMS មួយ​ទៅ​អ្នក​ទទួល"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"មិន​ស្គាល់​លេខ"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"សារថ្មី"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"សារថ្មី។"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"កម្មវិធីជ្រើសស៊ីម"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"បានជ្រើសរើស <xliff:g id="SIM_0">%1$s</xliff:g> កម្មវិធីជ្រើសស៊ីម"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"កែប្រធានបទ"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"ជ្រើសស៊ីម ឬកែប្រធានបទ"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ប៉ះ និងសង្កត់ដើម្បីថតសម្លេង"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"ចាប់ផ្ដើមការសន្ទនាថ្មី"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"ការផ្ញើសារ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"បញ្ជីសារ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"ការផ្ញើសារ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"សារថ្មី"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"បញ្ជីសន្ទនា"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"កំពុងផ្ទុកការសន្ទនា…"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"សារកំពុងដំណើរការ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"មើលការសន្ទនាច្រើនទៀត"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"មើលសារច្រើនទៀត"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"បានលុបការសន្ទនា"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"បានលុបការសន្ទនា។ សូមប៉ះដើម្បីបង្ហាញការសន្ទនាផ្សេងទៀតនៅក្នុងការផ្ញើសារ"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"បាន​ទប់ស្កាត់"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"បានឈប់ទប់ស្កាត់"</string>
+ <string name="db_full" msgid="8459265782521418031">"ចន្លោះផ្ទុកមានកម្រិតទាប។ ទិន្នន័យមួយចំនួនអាចបាត់បង់។"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ជ្រើសឯកសារភ្ជាប់"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"បញ្ជាក់​​ការ​ជ្រើស"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"បានជ្រើស <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"សូមយកឯកសារភ្ជាប់មួយ ឬច្រើនចេញ ហើយព្យាយាមម្តងទៀត។"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"អ្នកអាចព្យាយាមផ្ញើសារបស់អ្នក ប៉ុន្តែវាប្រហែលជាមិនផ្ញើទៅទេ លុះត្រាតែអ្នកយកឯកសារភ្ជាប់មួយ ឬច្រើនចេញ។"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"អ្នកអាចផ្ញើវីដេអូតែមួយប៉ុណ្ណោះនៅក្នុងសារមួយ។ សូមយកវីដេអូដែលលើសពីនេះចេញ ហើយព្យាយាមម្តងទៀត។"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"ការផ្ញើសារបានបរាជ័យក្នុងដំណើរការផ្ទុកឯកសារភ្ជាប់។"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"អញ្ចឹង​ផ្ញើ​​តែម្ដង"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"មិនអាចចាប់ផ្តើមការសន្ទនាទេ"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"បានជ្រើស <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-kn-rIN/arrays.xml b/res/values-kn-rIN/arrays.xml
new file mode 100644
index 0000000..932fec1
--- /dev/null
+++ b/res/values-kn-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ಯಾವುದೇ ವಿಷಯವಿಲ್ಲ"</item>
+ <item msgid="272485471009191934">"ಯಾವುದೇ ವಿಷಯವಿಲ್ಲ"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ಹೌದು"</item>
+ <item msgid="6049132459802288033">"ಬೇಡ"</item>
+ <item msgid="3084376867445867895">"ಸರಿ"</item>
+ <item msgid="3155097332660174689">"ಹೆಹೆ"</item>
+ <item msgid="2611328818571146775">"ಧನ್ಯವಾದಗಳು"</item>
+ <item msgid="4881335087096496747">"ಒಪ್ಪುತ್ತೇನೆ"</item>
+ <item msgid="2422296858597420738">"ಉತ್ತಮ"</item>
+ <item msgid="4805581752819452687">"ನನ್ನ ಹಾದಿಯಲ್ಲಿದ್ದೇನೆ"</item>
+ <item msgid="4746700499431366214">"ಸರಿ, ನಾನು ಮತ್ತೆ ನಿಮಗೆ ತಿಳಿಸುತ್ತೇನೆ"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
new file mode 100644
index 0000000..eb9539b
--- /dev/null
+++ b/res/values-kn-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"ಸಂಭಾಷಣೆಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"ಸಂದೇಶ ಕಳುಹಿಸು"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ಲಗತ್ತನ್ನು ಸೇರಿಸಿ"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"ಸಹಾಯ"</string>
+ <string name="welcome" msgid="2857560951820802321">"ಸುಸ್ವಾಗತ"</string>
+ <string name="skip" msgid="7238879696319945853">"ಸ್ಕಿಪ್‌"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"ಮುಂದೆ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"ಮುಂದೆ"</string>
+ <string name="exit" msgid="1905187380359981199">"ನಿರ್ಗಮಿಸು"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಗೆ SMS, ಫೋನ್ ಮತ್ತು ಸಂಪರ್ಕಗಳಿಗೆ ಅನುಮತಿ ಹೊಂದಿರುವ ಅಗತ್ಯವಿದೆ."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ನೀವು ಅನುಮತಿಗಳನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಅಪ್ಲಿಕೇಶನ್‌ಗಳು &gt; ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ &gt; ಅನುಮತಿಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ನೀವು ಅನುಮತಿಗಳನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು, ಅಪ್ಲಿಕೇಶನ್‌ಗಳು, ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ, ಅನುಮತಿಗಳಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"ಪುನರಾವರ್ತನೆಗಳು"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ಎಲ್ಲ ಸಂಪರ್ಕಗಳು"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> ಗೆ ಕಳುಹಿಸಿ"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ಚಿತ್ರಗಳು ಅಥವಾ ವೀಡಿಯೊವನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ಈ ಸಾಧನದಿಂದ ಚಿತ್ರಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಿ"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ಫೋಟೋ ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"ಮಾಧ್ಯಮ ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"ಮಾಧ್ಯಮ ಆಯ್ಕೆ ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ಚಿತ್ರ <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" msgid="8142175029053370662">"ಚಿತ್ರ"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಿ"</string>
+ <string name="action_share" msgid="2143483844803153871">"ಹಂಚಿಕೊಳ್ಳಿ"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ಈಗ ತಾನೇ"</string>
+ <string name="posted_now" msgid="867560789350406701">"ಇದೀಗ"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ನಿಮಿಷಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ನಿಮಿಷಗಳು</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ಗಂಟೆಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ಗಂಟೆಗಳು</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ದಿನಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ದಿನಗಳು</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ವಾರಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ವಾರಗಳು</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ತಿಂಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ತಿಂಗಳು</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ವರ್ಷಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ವರ್ಷಗಳು</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"ವರ್ಗ 0 ಸಂದೇಶ"</string>
+ <string name="save" msgid="5081141452059463572">"ಉಳಿಸು"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ಸಾಧನದಲ್ಲಿ ಸ್ಥಳಾವಕಾಶವು ಕಡಿಮೆ ಇದೆ. ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ಸ್ಥಳಾವಕಾಶವನ್ನು ತೆರವುಗೊಳಿಸಲು ಹಳೆಯ ಸಂದೇಶಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅಳಿಸುತ್ತದೆ."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"ಸಂಗ್ರಹಣೆ ಸ್ಥಳವು ತುಂಬಿದೆ"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶ ಲಭ್ಯವಿರುವವರೆಗೆ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸದಿರಬಹುದು ಅಥವಾ ಸ್ವೀಕರಿಸದಿರಬಹುದು."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"ಕಡಿಮೆ SMS ಸಂಗ್ರಹಣೆ. ನೀವು ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಬೇಕಾಗಬಹುದು."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"ನಿಮ್ಮ ಫೋನ್ ಸಂಖ್ಯೆಯನ್ನು ಖಚಿತಪಡಿಸಿ"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ಈ ಒಂದು-ಬಾರಿ ಹಂತವು ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ನಿಮ್ಮ ಗುಂಪು ಸಂದೇಶಗಳನ್ನು ಸರಿಯಾಗಿ ಕಳುಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸುತ್ತದೆ."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ಫೋನ್ ಸಂಖ್ಯೆ"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"ಮೀಡಿಯಾದ ಎಲ್ಲಾ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಸ್ವಯಂ-ಅಳಿಸಿ"</string>
+ <string name="ignore" msgid="7063392681130898793">"ನಿರ್ಲಕ್ಷಿಸು"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"ಮಾಧ್ಯಮದ ಎಲ್ಲಾ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸುವುದೇ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸುವುದೇ?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> ಗಿಂತಲೂ ಹಳೆಯದಾದ ಸಂದೇಶಗಳನ್ನು ಅಳಿಸಿ ಹಾಗೂ ಸ್ವಯಂ-ಅಳಿಸು ಆನ್‌ ಮಾಡಿ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ಹೇಳಿದ್ದಾರೆ"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"ನೀವು ಹೇಳಿದ್ದೀರಿ"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> ಅವರಿಂದ ಸಂದೇಶ"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"ನೀವು ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಿರುವಿರಿ"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"ಕಳುಹಿಸಲಾಗಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು ಸ್ಪರ್ಶಿಸಿ."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"ಕಳುಹಿಸಲಾಗಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲಾಗುತ್ತಿದೆ…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ಮರುಕಳುಹಿಸಿ ಅಥವಾ ಅಳಿಸಿ"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"ತುರ್ತು ಸೇವೆಗಳಿಗೆ ದಯವಿಟ್ಟು ಧ್ವನಿ ಕರೆಗಳನ್ನು ಮಾಡಿ. ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ ಪಠ್ಯ ಸಂದೇಶವನ್ನು ತಲುಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"ವಿಫಲವಾಗಿದೆ"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲು ಹೊಸ MMS ಸಂದೇಶ"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"ಹೊಸ MMS ಸಂದೇಶ"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲು ಸಾಧ್ಯವಾಗಿಲ್ಲ"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು ಸ್ಪರ್ಶಿಸಿ"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲು ಸ್ಪರ್ಶಿಸಿ"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಿ ಇಲ್ಲವೇ ಅಳಿಸಿ"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"ಸಂದೇಶ ಅವಧಿ ಮೀರಿದೆ ಅಥವಾ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ಗಾತ್ರ: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, ಮುಕ್ತಾಯ: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"ಕಳುಹಿಸಲಾಗುವುದಿಲ್ಲ. ಸ್ವೀಕರಿಸುವವರು ಮಾನ್ಯವಾಗಿಲ್ಲ."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಸೇವೆಯು ಸಕ್ರಿಯಗೊಂಡಿಲ್ಲ"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ನೆಟ್‌ವರ್ಕ್‌ ಸಮಸ್ಯೆಯಿಂದಾಗಿ ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"ಸಂದೇಶ ಅವಧಿ ಮೀರಿದೆ ಅಥವಾ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(ವಿಷಯ ಇಲ್ಲ)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"ಕಳುಹಿಸಿದವರು ತಿಳಿದಿಲ್ಲ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ತಲುಪಿಸಲಾಗಿದೆ"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> ರಿಂದ <xliff:g id="SUBJECT">%1$s</xliff:g> ಸಂದೇಶವನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <string name="low_memory" msgid="5300743415198486619">"ಕಡಿಮೆ ಮೆಮೊರಿಯ ಕಾರಣ ಡೇಟಾಬೇಸ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿಲ್ಲ"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯಲ್ಲಿ ಕೆಲವು ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲಾಗಿಲ್ಲ"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> ಸಂವಾದಗಳಲ್ಲಿ <xliff:g id="MESSAGES_1">%d</xliff:g> ಸಂದೇಶಗಳು</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> ಸಂವಾದಗಳಲ್ಲಿ <xliff:g id="MESSAGES_1">%d</xliff:g> ಸಂದೇಶಗಳು</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"ಸಂದೇಶವನ್ನು ಡೌನ್‌ಲೋಡ್‌ ಮಾಡಲಾಗಿಲ್ಲ"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯಲ್ಲಿ ಕೆಲವು ಸಂದೇಶಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿಲ್ಲ"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> ಸಂವಾದಗಳಲ್ಲಿ <xliff:g id="MESSAGES_1">%d</xliff:g> ಸಂದೇಶಗಳು</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> ಸಂವಾದಗಳಲ್ಲಿ <xliff:g id="MESSAGES_1">%d</xliff:g> ಸಂದೇಶಗಳು</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> ಸಂಖ್ಯೆಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಿಲ್ಲ"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"ತುರ್ತು ಸೇವೆಗಳಿಗೆ ದಯವಿಟ್ಟು ಧ್ವನಿ ಕರೆಗಳನ್ನು ಮಾಡಿ. ಈ ಸಮಯದಲ್ಲಿ ನಿಮ್ಮ ಪಠ್ಯ ಸಂದೇಶವನ್ನು <xliff:g id="NUMBER">%1$s</xliff:g> ಸಂಖ್ಯೆಗೆ ತಲುಪಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> ಹೊಸ ಸಂದೇಶಗಳು</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ಹೊಸ ಸಂದೇಶಗಳು</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ಪ್ರಾರಂಭಿಸು"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ಕ್ಯಾಮರಾ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ಕ್ಯಾಮರಾ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ವಿಡಿಯೋ ಸೆರೆಹಿಡಿಯುವಿಕೆ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"ಮಾಧ್ಯಮ ಉಳಿಸಲಾಗಲಿಲ್ಲ"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="back" msgid="1477626055115561645">"ಹಿಂದೆ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"ಅಳಿಸಿ"</string>
+ <string name="action_archive" msgid="5437034800324083170">"ಆರ್ಕೈವ್ ಮಾಡಿ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ಅನ್‌ಆರ್ಕೈವ್ ಮಾಡು"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"ಅಧಿಸೂಚನೆಗಳನ್ನು ಆಫ್ ಮಾಡಿ"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"ಅಧಿಸೂಚನೆಗಳನ್ನು ಆನ್ ಮಾಡು"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"ಸಂಪರ್ಕ ಸೇರಿಸು"</string>
+ <string name="action_download" msgid="7786338136368564146">"ಡೌನ್‌ಲೋಡ್‌"</string>
+ <string name="action_send" msgid="377635240181672039">"ಕಳುಹಿಸು"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"ಅಳಿಸಿ"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ಈ ಸಂದೇಶವನ್ನು ಅಳಿಸುವುದೇ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ಈ ಕ್ರಿಯೆಯನ್ನು ರದ್ದುಪಡಿಸಲಾಗುವುದಿಲ್ಲ"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"ಅಳಿಸಿ"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">ಈ ಸಂವಾದಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
+ <item quantity="other">ಈ ಸಂವಾದಗಳನ್ನು ಅಳಿಸುವುದೇ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"ಅಳಿಸಿ"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"ರದ್ದುಮಾಡು"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"ಇವರಿಗೆ"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"ಬಹು ಚಿತ್ರಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"ಆಯ್ಕೆ ಖಚಿತಪಡಿಸಿ"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ಆಡಿಯೋವನ್ನು ಪ್ಲೇ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ಆಡಿಯೋವನ್ನು ಉಳಿಸಲಾಗಲಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ಸ್ಪರ್ಶಿಸಿ &amp; ಹೋಲ್ಡ್‌ ಮಾಡಿ"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ಚಿತ್ರ"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ಆಡಿಯೊ ಕ್ಲಿಪ್"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ವೀಡಿಯೊ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"ಸಂಪರ್ಕದ ಕಾರ್ಡ್"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ಡೌನ್‌ಲೋಡ್‌"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS ಮೂಲಕ ಪ್ರತ್ಯುತ್ತರಿಸಿ"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS ಮೂಲಕ ಪ್ರತ್ಯುತ್ತರಿಸಿ"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ಪ್ರತ್ಯುತ್ತರಿಸು"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ಪಾಲ್ಗೊಳ್ಳುವವರು</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ಪಾಲ್ಗೊಳ್ಳುವವರು</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ನಾನು"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"ಸಂಪರ್ಕವನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ &amp; ಆರ್ಕೈವ್‌ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"ಸಂಪರ್ಕವನ್ನು ಅನಿರ್ಬಂಧಿಸಲಾಗಿದೆ &amp; ಆರ್ಕೈವ್ ರದ್ದುಮಾಡಲಾಗಿದೆ"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ಆರ್ಕೈವ್‌‌ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ಅನ್‌ಆರ್ಕೈವ್‌ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"ಅಧಿಸೂಚನೆಗಳನ್ನು ಆಫ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"ಅಧಿಸೂಚನೆಗಳನ್ನು ಆನ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"ಎಲ್ಲವನ್ನೂ ಹೊಂದಿಸಲಾಗಿದೆ. ಮತ್ತೆ ಕಳುಹಿಸಲು ಸ್ಪರ್ಶಿಸಿ."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಲಾಗಿದೆ."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">ಲಗತ್ತುಗಳನ್ನು ತಿರಸ್ಕರಿಸು</item>
+ <item quantity="other">ಲಗತ್ತುಗಳನ್ನು ತಿರಸ್ಕರಿಸು</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ಆಡಿಯೋ ಲಗತ್ತು"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ಆಡಿಯೊ ಲಗತ್ತನ್ನು ಪ್ಲೇ ಮಾಡಿ"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"ವಿರಾಮಗೊಳಿಸು"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> ಅವರಿಂದ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> ಅವರಿಂದ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> ಅವರಿಂದ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> ಗೆ ಕಳುಹಿಸದಿರುವ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> ಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> ಗೆ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ : <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> ಗೆ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> ಅವರಿಂದ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> ಅವರಿಂದ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> ಗೆ ಕಳುಹಿಸದಿರುವ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> ಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> ಗೆ ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ : <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> ಗೆ ಸಂದೇಶ: <xliff:g id="MESSAGE">%s</xliff:g>. ಸಮಯ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ಸಂದೇಶ ವಿಫಲವಾಗಿದೆ. ಮರುಪ್ರಯತ್ನಿಸಲು ಸ್ಪರ್ಶಿಸಿ."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> ಅವರೊಂದಿಗೆ ಸಂಭಾಷಣೆ"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"ವಿಷಯವನ್ನು ಅಳಿಸಿ"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ವೀಡಿಯೊ ಸೆರೆಹಿಡಿಯಿರಿ"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ಸ್ಥಿರ ಚಿತ್ರವನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ಫೋಟೋ ತೆಗೆಯಿರಿ"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ವೀಡಿಯೊ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"ಪೂರ್ಣ ಪರದೆ ಕ್ಯಾಮರಾಗೆ ಬದಲಿಸಿ"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"ಮುಂದಿನ ಮತ್ತು ಹಿಂದಿನ ಕ್ಯಾಮರಾ ನಡುವೆ ಬದಲಾಯಿಸಿ"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿ ಮತ್ತು ವೀಡಿಯೊ ಲಗತ್ತಿಸಿ"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ವೀಡಿಯೊ ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಿ"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"ಫೋಟೋಗಳ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ಆಲ್ಬಮ್‌ಗೆ <xliff:g id="QUANTITY_2">%d</xliff:g> ಫೋಟೋಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ <item quantity="other">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ಆಲ್ಬಮ್‌ಗೆ <xliff:g id="QUANTITY_2">%d</xliff:g> ಫೋಟೋಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ಆಲ್ಬಮ್‌ಗೆ <xliff:g id="QUANTITY_2">%d</xliff:g> ವೀಡಿಯೊಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ <item quantity="other">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ಆಲ್ಬಮ್‌ಗೆ <xliff:g id="QUANTITY_2">%d</xliff:g> ವೀಡಿಯೊಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ಆಲ್ಬಮ್‌ಗೆ <xliff:g id="QUANTITY_2">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ <item quantity="other">\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ಆಲ್ಬಮ್‌ಗೆ <xliff:g id="QUANTITY_2">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one">\"ಡೌನ್‌ಲೋಡ್‌ಗಳಿಗೆ\" <xliff:g id="QUANTITY_1">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ <item quantity="other">\"ಡೌನ್‌ಲೋಡ್‌ಗಳಿಗೆ\" <xliff:g id="QUANTITY_1">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ಲಗತ್ತುಗಳನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS ಲಗತ್ತನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"ಆರ್ಕೈವ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="action_close" msgid="1840519376200478419">"ಮುಚ್ಚು"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"ಸುಧಾರಿತ"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ಡೀಬಗ್ ಮಾಡು"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"ಅಧಿಸೂಚನೆಗಳು"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ಶಬ್ದ"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"ಶಾಂತ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"ವೈಬ್ರೇಟ್‌‌"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS ವಿತರಣಾ ವರದಿಗಳು"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ನೀವು ಕಳುಹಿಸುವ ಪ್ರತಿ SMS ಗೂ ವಿತರಣೆ ವರದಿಯನ್ನು ವಿನಂತಿಸಿ"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"ಸ್ವಯಂ-ಹಿಂಪಡೆಯುವಿಕೆ"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS ಸಂದೇಶಗಳನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಹಿಂಪಡೆದುಕೊಳ್ಳಿ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ರೋಮಿಂಗ್‌‌ನಲ್ಲಿರುವಾಗ ಸ್ವಯಂ-ಹಿಂಪಡೆಯುವಿಕೆ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಸ್ವಯಂಚಾಲಿತವಾಗಿ MMS ಹಿಂಪಡೆಯಿರಿ"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"ಗುಂಪು ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ಅನೇಕ ಸ್ವೀಕೃತದಾರರು ಇರುವಾಗ ಒಂದೇ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು MMS ಬಳಸಿ"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"ನಿಮ್ಮ ಫೋನ್‌ ಸಂಖ್ಯೆ"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"ಅಪರಿಚಿತ"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"ಹೊರಹೋಗುವ ಸಂದೇಶಕ್ಕೆ ಧ್ವನಿಗಳು"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"ಡಂಪ್ SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ಬಾಹ್ಯ ಸಂಗ್ರಹಣೆ ಫೈಲ್‌ಗೆ SMS ಕಚ್ಛಾ ಡೇಟಾವನ್ನು ಡಂಪ್ ಸ್ವೀಕರಿಸಿದೆ"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"ಡಂಪ್ MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ಬಾಹ್ಯ ಸಂಗ್ರಹಣೆ ಫೈಲ್‌ಗೆ MMS ಕಚ್ಛಾ ಡೇಟಾವನ್ನು ಡಂಪ್ ಸ್ವೀಕರಿಸಿದೆ"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ವೈರ್‌ಲೆಸ್ ಎಚ್ಚರಿಕೆಗಳು"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"ಸಂದೇಶ ಆಯ್ಕೆಗಳು"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ಪಠ್ಯ ನಕಲಿಸು"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"ವಿವರಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"ಅಳಿಸಿ"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ಫಾರ್ವರ್ಡ್ ಮಾಡು"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"ಸಂದೇಶ ವಿವರಗಳು"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ಪ್ರಕಾರ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"ಪಠ್ಯ ಸಂದೇಶ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"ಮಲ್ಟಿಮೀಡಿಯಾ ಸಂದೇಶ"</string>
+ <string name="from_label" msgid="1947831848146564875">"ಇಂದ: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"ಗೆ: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ಕಳುಹಿಸಿದ್ದು: "</string>
+ <string name="received_label" msgid="4442494712757995203">"ಸ್ವೀಕರಿಸಿದ್ದು: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"ವಿಷಯ: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"ಗಾತ್ರ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ಆದ್ಯತೆ: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"ಅಧಿಕ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"ಸಾಮಾನ್ಯ"</string>
+ <string name="priority_low" msgid="7398724779026801851">"ಕಡಿಮೆ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"ಮರೆಮಾಡಲಾಗಿರುವ ಕಳುಹಿಸುವವರ ವಿಳಾಸ"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"ಲಗತ್ತುಗಳನ್ನು ಲೋಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ಲಗತ್ತನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ನೆಟ್‌ವರ್ಕ್ ಸಿದ್ಧವಾಗಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ಪಠ್ಯ ಅಳಿಸಿ"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ಪಠ್ಯ ಮತ್ತು ಸಂಖ್ಯೆಗಳ ನಡುವೆ ನಮೂದಿಸುವುದನ್ನು ಬದಲಾಯಿಸಿ"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"ಇನ್ನಷ್ಟು ಪಾಲ್ಗೊಳ್ಳುವವರನ್ನು ಸೇರಿಸಿ"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"ಭಾಗವಹಿಸುವವರನ್ನು ಖಚಿತಪಡಿಸಿ"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"ಹೊಸ ಸಂವಾದವನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ಈ ಐಟಂ ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ವೀಡಿಯೊ ಪ್ಲೇ ಮಾಡಿ"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ಜನರು &amp; ಆಯ್ಕೆಗಳು"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ಡೀಬಗ್ ಮಾಡು"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ಜನರು &amp; ಆಯ್ಕೆಗಳು"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"ಸಾಮಾನ್ಯ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ಈ ಸಂವಾದದಲ್ಲಿರುವ ಜನರು"</string>
+ <string name="action_call" msgid="6596167921517350362">"ಕರೆ ಮಾಡಿ"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"ಸಂದೇಶ ಕಳುಹಿಸಿ"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; ನಿಂದ ಸಂದೇಶ ಕಳುಹಿಸಿ"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ಫೋಟೋಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ <item quantity="other">ಫೋಟೋಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ಆಡಿಯೋಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ <item quantity="other">ಆಡಿಯೋಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">ವೀಡಿಯೊಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ <item quantity="other">ವೀಡಿಯೊಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">ಸಂಪರ್ಕ ಕಾರ್ಡ್‍ಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ <item quantity="other">ಸಂಪರ್ಕ ಕಾರ್ಡ್‍ಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">ಲಗತ್ತುಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ <item quantity="other">ಲಗತ್ತುಗಳನ್ನು ಕಳುಹಿಸಿ</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ಕಳುಹಿಸಲು ಲಗತ್ತುಗಳು ಸಿದ್ಧವಾಗಿವೆ</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ಕಳುಹಿಸಲು ಲಗತ್ತುಗಳು ಸಿದ್ಧವಾಗಿವೆ</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ಪ್ರತಿಕ್ರಿಯೆ ಕಳುಹಿಸಿ"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store ನಲ್ಲಿ ವೀಕ್ಷಿಸಿ"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ಆವೃತ್ತಿ ಮಾಹಿತಿ"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"ಆವೃತ್ತಿ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"ಅಧಿಸೂಚನೆಗಳು"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ಲಗತ್ತು ಮಿತಿಯನ್ನು ತಲುಪಿದೆ"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ಲಗತ್ತನ್ನು ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"ಸಂಪರ್ಕಗಳಿಗೆ ಸೇರಿಸುವುದೇ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"ಸಂಪರ್ಕ ಸೇರಿಸು"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"ವಿಷಯ"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"ವಿಷಯ: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"ಸಂಪರ್ಕ ಕಾರ್ಡ್‌ ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"ಸಂಪರ್ಕ ಕಾರ್ಡ್ ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"ಸಂಪರ್ಕ ಕಾರ್ಡ್ ವೀಕ್ಷಿಸಿ"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ಸಂಪರ್ಕಗಳು</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ಸಂಪರ್ಕಗಳು</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"ಸಂಪರ್ಕ ಕಾರ್ಡ್‌ಗಳು"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"ಜನ್ಮದಿನ"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ಟಿಪ್ಪಣಿಗಳು"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"ಸಂದೇಶವನ್ನು ಫಾರ್ವರ್ಡ್ ಮಾಡು"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ಪ್ರತ್ಯುತ್ತರ ನೀಡು"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"ಕಳುಹಿಸಲು, ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಿ"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಿ"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"ಬದಲಾಯಿಸು"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು, ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಡೀಫಾಲ್ಟ್ SMS ಅಪ್ಲಿಕೇಶನ್‌ನಂತೆ ಹೊಂದಿಸಿ"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಯಾವುದೇ ಪ್ರಾಶಸ್ತ್ಯದ ಸಿಮ್ ಆಯ್ಕೆಮಾಡಲಾಗಿಲ್ಲ"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ಸಾಧನದ ಮಾಲೀಕರಿಂದ ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸಲಾಗಿಲ್ಲ"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ಸರಿ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"ಸಂವಾದದಲ್ಲಿ ಹಲವಾರು ಸಹಭಾಗಿಗಳಿದ್ದಾರೆ"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">ಅಮಾನ್ಯ ಸಂಪರ್ಕಗಳು</item>
+ <item quantity="other">ಅಮಾನ್ಯ ಸಂಪರ್ಕಗಳು</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"ಕ್ಯಾಮರಾ ಚಿತ್ರವನ್ನು ಲೋಡ್ ಮಾಡಲಾಗಲಿಲ್ಲ"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"ನೀವು: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ಡ್ರಾಫ್ಟ್‌"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"ನೀವು ಒಮ್ಮೆ ಹೊಸ ಸಂವಾದ ಪ್ರಾರಂಭಿಸಿದರೆ, ಅದು ಇಲ್ಲಿ ಪಟ್ಟಿ ಮಾಡಲಾಗಿರುವುದನ್ನು ನೋಡುತ್ತೀರಿ"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"ಆರ್ಕೈವ್‌ ಮಾಡಲಾದ ಸಂವಾದಗಳು ಇಲ್ಲಿ ಗೋಚರಿಸುತ್ತವೆ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"ಸಂವಾದಗಳನ್ನು ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ಚಿತ್ರ"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ಆಡಿಯೊ ಕ್ಲಿಪ್"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ವೀಡಿಯೊ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"ಸಂಪರ್ಕದ ಕಾರ್ಡ್"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"ರದ್ದುಮಾಡು"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ಮರುಪ್ರಯತ್ನಿಸು"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"ಹೊಸ ಸಂದೇಶವನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಂಪರ್ಕ ಹೆಸರು ಅಥವಾ ಫೊನ್ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ"</string>
+ <string name="action_block" msgid="9032076625645190136">"ನಿರ್ಬಂಧಿಸು"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> ಅನಿರ್ಬಂಧಿಸು"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> ನಿರ್ಬಂಧಿಸುವುದೇ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ಈ ಸಂಖ್ಯೆಯಿಂದ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ನೀವು ಮುಂದುವರೆಸುತ್ತೀರಿ ಆದರೆ ಇನ್ನು ಮುಂದೆ ಸೂಚನೆ ನೀಡುವುದಿಲ್ಲ. ಈ ಸಂವಾದವನ್ನು ಆರ್ಕೈವ್‌ ಮಾಡಲಾಗುವುದು."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"ನಿರ್ಬಂಧಿಸಲಾದ ಸಂಪರ್ಕಗಳು"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ಅನಿರ್ಬಂಧಿಸು"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"ನಿರ್ಬಂಧಿಸಲಾದ ಸಂಪರ್ಕಗಳು"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ಡಾಕ್ಯುಮೆಂಟ್ ಲೈಬ್ರರಿಯಿಂದ ಚಿತ್ರವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="sending_message" msgid="6363584950085384929">"ಸಂದೇಶ ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿದೆ"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"ಸೆಲ್ಯುಲಾರ್ ಡೇಟಾ ಆಫ್ ಮಾಡಲಾಗಿದೆ. ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರೀಕ್ಷಿಸಿ."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"ಸಂದೇಶಗಳನ್ನು ಏರ್‌ಪ್ಲೇನ್ ಮೋಡ್‌ನಲ್ಲಿ ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"ಸಂದೇಶ ಕಳುಸಲು ಆಗುತ್ತಿಲ್ಲ"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"ಸಂದೇಶ ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"ಸೆಲ್ಯುಲಾರ್ ಡೇಟಾ ಆಫ್ ಮಾಡಲಾಗಿದೆ. ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರೀಕ್ಷಿಸಿ."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ಏರ್‌ಪ್ಲೇನ್ ಮೋಡ್‌ನಲ್ಲಿ ಸಂದೇಶಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"ಸಂದೇಶವನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"ಶೂನ್ಯ"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ಒಂದು"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"ಎರಡು"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"ಮೂರು"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"ನಾಲ್ಕು"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ಐದು"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ಆರು"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ಏಳು"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ಎಂಟು"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ಒಂದತ್ತು"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> ಮೂಲಕ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ, ದೋಷ <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"ಅಜ್ಞಾತ ವಾಹಕದ ಮೂಲಕ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ, ದೋಷ <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"ಫಾರ್ವರ್ಡ್‌: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ಸೇವೆಯು ಸಕ್ರಿಯಗೊಂಡಿಲ್ಲ"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಅಮಾನ್ಯವಾದ ಗಮ್ಯಸ್ಥಾನ ವಿಳಾಸ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಅಮಾನ್ಯವಾದ ಸಂದೇಶ"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಬೆಂಬಲವಿಲ್ಲದ ವಿಷಯ"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಲಿಲ್ಲ: ಬೆಂಬಲವಿಲ್ಲದ ಸಂದೇಶ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲಾಗಿಲ್ಲ: ತುಂಬಾ ದೊಡ್ಡದಾಗಿದೆ"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"ಹೊಸ ಸಂದೇಶ"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"ವೀಕ್ಷಿಸಿ"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"ಚಿತ್ರ"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"ಸೂಕ್ತವಾದ ಅಪ್ಲಿಕೇಶನ್ ಕಂಡುಬಂದಿಲ್ಲ"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"ಸ್ವೀಕೃತಿದಾರರನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"ಹೊಸ ಸಂದೇಶ"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"ರದ್ದುಮಾಡು"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ಪ್ರವೇಶ ಬಿಂದು ಸಂಪಾದನೆ"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"ಹೊಂದಿಸಿಲ್ಲ"</string>
+ <string name="apn_name" msgid="1572691851070894985">"ಹೆಸರು"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS ಪ್ರಾಕ್ಸಿ"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS ಪೋರ್ಟ್"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN ಪ್ರಕಾರ"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN ಅಳಿಸು"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"ಹೊಸ APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"ಉಳಿಸು"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ತಿರಸ್ಕರಿಸು"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"ಹೆಸರಿನ ಕ್ಷೇತ್ರವು ಖಾಲಿ ಇರುವಂತಿಲ್ಲ."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ಖಾಲಿ ಇರುವಂತಿಲ್ಲ."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ಕ್ಷೇತ್ರವು 3 ಅಂಕಿಗಳಾಗಿರಬೇಕು."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ಕ್ಷೇತ್ರವು 2 ಅಥವಾ 3 ಅಂಕಿಗಳಾಗಿರಬೇಕು."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ಡೀಫಾಲ್ಟ್ APN ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಮರುಸ್ಥಾಪಿಸಲಾಗುತ್ತಿದೆ."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸು"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"ಡೀಫಾಲ್ಟ್ APN ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮರುಹೊಂದಿಕೆಯು ಪೂರ್ಣಗೊಂಡಿದೆ."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ಶೀರ್ಷಿಕೆರಹಿತ"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ಪ್ರವೇಶಿಸುವಿಕೆ ಕೇಂದ್ರದ ಹೆಸರುಗಳು"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN ಗಳು"</string>
+ <string name="menu_new" msgid="8286285392706532511">"ಹೊಸ APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ಪ್ರವೇಶ ಬಿಂದು ಹೆಸರಿನ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಈ ಬಳಕೆದಾರರಿಗೆ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸುವುದೇ?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"ನಕಲಿಸು"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> ಗೆ"</string>
+ <string name="general_settings" msgid="5409336577057897710">"ಸಾಮಾನ್ಯ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"ಸುಧಾರಿತ"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"ಸಾಮಾನ್ಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"ಸುಧಾರಿತ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" ಸಿಮ್‌"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ಎಲ್ಲಾ ಸ್ವೀಕೃತದಾರರಿಗೆ ಪ್ರತ್ಯೇಕ SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಿ. ಯಾವುದೇ ಪ್ರತ್ಯುತ್ತರಗಳು ನಿಮಗೆ ಮಾತ್ರ ಬರುತ್ತವೆ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"ಎಲ್ಲಾ ಸ್ವೀಕೃತಿದಾರರಿಗೆ ಒಂದೇ MMS ಕಳುಹಿಸಿ"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"ಅಜ್ಞಾತ ಸಂಖ್ಯೆ"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"ಹೊಸ ಸಂದೇಶ"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"ಹೊಸ ಸಂದೇಶ."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"ಸಿಮ್ ಆಯ್ಕೆದಾರ"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ, ಸಿಮ್ ಆಯ್ಕೆ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"ವಿಷಯವನ್ನು ಸಂಪಾದಿಸಿ"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"ಸಿಮ್ ಆಯ್ಕೆ ಮಾಡಿ ಅಥವಾ ವಿಷಯ ಸಂಪಾದಿಸಿ"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ಆಡಿಯೋ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"ಹೊಸ ಸಂವಾದವನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ ಪಟ್ಟಿ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"ಹೊಸ ಸಂದೇಶ"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"ಸಂವಾದ ಪಟ್ಟಿ"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"ಸಂವಾದಗಳನ್ನು ಲೋಡ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"ಸಂದೇಶಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"ಇನ್ನಷ್ಟು ಸಂವಾದಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"ಹೆಚ್ಚಿನ ಸಂದೇಶಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"ಸಂವಾದವನ್ನು ಅಳಿಸಲಾಗಿದೆ"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"ಸಂಭಾಷಣೆಯನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಬೇರೆ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆ ಸಂಭಾಷಣೆಯನ್ನು ತೋರಿಸಲು ಸ್ಪರ್ಶಿಸಿ"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"ಅನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="db_full" msgid="8459265782521418031">"ಸಂಗ್ರಹಣೆ ಸ್ಥಳಾವಕಾಶವು ಕಡಿಮೆ ಇದೆ. ಕೆಲವು ಡೇಟಾ ನಷ್ಟವಾಗಬಹುದು."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ಲಗತ್ತುಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"ಆಯ್ಕೆ ಖಚಿತಪಡಿಸಿ"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"ದಯವಿಟ್ಟು ಒಂದು ಅಥವಾ ಒಂದಕ್ಕಿಂತ ಹೆಚ್ಚಿನ ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕಿ ಮತ್ತು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"ನಿಮ್ಮ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ನೀವು ಪ್ರಯತ್ನಿಸಬಹುದು, ಆದರೆ ನೀವು ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕದ ಹೊರತು ಇದು ತಲುಪದಿರಬಹುದು."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"ನೀವು ಪ್ರತಿ ಸಂದೇಶಕ್ಕೆ ಒಂದು ವೀಡಿಯೊ ಮಾತ್ರ ಕಳುಹಿಸಬಹುದು. ದಯವಿಟ್ಟು ಹೆಚ್ಚುವರಿ ವೀಡಿಯೊಗಳನ್ನು ತೆಗೆದುಹಾಕಿ ಹಾಗೂ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಯು ಲಗತ್ತನ್ನು ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ಹೇಗಾದರೂ ಕಳುಹಿಸು"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"ಸಂವಾದವನ್ನು ಪ್ರಾರಂಭಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
+</resources>
diff --git a/res/values-ko/arrays.xml b/res/values-ko/arrays.xml
new file mode 100644
index 0000000..876776b
--- /dev/null
+++ b/res/values-ko/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"제목 없음"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"예"</item>
+ <item msgid="6049132459802288033">"아니요"</item>
+ <item msgid="3084376867445867895">"알겠습니다"</item>
+ <item msgid="3155097332660174689">"하하"</item>
+ <item msgid="2611328818571146775">"감사합니다."</item>
+ <item msgid="4881335087096496747">"동의합니다"</item>
+ <item msgid="2422296858597420738">"좋습니다."</item>
+ <item msgid="4805581752819452687">"가는 중입니다"</item>
+ <item msgid="4746700499431366214">"나중에 연락드리겠습니다"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..79949ea
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"메시지"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"메시지"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"대화 선택"</string>
+ <string name="action_settings" msgid="1329008122345201684">"설정"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"메시지 보내기"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"첨부파일 추가"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"도움말"</string>
+ <string name="welcome" msgid="2857560951820802321">"환영합니다."</string>
+ <string name="skip" msgid="7238879696319945853">"건너뛰기"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"다음 &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"다음"</string>
+ <string name="exit" msgid="1905187380359981199">"종료"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"설정 &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"설정"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"메시지를 사용하려면 SMS, 전화, 주소록에 대한 권한이 필요합니다."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"설정 &gt; 앱 &gt; 메시지 &gt; 권한에서 권한을 변경할 수 있습니다."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"설정, 앱, 메시지, 권한에서 권한을 변경할 수 있습니다."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"자주 연락하는 사람"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"모든 연락처"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"받는사람: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"사진 또는 동영상 캡처"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"이 기기에서 이미지 선택"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"오디오 녹음"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"사진 선택"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"미디어가 선택되었습니다."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"미디어가 선택 취소되었습니다."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g>개 선택됨"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"이미지 - <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" msgid="8142175029053370662">"이미지"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"오디오 녹음"</string>
+ <string name="action_share" msgid="2143483844803153871">"공유"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"방금 전"</string>
+ <string name="posted_now" msgid="867560789350406701">"지금"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>분</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>분</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>시간</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>시간</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>일</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>일</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>주</item>
+ <item quantity="one">1주</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>개월</item>
+ <item quantity="one">1개월</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>년</item>
+ <item quantity="one">1년</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"클래스 0 메시지"</string>
+ <string name="save" msgid="5081141452059463572">"저장"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"기기의 저장공간이 부족합니다. 메시지에서 공간을 확보하기 위해 오래된 메시지를 삭제합니다."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"저장 공간이 부족함"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"기기 공간이 더 확보되어야 메시지 앱에서 메시지를 보내고 받을 수 있습니다."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS 저장공간이 부족합니다. 메시지를 삭제해야 합니다."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"전화번호 확인"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"이 단계를 한 번만 실행하면 메시지 앱에서 그룹 메시지를 정확히 전달할 수 있습니다."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"전화번호"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"미디어가 포함된 모든 메시지 삭제"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g>보다 오래된 메시지 삭제"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> 이상 된 메시지 자동 삭제"</string>
+ <string name="ignore" msgid="7063392681130898793">"무시"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"미디어가 포함된 모든 메시지를 삭제하시겠습니까?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> 이상 된 메시지를 삭제하시겠습니까?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> 이상 된 메시지를 삭제하고 자동 삭제를 사용하시겠습니까?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g>님이 보낸 내용"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"보낸 내용"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g>님이 보낸 메시지"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"메시지 전송됨"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"보내는 중..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"전송되지 않았습니다. 다시 시도하려면 터치하세요."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"전송되지 않았습니다. 다시 시도하는 중…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"다시 보내기 또는 삭제"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"음성 통화로 응급 서비스에 연락하시기 바랍니다. 현재 SMS를 보낼 수 없습니다."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"실패"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"다운로드할 새 MMS 메시지"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"새 MMS 메시지"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"다운로드하지 못했습니다."</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"다시 시도하려면 터치"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"터치하여 다운로드"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"다운로드 또는 삭제"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"다운로드 중..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"메시지가 만료되었거나 사용할 수 없음"</string>
+ <string name="mms_info" msgid="3402311750134118165">"크기: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, 만료: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"보낼 수 없습니다. 수신자가 올바르지 않습니다."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"네트워크에 서비스가 활성화되어 있지 않습니다."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"네트워크 문제로 인해 전송하지 못했습니다."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"메시지가 만료되었거나 사용할 수 없음"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(제목 없음)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"알 수 없는 발신자"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"전송 완료"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g>님이 보낸 메시지(<xliff:g id="SUBJECT">%1$s</xliff:g>)를 다운로드할 수 없습니다."</string>
+ <string name="low_memory" msgid="5300743415198486619">"메모리가 부족하여 데이터베이스 작업을 완료할 수 없습니다."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"메시지를 보내지 못함"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"일부 메시지가 전송되지 않았습니다."</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other">대화 <xliff:g id="CONVERSATIONS">%d</xliff:g>개에 메시지 <xliff:g id="MESSAGES_1">%d</xliff:g>개</item>
+ <item quantity="one">대화 1개에 메시지 <xliff:g id="MESSAGES_0">%d</xliff:g>개</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"메시지를 다운로드하지 못했습니다."</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"일부 메시지가 다운로드되지 않았습니다."</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other">대화 <xliff:g id="CONVERSATIONS">%d</xliff:g>개에 메시지 <xliff:g id="MESSAGES_1">%d</xliff:g>개</item>
+ <item quantity="one">대화 1개에 메시지 <xliff:g id="MESSAGES_0">%d</xliff:g>개</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g>에 메시지를 보내지 못했습니다."</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"음성 통화로 응급 서비스에 연락하시기 바랍니다. 현재 <xliff:g id="NUMBER">%1$s</xliff:g>번으로 SMS를 보낼 수 없습니다."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other">새 메시지 <xliff:g id="MESSAGES">%d</xliff:g>개</item>
+ <item quantity="one">새 메시지 1개</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"시작"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"카메라를 사용할 수 없습니다."</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"카메라를 사용할 수 없습니다."</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"동영상을 캡처할 수 없습니다."</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"미디어를 저장할 수 없습니다."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"사진을 찍을 수 없음"</string>
+ <string name="back" msgid="1477626055115561645">"뒤로"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"보관처리됨"</string>
+ <string name="action_delete" msgid="4076795795307486019">"삭제"</string>
+ <string name="action_archive" msgid="5437034800324083170">"보관처리"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"보관 취소"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"알림 사용 중지"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"알림 사용"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"연락처 추가"</string>
+ <string name="action_download" msgid="7786338136368564146">"다운로드"</string>
+ <string name="action_send" msgid="377635240181672039">"전송"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"삭제"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"이 메일을 삭제하시겠습니까?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"이 작업은 취소할 수 없습니다."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"삭제"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">대화를 삭제하시겠습니까?</item>
+ <item quantity="one">대화를 삭제하시겠습니까?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"삭제"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"취소"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"받는사람"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"여러 이미지 선택"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"선택항목 확인"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"오디오를 녹음할 수 없습니다. 다시 시도해 주세요."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"오디오를 재생할 수 없습니다. 다시 시도해 주세요."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"오디오를 저장할 수 없습니다. 다시 시도해 주세요."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"길게 터치"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"사진"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"오디오 클립"</string>
+ <string name="notification_video" msgid="4331423498662606204">"동영상"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"연락처 카드"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"다운로드"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS를 통해 답장"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS로 답장"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"답장"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other">참여자 <xliff:g id="COUNT_1">%d</xliff:g>명</item>
+ <item quantity="one">참여자 <xliff:g id="COUNT_0">%d</xliff:g>명</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"나"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"연락처가 차단 및 보관되었습니다."</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"연락처 차단 해제 및 보관 취소됨"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g>개 보관됨"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g>개 보관 취소됨"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"알림 사용 중지됨"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"알림 사용 설정됨"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"준비가 완료되었습니다. 전송을 다시 터치해 주세요."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"메시지가 기본 SMS 앱으로 설정되었습니다."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">첨부파일 삭제</item>
+ <item quantity="one">첨부파일 삭제</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"오디오 첨부파일"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"오디오 첨부파일 재생"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"일시중지"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g>님의 메시지입니다. <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"받지 못한 <xliff:g id="SENDER">%s</xliff:g>님의 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>님의 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>님에게 전송되지 않은 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>님에게 전송 중인 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>님에게 전송하지 못한 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>님에게 보내는 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"받지 못한 <xliff:g id="SENDER">%s</xliff:g>님의 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>님의 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> 그룹에 전송되지 않은 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> 그룹에 전송 중인 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> 그룹에 전송하지 못한 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> 그룹에 보내는 메시지: <xliff:g id="MESSAGE">%s</xliff:g>. 시간: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"메시지를 전달하지 못했습니다. 다시 시도하려면 터치하세요."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g>님과 대화"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"제목 삭제"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"동영상 촬영"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"정지 이미지 캡처"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"사진 촬영"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"동영상 녹화 시작"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"전체화면 카메라로 전환"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"전면 및 후면 카메라 전환"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"녹화 중지 및 비디오 첨부"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"동영상 녹화 중지"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"메시지 사진"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">사진 <xliff:g id="QUANTITY_2">%d</xliff:g>장이 \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" 앨범에 저장됨</item>
+ <item quantity="one">사진 <xliff:g id="QUANTITY_0">%d</xliff:g>장이 \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" 앨범에 저장됨</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">동영상 <xliff:g id="QUANTITY_2">%d</xliff:g>개가 \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" 앨범에 저장됨</item>
+ <item quantity="one">동영상 <xliff:g id="QUANTITY_0">%d</xliff:g>개가 \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" 앨범에 저장됨</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">첨부파일 <xliff:g id="QUANTITY_2">%d</xliff:g>개가 \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" 앨범에 저장됨</item>
+ <item quantity="one">첨부파일 <xliff:g id="QUANTITY_0">%d</xliff:g>개가 \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" 앨범에 저장됨</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">첨부파일 <xliff:g id="QUANTITY_1">%d</xliff:g>개가 \"다운로드\"에 저장됨</item>
+ <item quantity="one">첨부파일 <xliff:g id="QUANTITY_0">%d</xliff:g>개가 \"다운로드\"에 저장됨</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">첨부파일 <xliff:g id="QUANTITY_1">%d</xliff:g>개가 첨부되었습니다.</item>
+ <item quantity="one">첨부파일 <xliff:g id="QUANTITY_0">%d</xliff:g>개가 저장되었습니다.</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">첨부파일 <xliff:g id="QUANTITY_1">%d</xliff:g>개를 저장하지 못했습니다.</item>
+ <item quantity="one">첨부파일 <xliff:g id="QUANTITY_0">%d</xliff:g>개를 저장하지 못했습니다.</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"저장된 MMS 첨부파일"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"설정"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"보관처리됨"</string>
+ <string name="action_close" msgid="1840519376200478419">"닫기"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"고급"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"디버그"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"알림"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"소리"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"무음"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"진동"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"차단됨"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS 전송 보고"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"모든 SMS에 대해 전송 확인 요청"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"자동 검색"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS 메시지를 자동으로 수신"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"로밍 중 자동 수신"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"로밍 시 MMS 자동 검색"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"그룹 메시지"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"수신자가 여러 명일 때 MMS를 사용하여 한 개의 메시지를 전송합니다."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"기본 SMS 앱"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"기본 SMS 앱"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"전화번호"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"알 수 없음"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"발신 메시지 소리"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS 덤프"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"수신된 SMS 원시 데이터를 외부 저장소 파일에 덤프합니다."</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS 덤프"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"수신된 MSS 원시 데이터를 외부 저장소 파일에 덤프합니다."</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"긴급 무선 경보"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"메시지 옵션"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"텍스트 복사"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"세부정보 보기"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"삭제"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"전달"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"메시지 세부정보"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"유형: "</string>
+ <string name="text_message" msgid="7415419755252205721">"문자 메시지"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"멀티미디어 메시지"</string>
+ <string name="from_label" msgid="1947831848146564875">"보낸사람: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"받는사람: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"보낸 시간: "</string>
+ <string name="received_label" msgid="4442494712757995203">"받은 시간: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"제목: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"크기: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"중요도: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"높음"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"보통"</string>
+ <string name="priority_low" msgid="7398724779026801851">"낮음"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"숨긴 발신자 주소"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"파일을 로드하는 동안 메시지를 보낼 수 없습니다."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"첨부파일을 로드할 수 없습니다. 다시 시도해 주세요."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"네트워크가 준비되지 않았습니다. 다시 시도해 주세요."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"텍스트 삭제"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"텍스트 및 숫자 입력 간 전환"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"더 많은 참가자 추가"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"참가자 확인"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"새로운 대화 시작"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"이 항목을 선택합니다."</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Play 동영상"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"사용자 및 옵션"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"디버그"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"사용자 및 옵션"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"일반"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"이 대화에 참여하는 사용자"</string>
+ <string name="action_call" msgid="6596167921517350362">"전화 걸기"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"메시지 보내기"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;에서 메시지 보내기"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">사진 전송</item>
+ <item quantity="one">사진 전송</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">오디오 전송</item>
+ <item quantity="one">오디오 전송</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">동영상 전송</item>
+ <item quantity="one">동영상 전송</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">연락처 카드 전송</item>
+ <item quantity="one">연락처 카드 전송</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">첨부파일 전송</item>
+ <item quantity="one">첨부파일 전송</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other">첨부파일 <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g>개 전송 준비 완료</item>
+ <item quantity="one">첨부파일 1개 전송 준비 완료</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"의견 보내기"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play 스토어에서 보기"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"버전 정보"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"버전: %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"오픈소스 라이선스"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"알림"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"첨부파일 한도 도달"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"첨부파일을 로드하지 못했습니다."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"주소록에 추가하시겠습니까?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"연락처 추가"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"제목"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"제목: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"연락처 카드 로드 중"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"연락처 카드를 로드할 수 없습니다."</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"연락처 카드 보기"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other">연락처 <xliff:g id="COUNT_1">%d</xliff:g>개</item>
+ <item quantity="one">연락처 <xliff:g id="COUNT_0">%d</xliff:g>개</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"연락처 카드"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"생일"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"메모"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"메시지 전달"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"답장"</string>
+ <string name="plus_one" msgid="9010288417554932581">"외 1명"</string>
+ <string name="plus_n" msgid="8961547034116059566">"외 %d명"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS 사용 중지됨"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"메시지를 전송하려면 메시지를 기본 SMS 앱으로 설정하세요."</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"메시지를 기본 SMS 앱으로 설정하세요."</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"변경"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"메시지를 수신하려면 메시지를 기본 SMS 앱으로 설정하세요."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS 메시지 보내기에 사용할 기본 앱이 선택되어 있지 않습니다."</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"기기 소유자가 허용하지 않은 앱입니다."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"확인"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"대화 참가자가 너무 많습니다."</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">잘못된 연락처</item>
+ <item quantity="one">잘못된 연락처</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"카메라 이미지를 로드할 수 없습니다."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"나: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"임시보관"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"새 대화를 시작하면 여기에 표시됩니다."</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"보관처리된 대화는 여기에 표시됩니다."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"대화 로드 중..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"사진"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"오디오 클립"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"동영상"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"연락처 카드"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"실행취소"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"다시 시도"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"새로운 메시지를 시작하려면 연락처 이름 또는 전화번호를 입력하세요."</string>
+ <string name="action_block" msgid="9032076625645190136">"차단"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> 차단"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> 차단 해제"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>을(를) 차단하시겠습니까?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"앞으로 이 번호에서 보낸 메시지를 계속 받지만 알림은 더 이상 받지 않습니다. 이 대화는 보관처리됩니다."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"차단된 연락처"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"차단 해제"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"차단된 연락처"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"문서 라이브러리에서 이미지 선택"</string>
+ <string name="sending_message" msgid="6363584950085384929">"메시지를 전송하는 중입니다."</string>
+ <string name="send_message_success" msgid="4088006261869323324">"메시지가 전송되었습니다."</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"이동통신 데이터 사용이 중지되어 있습니다. 설정을 확인하세요."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"비행기 모드에서는 메시지를 보낼 수 없습니다."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"메시지를 보내지 못했습니다."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"메시지가 다운로드되었습니다."</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"이동통신 데이터 사용이 중지되어 있습니다. 설정을 확인하세요."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"비행기 모드에서 메시지를 다운로드할 수 없습니다."</string>
+ <string name="download_message_failure" msgid="635370887537738004">"메시지를 다운로드할 수 없습니다."</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"0"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"1"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"2"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"3"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"4"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"5"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"6"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"7"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"8"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"9"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g>을(를) 사용하여 메시지를 보낼 수 없습니다. 오류: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"알 수 없는 이동통신사를 사용하여 메시지를 보낼 수 없습니다. 오류: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"전달: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"서비스가 네트워크에서 활성화되지 않아 메시지가 전송되지 않았습니다."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"메시지가 전송되지 않음: 잘못된 수신 주소"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"메시지가 전송되지 않음: 잘못된 메시지"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"메시지가 전송되지 않음: 지원되지 않는 콘텐츠"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"메시지가 전송되지 않음: 지원되지 않는 메시지"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"메시지가 전송되지 않음: 용량 초과"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"새 메시지"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"보기"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"이미지"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"관련 애플리케이션을 찾을 수 없습니다."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"수신자 삭제"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"새 메시지"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"취소"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"액세스포인트 네임(APN) 수정"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"설정되지 않음"</string>
+ <string name="apn_name" msgid="1572691851070894985">"이름"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS 프록시"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS 포트"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN 유형"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN 삭제"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"새 APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"저장"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"취소"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"이름 입력란은 비워둘 수 없습니다."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN을 비워둘 수 없습니다."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC 입력란에는 3자리 숫자를 입력해야 합니다."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC 입력란에는 2~3자리 숫자를 입력해야 합니다."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"기본 APN 설정 복원 중"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"기본값으로 재설정"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"기본 APN 설정을 초기화했습니다."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"제목 없음"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"APN"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"새 APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"이 사용자는 액세스포인트 네임(APN) 설정을 수정할 수 없습니다."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"클립보드에 복사할까요?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"복사"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"전송 대상: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"일반"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"고급"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"일반 설정"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"고급 설정"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\'<xliff:g id="SIM_NAME">%s</xliff:g>\' SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"모든 수신자에게 개별 SMS 보내기(나만 답장을 받을 수 있음)"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"모든 수신자에게 하나의 MMS 보내기"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"알 수 없는 번호"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"새 메시지"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"새 메시지"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM 선택기"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> 선택됨. SIM 선택기"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"제목 수정"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM 선택 또는 제목 수정"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"길게 눌러 오디오 녹음"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"새로운 대화 시작"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"메시지"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"메시지 목록"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"메시지"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"새 메시지"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"대화 목록"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"대화 로드 중"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"메시지 로드 중"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"대화 더보기"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"메시지 더보기"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"대화를 삭제함"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"대화가 삭제되었습니다. 다른 메시지 대화를 표시하려면 터치하세요."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"차단됨"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"차단 해제됨"</string>
+ <string name="db_full" msgid="8459265782521418031">"저장공간이 충분하지 않아 일부 데이터가 저장되지 않을 수 있습니다."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"첨부파일 선택"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"선택항목 확인"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g>개 선택됨"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"하나 이상의 첨부파일을 삭제하고 다시 시도하세요."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"메시지 보내기를 시도할 수는 있지만 하나 이상의 첨부파일을 삭제하지 않으면 전송되지 않을 수 있습니다."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"메시지당 하나의 동영상만 보낼 수 있습니다. 추가 동영상을 삭제하고 다시 시도하세요."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"메시지에서 첨부파일이 로드되지 않았습니다."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"무시하고 보내기"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"대화를 시작할 수 없습니다."</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g>(<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> 선택됨"</string>
+</resources>
diff --git a/res/values-ky-rKG/arrays.xml b/res/values-ky-rKG/arrays.xml
new file mode 100644
index 0000000..0227144
--- /dev/null
+++ b/res/values-ky-rKG/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"темасы жок"</item>
+ <item msgid="272485471009191934">"темасыжок"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ооба"</item>
+ <item msgid="6049132459802288033">"Жок"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Хе-хе"</item>
+ <item msgid="2611328818571146775">"Рахмат"</item>
+ <item msgid="4881335087096496747">"Туура"</item>
+ <item msgid="2422296858597420738">"Сонун"</item>
+ <item msgid="4805581752819452687">"Жолдомун"</item>
+ <item msgid="4746700499431366214">"Макул, бир аздан кийин кайрылайынчы"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
new file mode 100644
index 0000000..b53ca0c
--- /dev/null
+++ b/res/values-ky-rKG/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"SMS/MMS"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"SMS/MMS"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Маек тандаңыз"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Жөндөөлөр"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Билдирүү жөнөтүү"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Тиркеме кошуңуз"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Жардам"</string>
+ <string name="welcome" msgid="2857560951820802321">"Кош келиңиз"</string>
+ <string name="skip" msgid="7238879696319945853">"Өткөрүп жиберүү"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Кийинки &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Кийинки"</string>
+ <string name="exit" msgid="1905187380359981199">"Чыгуу"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Жөндөөлөр &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Жөндөөлөр"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"SMS/MMS\'ке SMS, телефон жана байланыштар уруксаты керек."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Уруксаттарды өзгөртүү үчүн Жөндөөлөр &gt; Колдонмолор &gt; SMS/MMS &gt; уруксаттарды басыңыз."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Уруксаттарды өзгөртүү үчүн Жөндөөлөр, Колдонмолор, SMS/MMS, уруксаттарды басыңыз."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Көп байланышкандар"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Бардык байланыштар"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Төмөнкүгө жөнөтүү <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Сүрөттөрдү же видео тартып алуу"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Бул түзмөктөн сүрөттөрдү тандаңыз"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Аудио жаздыруу"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Сүрөт тандаңыз"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Медиа файл тандалды."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Медиа файл тандоодон чыгарылды."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> тандалды"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"сүрөт <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" msgid="8142175029053370662">"сүрөт"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Аудио жаздыруу"</string>
+ <string name="action_share" msgid="2143483844803153871">"Бөлүшүү"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Жаңы эле"</string>
+ <string name="posted_now" msgid="867560789350406701">"Азыр"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> мүн</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> мүн</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> саат</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> саат</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> күн</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> күн</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> апта</item>
+ <item quantity="one">бир апта</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ай</item>
+ <item quantity="one">бир ай</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> жыл</item>
+ <item quantity="one">бир жыл</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0 классындагы билдирүү"</string>
+ <string name="save" msgid="5081141452059463572">"Сактоо"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Түзмөктө бош орун жетишпейт. Орун бошотуу үчүн, SMS/MMS эски SMS билдирүүлөрүн автоматтык түрдө жок кылат."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Сактагычта орун калбай баратат"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Түзмөгүңүздө көбүрөөк бош орун болмоюнча, SMS/MMS билдирүүлөрдү жөнөтүп же ала албашы мүмкүн."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS үчүн орун жетишпейт. Билдирүүлөрдү жок кылышыңыз керек окшойт."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Телефон номериңизди ырастаңыз"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Номер топтошкон SMS/MMS билдирүүлөрүн жөнөтүү үчүн талап кылынат. Аны бир гана жолу киргизүү керек."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Телефон номери"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Медиа файл камтылган бардык билдирүүлөрдү жок кылуу"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> ашкан билдирүүлөрдү жок кылуу"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> мурунку билдирүүлөр автоматтык түрдө жок кылынсын"</string>
+ <string name="ignore" msgid="7063392681130898793">"Көңүл бурбоо"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Медиа менен бардык файлдар жок кылынсынбы?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> мурунку файлдар жок кылынсынбы?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> мурунку билдирүүлөр жок кылынып, авто жок кылуу күйгүзүлсүнбү?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> айтты"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Сиз айттыңыз"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> дегенден билдирүү"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Билдирүү жөнөттүңүз"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Жөнөтүлүүдө…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Жөнөтүлгөн жок. Дагы аракет кылуу үчүн тийип коюңуз."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Жөнөтүлгөн жок. Кайра аракет жасалууда…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Кайра жөнөтүү же жок кылуу"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Өзгөчө кырдаал кызматтарына үн чалуу жасаңыз. Азыркы учурда текст билдирүүңүз жеткирилбей койду."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Болбой калды"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Жүктөлүп алынуучу жаңы MMS билдирүү"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Жаңы MMS билдирүү"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Жүктөлүп алынган жок"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Дагы аракет кылуу үчүн тийип коюңуз"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Жүктөп алуу үчүн тийип коюңуз"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Жүктөп алуу же жок кылуу"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Жүктөлүүдө…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Билдирүү мөөнөтү аяктады же жеткиликтүү эмес"</string>
+ <string name="mms_info" msgid="3402311750134118165">"өлчөмү: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, мөөнөтү бүтөт: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Жөнөтүлбөй жатат. Алуучу жарактуу эмес."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Тармакта кызмат жандырылган эмес"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Тармактагы көйгөйдөн улам жөнөтүлгөн жок"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Билдирүү мөөнөтү аяктады же жеткиликтүү эмес"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Темасы жок)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Белгисиз жөнөтүүчү"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Жеткирилди"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> келген <xliff:g id="SUBJECT">%1$s</xliff:g> билдирүүсү жүктөлүп алынган жок."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Сактагычта орун аз болгондуктан, дайындар базасы аяктабай калды"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Билдирүү жөнөтүлгөн жок"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Айрым SMS/MMS билдирүүлөрү жөнөтүлгөн жок"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> баарлашууда <xliff:g id="MESSAGES_1">%d</xliff:g> билдирүү</item>
+ <item quantity="one">Бир баарлашууда <xliff:g id="MESSAGES_0">%d</xliff:g> билдирүү</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Билдирүү жүктөлүп алынган жок"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Айрым SMS/MMS билдирүүлөрү жүктөлүп алынган жок"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> баарлашууда <xliff:g id="MESSAGES_1">%d</xliff:g> билдирүү</item>
+ <item quantity="one">Бир баарлашууда <xliff:g id="MESSAGES_0">%d</xliff:g> билдирүү</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> дегенге билдирүү жөнөтүлгөн жок"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Өзгөчө кырдаал кызматтарына үн чалуу жасаңыз. Азыркы учурда <xliff:g id="NUMBER">%1$s</xliff:g> номерине текст билдирүүңүз жеткирилбей койду."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> жаңы билдирүү</item>
+ <item quantity="one">Жаңы билдирүү</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Баштоо"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Камера жеткиликтүү эмес"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Камера жеткиликтүү эмес"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Видеону тартып алуу жеткиликтүү эмес"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Медиа сакталбай жатат"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Сүрөткө тарта албайт"</string>
+ <string name="back" msgid="1477626055115561645">"Артка"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архивделди"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Жок кылуу"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архивдөө"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Архивден чыгаруу"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Эскертмелерди өчүрүү"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Эскертмелерди күйгүзүү"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Байланыш кошуу"</string>
+ <string name="action_download" msgid="7786338136368564146">"Жүктөп алуу"</string>
+ <string name="action_send" msgid="377635240181672039">"Жөнөтүү"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Жок кылуу"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Бул билдирүү жок кылынсынбы?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Бул аракетти артка кайтаруу мүмкүн эмес."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Жок кылуу"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Бул баарлашуулөр жок кылынсынбы?</item>
+ <item quantity="one">Бул баарлашуу жок кылынсынбы?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Жок кылуу"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Жокко чыгаруу"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Кимге"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Бир нече сүрөт тандаңыз"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Тандалгандарды ырастаңыз"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Аудио жазылбай жатат. Дагы аракет кылыңыз."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Аудио ойнотулбай жатат. Дагы аракет кылыңыз."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Аудио сакталган жок. Дагы аракет кылыңыз."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Коё бербей amp; тийип туруу"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Сүрөт"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудио клип"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Видео"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Байланыш картасы"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Жүктөп алуу"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS аркылуу жооп берүү"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS аркылуу жооп берүү"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Жооп берүү"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> катышуучу</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> катышуучу</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Мен"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Байланыш бөгөттөлдү &amp; архивделди"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Байланыш бөгөттөн жана архивден чыгарылды"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> архивделди"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> архивден чыгарылды"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Эскертмелер өчүрүлгөн"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Эскертмелер күйгүзүлгөн"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Баары коюлду. Жөнөтүүнү кайра басыңыз."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"SMS/MMS демейки SMS колдонмосу катары коюлду."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Тиркемелерди жарактан чыгаруу</item>
+ <item quantity="one">Тиркемени жарактан чыгаруу</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Аудио тиркеме"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Аудио тиркемени ойнотуу"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Бир азга токтотуу"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> жөнөткөн билдирүү: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү бузулду. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Төмөнкүгө жиберилбеген билдирү <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Төмөнкүгө билдирүү жөнөтүлүүдө: <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү бузулду. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү бузулду. Убакыт: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү. Убакыт: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Төмөнкүгө жиберилбеген билдирүү: <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Төмөнкүгө билдирүү жөнөтүлүүдө: <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү бузулду. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> билдирүүсү. Убакыт: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Билдирүү жөнөтүлбөй койду. Кайра аракет кылуу үчүн тийип коюңуз."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> менен сүйлөшүү"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Темасын жок кылуу"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Видео жаздыруу"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Кыймылдабаган сүрөттү тартып алуу"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Сүрөт тартуу"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Видео тартып баштоо"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Камераны толук экранга которуу"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Алдыңкы жана арткы камераны которуштуруу"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Тартылып жаткан видеону токтотуп, тиркеңиз"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Видео жаздырууну токтотуу"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"SMS/MMS сүрөттөрү"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> сүрөт \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" альбомуна сакталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> сүрөт \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" альбомуна сакталды</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> видео \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" альбомуна сакталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> видео \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" альбомуна сакталды</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> тиркеме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" альбомуна сакталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тиркеме \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" альбомуна сакталды</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> тиркеме \"Жүктөлүп алынгандарга\" сакталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тиркеме \"Жүктөлүп алынгандарга\" сакталды</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> тиркеме сакталды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тиркеме сакталды</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> тиркеме сакталбай калды</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> тиркеме сакталбай калды</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Сакталган MMS тиркемеси"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Жөндөөлөр"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архивделди"</string>
+ <string name="action_close" msgid="1840519376200478419">"Жабуу"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Өркүндөтүлгөн"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Мүчүлүштүктөрдү таап оңдоо"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Эскертмелер"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Үн"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Үнсүз"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Дирилдөө"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Бөгөттөлгөн"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS жеткирүү кабарлары"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Ар бир жөнөтүлгөн SMS билдирүүсүнүн алынгандыгын кабарлап коюну өтүнүү"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Авто-түшүрүү"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS билдирүүлөрүн автоматтык түрдө түшүрүп алуу"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Роумингде авто-түшүрүп алуу"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Роуминг учурунда MMS\'ти автоматтык түрдө чыгарып алуу"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Топтошуп билдирүү жазышуу"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Бир нече алуучуга бир билдирүү жөнөтүү үчүн MMS\'ти колдонуңуз"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Демейки SMS колдонмо"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Демейки SMS колдонмо"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Телефон номериңиз"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Белгисиз"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Билдирүү кеткенде добуш чыксын"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS\'ти ыргытуу"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Алынган SMS\'тин чийки дайындарын тышкы сактагычтагы файлга ыргытыңыз"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS\'ти ыргытуу"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Алынган MMS\'тин чийки дайындарын тышкы сактагычтагы файлга ыргытыңыз"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Зымсыз айгай кабарлар"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Билдирүүнүн параметрлери"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Текстти көчүрүү"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Дайындарды карап көрүү"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Жок кылуу"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Багыттоо"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Билдирүүнүн чоо-жайы"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Түрү: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS билдирүүсү"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Мультимедиа билдирүүсү"</string>
+ <string name="from_label" msgid="1947831848146564875">"Кимден: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Кимге: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Жөнөтүлдү: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Алынды: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Темасы: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Өлчөмү: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Шашылыштыгы: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Жогору"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Орточо"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Төмөн"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Дареги жашырылган жөнөтүүчү"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Тиркемелер жүктөлүп жатканда билдирүү жөнөтүлбөйт."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Тиркеме жүктөлбөй жатат. Дагы аракет кылыңыз."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Тармак даяр эмес. Дагы аракет кылыңыз."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Текстти жок кылуу"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Текст жана сандарды киргизүү ыкмаларын которуштуруу"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Дагы катышуучуларды кошуу"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Катышуучуларды ырастоо"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Жаңы сүйлөшүү баштоо"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Бул нерсени тандоо"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Видео ойнотуу"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Адамдар &amp; параметрлер"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Мүчүлүштүктөрдү таап оңдоо"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Адамдар &amp; параметрлер"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Жалпы"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Бул маектеги адамдар"</string>
+ <string name="action_call" msgid="6596167921517350362">"Чалуу"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Билдирүү жөнөтүү"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt; <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; аркылуу билдирүү жөнөтүү"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Сүрөттөрдү жөнөтүү</item>
+ <item quantity="one">Сүрөт жөнөтүү</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Аудиолорду жөнөтүү</item>
+ <item quantity="one">Аудио жөнөтүү</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Видеолорду жөнөтүү</item>
+ <item quantity="one">Видео жөнөтүү</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Байланыш карталарын жөнөтүү</item>
+ <item quantity="one">Байланыш картасын жөнөтүү</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Тиркемелерди жөнөтүү</item>
+ <item quantity="one">Тиркемени жөнөтүү</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> тиркеме жөнөтүүгө даяр</item>
+ <item quantity="one">Бир тиркеме жөнөтүүгө даяр</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Жооп пикир жөнөтүү"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Дүкөнүндө карап көрүү"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Версия маалыматы"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"%1$s версиясы"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Ачык булак лицензиялары"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Эскертмелер"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Тиркеме чегине жетти"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Тиркеме жүктөлбөй койду."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Байланыштарга кошулсунбу?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Байланыш кошуу"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Тема"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Темасы: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Байланыш картасы жүктөлүүдө"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Байланыштын картасы жүктөлгөн жок"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Байланыш картасын карап көрүү"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> байланыш</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> байланыш</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Байланыш карталары"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Туулган күн"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Эскертүүлөр"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Билдирүүнү багыттоо"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Жооп берүү"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS өчүк"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Билдирүүлөрдү жөнөтүү үчүн, SMS/MMS\'ти демейки SMS колдонмосу катары коюңуз"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"SMS/MMS\'ти демейки SMS колдонмосу катары коюңуз"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Өзгөртүү"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Билдирүүлөрдү алуу үчүн, SMS/MMS\'ти демейки SMS колдонмосу катары коюңуз"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS билдирүүлөрдү жөнөтүү үчүн артыкчылыктуу SIM тандалган эмес"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Бул колдонмого түзмөк ээсинен уруксат жок."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Маекте катышуучулар өтө эле көп"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Жараксыз байланыштар</item>
+ <item quantity="one">Жараксыз байланыш</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Камера сүрөтү жүктөлгөн жок"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Сиз: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Сомо"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Жаңы маек башталганда, аны бул жердеги тизмеден көрөсүз"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Архивделген маектер бул жерден көрүнөт"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Маектер жүктөлүүдө…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Сүрөт"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудио клип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Видео"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Байланыш картасы"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Кайтаруу"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Дагы аракет кылуу"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Жаңы билдирүүнү жазып баштоо үчүн байланыштын атын же телефон номерин киргизиңиз"</string>
+ <string name="action_block" msgid="9032076625645190136">"Бөгөттөө"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> дегенди бөгөттөө"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> дегенди бөгөттөн чыгаруу"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> бөгөттөлсүнбү?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Бул номерден билдирүүлөрдү ала бересиз, бирок мындан ары эскертмелерди албай каласыз. Бул маек архивделет."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Бөгөттөлгөн байланыштар"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"БӨГӨТТӨН ЧЫГАРУУ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Бөгөттөлгөн байланыштар"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Документтер китепканасынан сүрөт тандаңыз"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Билдирүү жөнөтүлүүдө…"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Билдирүү жөнөтүлдү"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Уюлдук дайындар өчүрүлгөн. Жөндөөлөрүңүздү текшериңиз."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Учак режиминде билдирүүлөр жөнөтүлбөйт"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Билдирүү жөнөтүлбөй жатат"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Билдирүү жүктөлүп алынды"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Уюлдук дайындар өчүрүлгөн. Жөндөөлөрүңүздү текшериңиз."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Учак режиминде билдирүүлөрдү жүктөп ала албайт"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Билдирүү жүктөлүп алынбай койду"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Ноль"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Бир"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Эки"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Үч"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Төрт"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Беш"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Алты"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Жети"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Сегиз"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Тогуз"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Билдирүүнү <xliff:g id="CARRIERNAME">%1$s</xliff:g> оператору аркылуу жөнөтүү мүмкүн эмес, ката <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Билдирүүнү белгисиз түзмөк аркылуу жөнөтүү мүмкүн эмес, ката <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Багыттоо: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Билдирүү жөнөтүлгөн жок: кызмат тармакта жандырылган эмес"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Билдирүү жөнөтүлгөн жок: жеткирүү дареги жараксыз"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Билдирүү жөнөтүлгөн жок: билдирүү жараксыз"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Билдирүү жөнөтүлгөн жок: мазмун колдоого алынбайт"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Билдирүү жөнөтүлгөн жок: билдирүү колдоого алынбайт"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Билдирүү жөнөтүлгөн жок: өтө чоң"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Жаңы билдирүү"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Карап көрүү"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Сүрөт"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Ылайыктуу колдонмо табылган жок"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Алуучуну чыгарып салуу"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Жаңы билдирүү"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Жокко чыгаруу"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Мүмкүнчүлүк алуу түйүнүн түзөтүү"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Коюлган эмес"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Ысымы"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS прокси"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS порту"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN түрү"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN жок кылуу"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Жаңы APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Сактоо"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Жарактан чыгаруу"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Аталыш талаасы бош болбошу керек."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN бош болбошу керек."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC талаасы 3 сандан турушу керек."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC талаасы 2 же 3 сандан турушу керек."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Демейки APN жөндөөлөрү калыбына келтирилүүдө."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Баштапкы абалга келтирүү"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Демейки APN жөндөөлөрү баштапкы абалга келтирилди."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Аталышы жок"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Мүмкүнчүлүк алуу түйүнүнүн аталыштары"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN\'дер"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Жаңы APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Бул колдонуучу мүмкүнчүлүк алуу түйүнүнүн аталышынын жөндөөлөрүн колдоно албайт"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Буферге көчүрүлсүнбү?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Көчүрүү"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> SIMге"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Жалпы"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Өркүндөтүлгөн"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Жалпы жөндөөлөр"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Өркүндөтүлгөн жөндөөлөр"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Бардык алуучуларга жеке SMS билдирүүлөрүн жөнөтүү. Бардык жоопторду сиз гана аласыз"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Бардык алуучуларга бир MMS жөнөтүү"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Белгисиз номер"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Жаңы билдирүү"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Жаңы билдирүү."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM тандагыч"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> тандалган, SIM тандагыч"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Теманы түзөтүү"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM тандоо же теманы түзөтүү"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Аудио жаздырыш үчүн кол тийгизип туруңуз"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Жаңы сүйлөшүү баштоо"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS/MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"SMS/MMS тизмеси"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS/MMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Жаңы билдирүү"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Маек тизмеси"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Сүйлөшүүлөр жүктөлүүдө…"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Билдирүүлөр жүктөлүүдө"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Көбүрөөк сүйлөшүүлөрдү көрүү"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Дагы билдирүүлөрдү көрүү"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Сүйлөшүү жок кылынды"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Сүйлөшүү жок кылынды. Башка SMS/MMS сүйлөшүүсүн көрсөтүү үчүн тийиңиз"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Бөгөттөлдү"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Бөгөттөн чыгарылды"</string>
+ <string name="db_full" msgid="8459265782521418031">"Сактагычта бош орун аз. Айрым дайындар жоголушу мүмкүн."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Тиркемелерди тандаңыз"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Тандалгандарды ырастаңыз"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> тандалды"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Тиркемелердин бирин же көбүрөөгүн алып салып, кайра аракет кылыңыз."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Билдирүүңүздү жөнөтүп көрсөңүз болот, бирок ал бир же көбүрөөк тиркемени алып салмайыңызча жеткирилбеши мүмкүн."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Бир билдирүү менен бир эле видео жөнөтө аласыз. Кошумча видеолорду алып салып, кайра аракет кылыңыз."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Тиркелген файлдар жүктөлбөй калды."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Баары бир жөнөтүлсүн"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Сүйлөшүүнү баштай алган жок"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> тандалды"</string>
+</resources>
diff --git a/res/values-ldrtl-v21/styles.xml b/res/values-ldrtl-v21/styles.xml
new file mode 100644
index 0000000..4775979
--- /dev/null
+++ b/res/values-ldrtl-v21/styles.xml
@@ -0,0 +1,23 @@
+<?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>
+ <style name="ComposeMessageViewAttachMediaButtonStyle">
+ <item name="android:paddingStart">@dimen/compose_message_attachment_padding_sides</item>
+ <item name="android:paddingEnd">@dimen/compose_message_attachment_padding_sides</item>
+ <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+ </style>
+</resources>
diff --git a/res/values-ldrtl/styles.xml b/res/values-ldrtl/styles.xml
new file mode 100644
index 0000000..bd270c7
--- /dev/null
+++ b/res/values-ldrtl/styles.xml
@@ -0,0 +1,273 @@
+<?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>
+ <style name="AttachmentPreviewCloseButtonStyle">
+ <item name="android:gravity">start</item>
+ </style>
+
+ <style name="ComposeMessageViewAttachMediaButtonStyle">
+ <item name="android:paddingStart">@dimen/compose_message_attachment_padding_sides</item>
+ <item name="android:paddingEnd">@dimen/compose_message_attachment_padding_sides</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="ConversationComposeSendText">
+ <item name="android:textColor">@color/compose_send_text_color</item>
+ <item name="android:singleLine">false</item>
+ <item name="android:textSize">@dimen/conversation_compose_send_text_size</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:paddingStart">@dimen/compose_message_text_box_padding_side</item>
+ <item name="android:paddingEnd">@dimen/compose_message_text_box_padding_side</item>
+ <item name="android:minHeight">@dimen/conversation_message_contact_icon_size</item>
+ <item name="android:maxLines">4</item>
+ <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>
+ </style>
+
+ <style name="ConversationComposeSubjectText" parent="ConversationComposeSendText">
+ <item name="android:paddingStart">18dp</item>
+ <item name="android:gravity">start|center_vertical</item>
+ <item name="android:layout_marginStart">@dimen/compose_message_subject_cancel_left_margin</item>
+ </style>
+
+ <style name="ContactListItemViewStyle">
+ <item name="android:paddingStart">16dp</item>
+ </style>
+
+ <style name="ContactListItemLinearLayoutStyle">
+ <item name="android:paddingStart">12dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ </style>
+
+ <style name="ConversationListItemViewPaddingStyle">
+ <item name="android:paddingStart">@dimen/conversation_list_item_view_padding</item>
+ <item name="android:paddingEnd">@dimen/conversation_list_item_view_padding</item>
+ </style>
+
+ <style name="ConversationListItemViewTextStyle">
+ <item name="android:includeFontPadding">false</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:background">@null</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:textColor">@color/conversation_list_details</item>
+ </style>
+
+ <style name="ConversationListItemViewConversationNameStyle" parent="ConversationListItemViewTextStyle">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/conversation_list_name</item>
+ </style>
+
+ <style name="ConversationListNotificationBellPaddingStyle">
+ <item name="android:paddingEnd">@dimen/conversation_list_notification_bell_padding</item>
+ </style>
+
+ <style name="ComposeMessageViewFrameLayoutStyle">
+ <item name="android:paddingEnd">@dimen/compose_message_send_button_padding_right</item>
+ </style>
+
+ <style name="ConversationListFragmentStartNewButtonStyle">
+ <item name="android:layout_marginEnd">@dimen/fab_left_right_margin</item>
+ </style>
+
+ <style name="GalleryGridItemViewCheckBoxStyle">
+ <item name="android:paddingEnd">4dp</item>
+ </style>
+
+ <style name="BugleActionBarTitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">
+ <item name="android:textColor">@color/action_bar_title_text_color</item>
+ <item name="android:textSize">@dimen/action_bar_text_size</item>
+ <item name="android:lines">1</item>
+ <item name="android:textAlignment">viewStart</item>
+ </style>
+
+ <style name="AttachmentPreviewAttachmentStyle">
+ <item name="android:layout_marginStart">10dp</item>
+ </style>
+
+ <style name="AudioAttachmentViewPlayPauseButtonStyle">
+ <item name="android:layout_marginEnd">16dp</item>
+ </style>
+
+ <style name="ComposeMessageViewDraftViewStyle">
+ <item name="android:layout_marginStart">53dp</item>
+ <item name="android:layout_marginEnd">63dp</item>
+ </style>
+
+ <style name="AudioAttachmentTimerText">
+ <item name="android:textSize">@dimen/audio_attachment_text_size</item>
+ <item name="android:textColor">@color/audio_attachment_timer_text_color</item>
+ <item name="android:layout_marginEnd">16dp</item>
+ </style>
+
+ <style name="ConversationMessageText">
+ <item name="android:background">@null</item>
+ <item name="android:textAlignment">viewStart</item>
+ </style>
+
+ <style name="ConversationMessageViewStyle">
+ <item name="android:paddingStart">12dp</item>
+ <item name="android:paddingEnd">12dp</item>
+ </style>
+
+ <style name="SnackBarMessageWrapper">
+ <item name="android:layout_marginStart">@dimen/snack_bar_left_right_margin</item>
+ <item name="android:layout_marginEnd">0dp</item>
+ </style>
+
+ <style name="SubjectViewStyle">
+ <item name="android:paddingStart">@dimen/compose_message_subject_left_padding</item>
+ <item name="android:paddingEnd">@dimen/compose_message_subject_right_padding</item>
+ </style>
+
+ <style name="SmsDeliverdBadge">
+ <item name="android:layout_marginStart">4dp</item>
+ </style>
+
+ <style name="ChipDeleteIconStyle">
+ <item name="android:paddingStart">8dp</item>
+ </style>
+
+ <style name="ChipIconStyle">
+ <item name="android:layout_marginEnd">12dp</item>
+ </style>
+
+ <style name="SimSelectorItem">
+ <item name="android:textSize">@dimen/sim_selector_text_size</item>
+ <item name="android:textColor">@color/sim_selector_text_primary</item>
+ <item name="android:background">@null</item>
+ <item name="android:layout_gravity">end</item>
+ </style>
+
+ <style name="SimSelectorItemDetail">
+ <item name="android:textSize">@dimen/sim_selector_detail_text_size</item>
+ <item name="android:textColor">@color/sim_selector_text_secondary</item>
+ <item name="android:background">@null</item>
+ <item name="android:layout_gravity">end</item>
+ </style>
+
+ <style name="SimSelectorItemLinearLayoutStyle">
+ <item name="android:paddingEnd">12dp</item>
+ </style>
+
+ <style name="MessageSimIndicator">
+ <item name="android:textSize">@dimen/conversation_status_text_size</item>
+ <item name="android:layout_marginStart">4dp</item>
+ </style>
+
+ <style name="ComposeMessageViewTextCounterStyle">
+ <item name="android:textColor">@color/message_text_counter_color</item>
+ <item name="android:textSize">@dimen/message_text_counter_size</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:layout_gravity">end|center_vertical</item>
+ <item name="android:paddingEnd">@dimen/compose_message_text_box_padding_side</item>
+ </style>
+
+ <style name="MmsIndicatorStyle">
+ <item name="android:textColor">@color/mms_indicator_color</item>
+ <item name="android:textSize">@dimen/mms_indicator_size</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:background">@null</item>
+ <item name="android:paddingEnd">@dimen/compose_message_send_button_padding_right</item>
+ <item name="android:paddingTop">@dimen/compose_message_mms_indicator_padding_top</item>
+ <item name="android:layout_gravity">center_horizontal</item>
+ </style>
+
+ <style name="AttachmentGridItemViewCheckBoxStyle">
+ <item name="android:paddingEnd">4dp</item>
+ </style>
+
+ <style name="ParticipantListItem">
+ <item name="android:textSize">@dimen/participant_list_text_size</item>
+ <item name="android:textColor">@color/participant_list_text_primary</item>
+ <item name="android:background">@null</item>
+ <item name="android:layout_gravity">start|center_vertical</item>
+ </style>
+
+ <style name="WidgetConversationItemFailed">
+ <item name="android:layout_gravity">bottom|end</item>
+ </style>
+
+ <style name="WidgetConversationItemIncomingAvatarShadow">
+ <item name="android:layout_marginStart">4dp</item>
+ </style>
+
+ <style name="WidgetConversationItemOutgoingAvatarShadow">
+ <item name="android:layout_marginStart">-4dp</item>
+ </style>
+
+ <style name="WidgetConversationItemDate">
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:layout_marginStart">5dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ </style>
+
+ <style name="WidgetConversationItemWidget">
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:layout_marginStart">5dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ </style>
+
+ <style name="WidgetConversationListItemBody">
+ <item name="android:layout_toEndOf">@id/avatarFrame</item>
+ </style>
+
+ <style name="WidgetLoading">
+ <item name="android:layout_marginStart">16dp</item>
+ </style>
+
+ <style name="WidgetConversationItemBodyIncoming">
+ <item name="android:layout_toEndOf">@id/avatarFrame</item>
+ </style>
+
+ <style name="WidgetConversationItemBodyOutgoing">
+ <item name="android:layout_toStartOf">@id/avatarFrame</item>
+ </style>
+
+ <style name="WidgetConversationItemIncoming">
+ <item name="android:layout_marginEnd">5dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ <item name="android:layout_toStartOf">@id/date</item>
+ </style>
+
+ <style name="WidgetConversationItemAttachment">
+ <item name="android:layout_gravity">bottom|end</item>
+ </style>
+
+ <style name="WidgetConversationAppIcon" parent="WidgetHeaderImage">
+ <item name="android:layout_marginStart">-20dp</item>
+ </style>
+
+ <style name="WidgetConversationItemAvatarOutgoing">
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:layout_alignParentEnd">true</item>
+ </style>
+
+ <style name="WidgetConversationListItemFrom">
+ <item name="android:layout_marginEnd">5dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ </style>
+
+ <style name="WidgetConversationListItemDate">
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:layout_marginStart">5dp</item>
+ <item name="android:layout_gravity">start</item>
+ </style>
+
+</resources>
diff --git a/res/values-lo-rLA/arrays.xml b/res/values-lo-rLA/arrays.xml
new file mode 100644
index 0000000..cae3797
--- /dev/null
+++ b/res/values-lo-rLA/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ບໍ່ມີຫົວຂໍ້"</item>
+ <item msgid="272485471009191934">"​ບໍ່​ມີ​ຫົວ​ຂໍ້"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"​ແມ່ນ​ແລ້ວ"</item>
+ <item msgid="6049132459802288033">"ບໍ່​"</item>
+ <item msgid="3084376867445867895">"ຕົກລົງ"</item>
+ <item msgid="3155097332660174689">"​ຮິ​ຮິ"</item>
+ <item msgid="2611328818571146775">"​ຂອບ​ໃຈ"</item>
+ <item msgid="4881335087096496747">"​ຂ້ອຍ​​ຍອມ​ຮັບ"</item>
+ <item msgid="2422296858597420738">"​ຄັກຫຼາຍ"</item>
+ <item msgid="4805581752819452687">"​ກຳ​ລັງ​ໄປ"</item>
+ <item msgid="4746700499431366214">"​ຕົກ​ລົງ, ດຽວ​ຂ້ອຍ​ຈະ​ຕິດ​ຕໍ່​ກັບ​ໄປ"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..f433031
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"ຂໍ້ຄວາມ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"ຂໍ້ຄວາມ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"​ເລືອກ​ການ​ແປງ​ຂໍ້​ມູນ"</string>
+ <string name="action_settings" msgid="1329008122345201684">"​ການ​ຕັ້ງ​ຄ່າ"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"​ສົ່ງ​ຂໍ້​ຄວາມ"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ເພີ່ມໄຟລ໌ແນບ"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"​ຊ່ວຍ​ເຫຼືອ"</string>
+ <string name="welcome" msgid="2857560951820802321">"ຍິນດີຕ້ອນຮັບ"</string>
+ <string name="skip" msgid="7238879696319945853">"ຂ້າມ"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"ຕໍ່ໄປ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"ຕໍ່​ໄປ"</string>
+ <string name="exit" msgid="1905187380359981199">"ອອກ"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ການ​ຕັ້ງ​ຄ່າ &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ການ​ຕັ້ງ​ຄ່າ"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"ການສົ່ງຂໍ້​ຄວາມຕ້ອງ​ການ​ການ​ອະ​ນຸ​ຍາດ​ຫາ SMS, ໂທ​ລະ​ສັບ ແລະ ​ລາຍ​ຊື່."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ທ່ານ​ສາ​ມາດ​ປ່ຽນ​ແປງ​ການ​ການ​ອະ​ນຸ​ຍາດ​ໄດ້​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ &gt; ແອັບ &gt; ຂໍ້​ຄວາມ &gt; ການ​ອະ​ນຸ​ຍາດ."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ທ່ານ​ສາ​ມາດ​ປ່ຽນ​ແປງ​ການ​ອະ​ນຸ​ຍາດ​ໄດ້​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ, ແອັບ, ຂໍ້​ຄວາມ, ການ​ອະ​ນຸ​ຍາດ."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"​ຄວາມ​ຖີ່"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ລາຍ​ຊື່​ຜູ້ຕິດ​ຕໍ່​ທັງ​ໝົດ"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"ສົ່ງໄປ <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ບັນ​ທຶກ​ຮູບ ຫຼື​ວິ​ດີ​ໂອ"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ເລືອກ​ຮູບ​ສຳ​ລັບ​ອຸ​ປະ​ກອນ​ນີ້"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ບັນທຶກສຽງ"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ເລືອກ​ຮູບ"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"ມີ​ດີ​ນີ້​ຖືກ​ເລືອກ​ແລ້ວ."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"ມີ​ເດຍ​ຖືກ​ຖອນ​ເລືອກ​ແລ້ວ."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"ເລືອກແລ້ວ <xliff:g id="COUNT">%d</xliff:g> ລາຍການ"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ຮູບ <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" msgid="8142175029053370662">"ຮູບ"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ບັນທຶກສຽງ"</string>
+ <string name="action_share" msgid="2143483844803153871">"ແບ່ງ​ປັນ"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ຕອນນີ້"</string>
+ <string name="posted_now" msgid="867560789350406701">"ຕອນນີ້"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ນ​ທ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ນ​ທ</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ຊົ່ວ​ໂມງ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ຊົ່ວ​ໂມງ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ວັນ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ວັນ</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ອາ​ທິດ</item>
+ <item quantity="one">ໜຶ່ງອາ​ທິດ</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ເດືອນ</item>
+ <item quantity="one">ໜຶ່ງ​ເດືອນ</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ປີ</item>
+ <item quantity="one">ໜຶ່ງ​ປີ</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"ຂໍ້ຄວາມຄລາສ 0"</string>
+ <string name="save" msgid="5081141452059463572">"ບັນທຶກ"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ອຸ​ປະ​ກອນ​ມີ​ພື້ນ​ທີ່​​ເກັບ​ຂໍ້​ມູນ​ໜ້ອຍ. ກາ​ນສົ່ງຂໍ້​ຄວາມ​ຈະ​ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ໂດຍ​ອັດ​ຕະ​ໂນ​ມັດ ເພື່ອ​ເຮັດ​ໃຫ້​ມີ​ພື້ນ​ທີ່​ຫວ່າງ​ເພີ່ມ​ຂຶ້ນ."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"ພື້ນທີ່​ຈັດ​ເກັບ​ຂໍ້ມູນ​ກຳລັງ​ຈະ​ເຕັມ"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"ການສົ່ງຂໍ້​ຄວາມ​ອາດ​ຈະ​ບໍ່​ສົ່ງ ຫຼື ຮັບ​ຂໍ້​ຄວາມ​ຈົນ​ກ່​ວາ​ມີ​ພື້ນ​ທີ່​ເພີ່ມ​ເຕີມ​ຢູ່​ໃນ​ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"ພື້ນ​ທີ່​ເກັບ​ຂໍ້ຄວາມ​ໜ້ອຍ​ລົງ. ທ່ານ​ອາດ​ຈະ​ຕ້ອງ​ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ອອກ​ກ່ອນ."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"ຢືນຢັນເບີໂທລະສັບຂອງທ່ານ"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ຂັ້ນ​ຕອນ​ໃຊ້​ເທື່ອ​ດຽວ​ນີ້​ຈະ​ຮັບ​ປະ​ກັນ​ໃຫ້ຂໍ້​ຄວາມສົ່ງ​ຂໍ້​ຄວາມ​ກຸ່ມ​ຂອງ​ທ່ານ​ໄດ້​ຢ່າງ​ຖືກ​ຕ້ອງ."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ເບີໂທລະສັບ"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"ລຶບ​ຂໍ້ຄວາມ​ທັງໝົດ​ພ້ອມ​ທັງ​ມີເດຍ​ຕ່າງໆ"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"ລຶບ​ຂໍ້​ຄວາມ​ທີ່​ເກົ່າ​ກວ່າ <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ກວ່າ <xliff:g id="DURATION">%s</xliff:g> ໂດຍ​ອັດ​ຕະ​ໂນ​ມັດ"</string>
+ <string name="ignore" msgid="7063392681130898793">"ບໍ່ສົນໃຈ"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"ລຶບ​ຂໍ້ຄວາມ​ທັງ​ໝົດ​ພ້ອມ​ທັງ​ມີເດຍ​ຕ່າງໆຫຼື​ບໍ່?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"ລຶບ​ຂໍ້ຄວາມ​ທີ່ເກົ່າກວ່າ <xliff:g id="DURATION">%s</xliff:g> ຫຼື​ບໍ່?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"​ລຶບ​ຂໍ້​ຄວາມ​ເກົ່າ​ກວ່າ <xliff:g id="DURATION">%s</xliff:g> ​ແລະ ເປີດ​ການ​ລຶບ​ໂດຍ​ອັດ​ຕະ​ໂນ​ມັດຫຼື​ບໍ່?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ໄດ້ເວົ້າ"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"ທ່ານໄດ້​ເວົ້າ"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"ຂໍ້ຄວາມຈາກ <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"ທ່ານ​ໄດ້ສົ່ງ​ຂໍ້​ຄວາມ"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"ກຳລັງສົ່ງ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"ສົ່ງ​ບໍ່​ໄດ້. ສຳ​ຜັດ​ເພື່ອ​ລອງ​ໃໝ່​ອີກ​ຄັ້ງ."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"ສົ່ງ​ບໍ່​ໄດ້. ກຳ​ລັງ​ລອງ​ໃໝ່​ອີກ​ຄັ້ງ…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Resend or delete"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"ກະ​ລຸ​ນາ​ໂທ​ດ້ວຍ​ສຽງ​ໄປ​ຫາ​ບໍ​ລິ​ການ​ສຸກ​ເສີນ. ຂໍ້​ຄວາມ​ຂອງ​ທ່ານ​ບໍ່​ສາ​ມາດ​ນຳ​ສົ່ງ​ໄດ້​ໃນ​ເວ​ລາ​ນີ້."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"​ລົ້ມ​ເຫລວ"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ຂໍ້ຄວາມ MMS ​ໃຫມ່​ທີ່ຈະ​ດາວ​ໂຫລດ"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"ຂໍ້ຄວາມ MMS ​ໃຫມ່"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ບໍ່​ສາ​ມາດ​ດາວ​ໂຫລດ​ໄດ້"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ສຳ​ຜັດ​ເພື່ອ​ລອງ​ອີກ​ຄັ້ງ"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ແຕະເພື່ອ​ດາວ​ໂຫຼດ"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ດາວ​ໂຫລດ​ ຫລື​ລຶບ"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"​ກຳ​ລັງ​ດາວ​ໂຫລດ..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"ຂໍ້​ຄວາມ​ໝົດ​ອາຍຸ ຫຼື ໃຊ້​ບໍ່​ໄດ້​ແລ້ວ"</string>
+ <string name="mms_info" msgid="3402311750134118165">"size: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"​ບໍ່​ສາ​ມາດ​ສົ່ງ​ໄດ້. ຂໍ້​ມູນ​ຜູ່​ຮັບ​ບໍ່​ຖືກ​ຕ້ອງ."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"ບໍ່​ໄດ້​ເປີດ​ໃຊ້​ບໍລິການ​ໃນ​ເຄືອຂ່າຍ"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ບໍ່ສາມາດ​ສົ່ງໄດ້​ເນື່ອງຈາກ​ມີບັນຫາ​ກັບເຄືອຂ່າຍ"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"ຂໍ້​ຄວາມ​ໝົດ​ກຳ​ນົດ ຫຼື ໃຊ້​ບໍ່​ໄດ້​ແລ້ວ"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(ບໍ່ມີຫົວຂໍ້)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"ບໍ່ຮູ້ຜູ່ສົ່ງ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ສົ່ງຮອດແລ້ວ"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"ບໍ່​ສາມາດ​ດາວໂຫລດ​ຂໍ້​ຄວາມ <xliff:g id="SUBJECT">%1$s</xliff:g> ຈາກ <xliff:g id="FROM">%2$s</xliff:g> ໄດ້."</string>
+ <string name="low_memory" msgid="5300743415198486619">"ບໍ່​ສາມາດ​ປະຕິບັດ​ການ​ທາງ​ດ້ານ​ຖານ​ຂໍ້ມູນ​ເນື່ອງ​ຈາກ​ໜ່ວຍ​ຄວາມ​ຈຳ​ຕໍ່າ"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"ຂໍ້ຄວາມ​ຍັງ​ບໍ່ໄດ້​ຖືກສົ່ງ"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"ບາງ​ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ສົ່ງ​ຢູ່​ໃນການສົ່ງ​ຂໍ້​ຄວາມ"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ຂໍ້​ຄວາມ​ໃນ <xliff:g id="CONVERSATIONS">%d</xliff:g> ບົດ​ສົນ​ທະ​ນາ</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> ຂໍ້​ຄວາມ​ໃນ​ຫນຶ່ງ​ບົດ​ສົນ​ທະ​ນາ</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"ບໍ່ສາມາດດາວໂຫລດຂໍ້ຄວາມໄດ້"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"ບາງ​ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ຮັບ​ການ​ດາວ​ໂຫຼດ​ຢູ່​ໃນການສົ່ງ​ຂໍ້​ຄວາມ"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ຂໍ້​ຄວາມ​ໃນ <xliff:g id="CONVERSATIONS">%d</xliff:g> ບົດ​ສົນ​ທະ​ນາ</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> ຂໍ້​ຄວາມ​ໃນ​ຫນຶ່ງ​ບົດ​ສົນ​ທະ​ນາ</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"ຂໍ້​ຄວາມ​ຫາ <xliff:g id="NUMBER">%1$s</xliff:g> ບໍ່​ໄດ້​ສົ່ງ"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"ກະ​ລຸ​ນາ​ໂທ​ດ້ວຍ​ສຽງ​ໄປ​ຫາ​ບໍ​ລິ​ການ​ສຸກ​ເສີນ. ຂໍ້​ຄວາມ​ຂອງ​ທ່ານຫາ <xliff:g id="NUMBER">%1$s</xliff:g> ບໍ່​ສາ​ມາດ​ນຳ​ສົ່ງ​ໄດ້​ໃນ​ເວ​ລາ​ນີ້."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ຂໍ້ຄວາມ​ໃໝ່.</item>
+ <item quantity="one">ຂໍ້ຄວາມ​ໃໝ່.</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ເລີ່ມ"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ບໍ່​ສາມາດ​ໃຊ້​ກ້ອງໄດ້"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ບໍ່​ສາມາດ​ໃຊ້​ກ້ອງໄດ້"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"​ບໍ່​ສາ​ມາດ​ໃຊ້​ການບັນ​ທຶກວິ​ດີ​ໂອ​ໄດ້"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"​ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ​ມີ​ເດຍ​ໄດ້"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ບໍ່​ສາ​ມາດ​ຖ່າຍ​ຮູບ​ໄດ້"</string>
+ <string name="back" msgid="1477626055115561645">"ກັບຄືນ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"ຈັດເກັບແລ້ວ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"​ລຶບ"</string>
+ <string name="action_archive" msgid="5437034800324083170">"ຈັດເກັບ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ຖອດອອກຈາກແຟ້ມ"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"ປິດ​ການ​ແຈ້ງເຕືອນ"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"ເປີດ​ການ​ແຈ້ງເຕືອນ"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"ເພີ່ມ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່"</string>
+ <string name="action_download" msgid="7786338136368564146">"ດາວໂຫລດ"</string>
+ <string name="action_send" msgid="377635240181672039">"ສົ່ງ"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"​ລຶບ"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ລຶບ​ຂໍ້ຄວາມ​ນີ້ຫຼືບໍ່?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"​ການ​ກະ​ທຳ​ນີ້ຈະ​ບໍ່​ສາ​ມາດ​ກູ້​ຄືນ​ໄດ້."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"​ລຶບ"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">ລຶບການສົນທະນານີ້ບໍ?</item>
+ <item quantity="one">ລຶບການສົນທະນານີ້ບໍ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"​ລຶບ"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"​ຍົກ​ເລີກ"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"ໄປ"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"​ເລືອກ​ຫຼາຍ​ຮູບ"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"​ຢືນ​ຢັນ​ການ​ເລືອກ"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ບໍ່​ສາ​ມາດ​ອັດ​ສຽງ​ໄດ້. ລອງ​ໃໝ່​ອີກ​ຄັ້ງ."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ບໍ່​ສາ​ມາດຫຼິ້ນ​ສຽງ​ໄດ້. ລອງ​ໃໝ່​ອີກ​ຄັ້ງ."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"​ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ​ສຽງ​ໄດ້. ລອງ​ໃໝ່​ອີກ​ຄັ້ງ."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ແຕະຄ້າງ​ໄວ້"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ຮູບພາບ"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ຄລິບສຽງ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ວິດີໂອ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"ບັດ​ລາຍຊື່​ຜູ່ຕິດຕໍ່"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ດາວໂຫຼດ"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"ຕອບ​ກັບ​ຜ່ານ SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"ຕອບ​ກັບ​ຜ່ານ MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ຕອບກັບ"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ຜູ້ເຂົ້າ​ຮ່ວມ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ຜູ້ເຂົ້າ​ຮ່ວມ</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ຂ້ອຍ"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"ບລັອກ​ &amp; ຈັດ​ເກັບ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ເຂົ້າ​ແຟ້ມ​ແລ້ວ"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"ປົດ​ບ​ລັອກ​ລາຍ​ຊື່​ແລ້ວ &amp; ບໍ່​ໄດ້​ເກັບ​ໄວ້"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ຖືກຈັດເກັບແລ້ວ"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ຖືກ​ຖອດ​ອອກ​ຈາກ​ບ່ອນ​ຈັດ​ເກັບ​ແລ້ວ"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"ການ​ແຈ້ງ​ເຕືອນ​ປິດ​ແລ້ວ"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"ການ​ແຈ້ງ​ເຕືອນ​ເປີດ​ແລ້ວ"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"​ຕັ້ງ​ຮຽບ​ຮ້ອຍ​ແລ້ວ. ສຳ​ຜັດ​ເພື່ອ​ສົ່ງ​ໃໝ່​ອີກ​ຄັ້ງ."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"ຕັ້ງ​ຂໍ້​ຄວາມ​ເປັນ​ແອັບ SMS ເລີ່ມຕົ້ນ​ສຳ​ເລັດ​ແລ້ວ."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">ເອົາ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖິ້ມ</item>
+ <item quantity="one">ເອົາ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖິ້ມ</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ໄຟ​ລ໌​ຄັດ​ຕິດ​ສຽງ"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ຫຼິ້ນ​ສິ່ງຕິດຄັດທີ່ເປັນ​ສຽງ"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"ຢຸດຊົ່ວຄາວ"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"ຂໍ້​ຄວາມ​ຈາກ<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດ​ຈາກ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"ຂໍ້​ຄວາມ​ຈາກ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ສົ່ງ​ຫາ <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"ຂໍ້​ຄວາມ​ກຳ​ລັງ​ສົ່ງ​ຫາ <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດ​ຫາ <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"ຂໍ້​ຄວາມ​ຫາ <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດຈາກ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"ຂໍ້​ຄວາມ​ຈາກ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"ຂໍ້​ຄວາມ​ບໍ່​ໄດ້​ສົ່ງ​ຫາ <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"ຂໍ້​ຄວາມກຳ​ລັງ​ສົ່ງ​​ຫາ <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"ຂໍ້​ຄວາມ​ບໍ່​ສຳເລັດ​ຫາ <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Time: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"ຂໍ້​ຄວາມ​ຫາ <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. ເວ​ລາ: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ຂໍ້​ຄວາມ​ບໍ່​ສຳ​ເລັດ. ສຳ​ຜັດ​ເພື່ອ​ລອງ​ໃໝ່​ອີກ."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"ການ​ສົນ​ທະ​ນາ​ກັບ <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"​ລຶບ​ຫົວ​ຂໍ້"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ບັນ​ທຶກ​ວິດີໂອ"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ບັນ​ທຶກ​ຮູບ"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ຖ່າຍຮູບ"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ເລີ່ມ​ການ​ບັນທຶກ​ວິດີໂອ"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"ສະລັບ​ໄປ​ໃຊ້​ກ້ອງ​ແບບ​ເຕັມຈໍ"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"​ສະ​ລັບ​ລະ​ຫວ່າງ​ກ້ອງ​ໜ້າ ແລະ ຫຼັງ"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"​ຢຸດ​ການ​ບັນ​ທຶກ ແລະ ແນບ​ວິ​ດີ​ໂອ"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"​ຢຸດ​ການ​ບັນ​ທຶກວິ​ດີ​ໂອ"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"ຮູບຖ່າຍານສົ່ງ​ຂໍ້​ຄວາມ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ຮູບ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ຮູບ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ວິ​ດີ​ໂອ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ວິ​ດີ​ໂອ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່​ອາ​ລະ​ບັ້ມ \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶ​ກ​ໄປ​ໃສ່ອາ​ລະບັ້ມ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶ​ກ​ໄປ​ໃສ່ອາ​ລະ​ບັ້ມ \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່ \"ດາວ​ໂຫຼດ\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ໄປ​ໃສ່ \"ດາວ​ໂຫຼດ\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ແລ້ວ</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ຖືກ​ບັນ​ທຶກ​ແລ້ວ</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ <xliff:g id="QUANTITY_1">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ໄດ້</item>
+ <item quantity="one">ບໍ່​ສາ​ມາດ​ບັນ​ທຶກ <xliff:g id="QUANTITY_0">%d</xliff:g> ໄຟ​ລ໌​ຄັດ​ຕິດ​ໄດ້</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"ບັນ​ທຶກ​ໄຟລ໌​ແນບ MMS ​ແລ້ວ"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ການ​ຕັ້ງ​ຄ່າ"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"ຈັດເກັບແລ້ວ"</string>
+ <string name="action_close" msgid="1840519376200478419">"ປິດ"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"​ຂັ້ນ​ສູງ"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ດີບັ໊ກ"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"ການແຈ້ງເຕືອນ"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ສຽງ"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"ປິດສຽງ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"ສັ່ນເຕືອນ"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ບລັອກ​ແລ້ວ"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"​ລາຍ​ງານ​ຜົນ​ການສົ່ງ SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"​ຂໍ​ລາຍ​ງານຜົນ​ການ​ສົ່ງ​ສຳ​ລັບ​ແຕ່​ລະ​ SMS ທີ່​ທ່ານ​ສົ່ງ"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"ດຶງ​ຂໍ້ມູນ​ອັດຕະໂນມັດ"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"ດຶງຂໍ້ຄວາມ MMS ອັດຕະໂນມັດ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ດຶງ​ຂໍ້ຄວາມ​ອັດຕະໂນມັດ​ເມື່ອ​ໂຣມມິງ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ກູ້​ຄືນ MMS ອັດ​ຕະ​ໂນ​ມັດ​ເມື່ອ​ໂຣມ​ມິງ"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"ການສົ່ງ​ຂໍ້ຄວາມກຸ່ມ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ໃຊ້ MMS ເພື່ອສົ່ງ​ຂໍ້ຄວາມ​ໃດນຶ່ງ​ເມື່ອມີຜູ່ຮັບ​ຫຼາຍຄົນ"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ແອັບຯ SMS ມາດ​ຕະ​ຖານ"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ແອັບຯ SMS ມາດ​ຕະ​ຖານ"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"​ເບີ​ໂທ​ລະ​ສັບ​ຂອງ​ທ່ານ"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"​ບໍ່​ຮູ້​ຈັກ"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"ສຽງ​ຂໍ້​ຄວາມ​ຂາ​ອອກ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"ດຶງ SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ດຶງ​ຂໍ້​ມູນ SMS ​ດິບທີ່​ໄດ້​ຮັບ​ໃນ​ຮູບ​ແບບ​ໄຟ​ລ໌​ເກັບ​ຂໍ້​ມູນ​ພາຍນອກ"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"ດຶງ MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ດຶງ​ຂໍ້​ມູນ MMS ​ດິບທີ່​ໄດ້​ຮັບ​ໃນ​ຮູບ​ແບບ​ໄຟ​ລ໌​ເກັບ​ຂໍ້​ມູນ​ພາຍນອກ"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ແຈ້ງ​ເຕືອນ​ລະບົບ​ໄຮ້ສາຍ"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"​ໂຕ​ເລືອກ​ຂໍ້​ຄວາມ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"​ສຳ​ເນົາ​ຂໍ້​ຄວາມ"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"​ເບິ່ງ​ລາຍ​ລະ​ອຽດ"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"​ລຶບ"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ສົ່ງຕໍ່"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"​ລາຍ​ລະ​ອຽດ​ຂໍ້​ຄວາມ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ປະ​ເພດ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"​ຂໍ້​ຄວາມ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"ຂໍ້​ຄວາມ​ມັນ​ຕິ​ມີ​ເດຍ"</string>
+ <string name="from_label" msgid="1947831848146564875">"ຈາກ: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"ເຖິງ: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ສົ່ງ​ເມື່ອ: "</string>
+ <string name="received_label" msgid="4442494712757995203">"ໄດ້​ຮັບເມື່ອ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"ຫົວ​ຂໍ້: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"​ຂະ​ໜາດ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ຄວາມ​ສຳ​ຄັນ: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"​ຊິມ: "</string>
+ <string name="priority_high" msgid="728836357310908368">"​ສູງ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"​ປານ​ກາງ"</string>
+ <string name="priority_low" msgid="7398724779026801851">"​ຕ່ຳ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"​ຊິມ <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"​​ເຊື່ອງ​ທີ່​ຢູ່​ຜູ່​ສົ່ງ"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"​ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ໄດ້​ໃນ​ຂະ​ນະ​ທີ່​ໂຫຼດ​ໄຟລ໌​ແນບ."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ບໍ່​ສາ​ມາດ​ໂຫລດ​ໄຟລ໌​ແນບ​ໄດ້. ​ລອງ​ໃໝ່​ອີກ​ຄັ້ງ."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ເຄືອ​ຂ່າຍ​ບໍ່​ພ້ອມ. ລອງ​ໃໝ່​ອີກ."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ລຶບ​ຂໍ້​ຄວາມ"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ສະ​ລັບ​ລະ​ຫວ່າງ​ການ​ພິມ​ໂຕ​ອັກ​ສອນ ແລະ​ໂຕ​ເລກ"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"ເພີ່ມ​ຜູ່​ເຂົ້າ​ຮ່ວມ​ອື່ນ"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"ຢືນ​ຢັນ​ຜູ່​ເຂົ້າ​ຮ່ວມ"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"​ເລີ່ມ​ບົດ​ສົນ​ທະ​ນາ​ໃໝ່"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"​ເລືອກ​ລາຍ​ການ​ນີ້"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ຫຼິ້ນວິດີໂອ"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ &amp; ໂຕ​ເລືອກ"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ດີບັ໊ກ"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ &amp; ໂຕ​ເລືອກ"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"ທົ່ວໄປ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ຄ​ົນ​ໃນ​ບົດ​ສົນ​ທະ​ນາ​ນີ້"</string>
+ <string name="action_call" msgid="6596167921517350362">"​ໂທ​ອອກ"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"ສົ່ງຂໍ້ຄວາມ"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"​ສົ່ງ​ຂໍ້​ຄວາມ&lt;br/&gt;&lt;small&gt;​ຈາກ <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">​ສົ່ງ​ຮູບ​ພາບ</item>
+ <item quantity="one">​ສົ່ງ​ຮູບ​ພາບ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">​ສົ່ງ​ສຽງ</item>
+ <item quantity="one">​ສົ່ງ​ສຽງ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">ສົ່ງ​ວິດີໂອ</item>
+ <item quantity="one">ສົ່ງ​ວິດີໂອ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">​ສົ່ງ​ບັດ​ລາຍ​ຊື່​ຜູ້ຕິດ​ຕໍ່</item>
+ <item quantity="one">​ສົ່ງ​ບັດ​ລາຍ​ຊື່​ຜູ້ຕິດ​ຕໍ່</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">​ສົ່ງ​ໄຟລ໌​ຄັດ​ຕິດ</item>
+ <item quantity="one">​ສົ່ງ​ໄຟລ໌​ຄັດ​ຕິດ</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ພ້ອມ​ສົ່ງ​ໄດ້​ແລ້ວ</item>
+ <item quantity="one">ໜຶ່ງ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ພ້ອມ​ສົ່ງ​ໄດ້​ແລ້ວ</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"​ສົ່ງ​ຄຳ​ຕິ​ຊົມ"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"ເບິ່ງໃນ Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"​​ຂໍ​້​ມູນ​ເວີ​ຊັນ"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"ເວີ​ຊັນ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"​ລິ​ຂະ​ສິດ​ໂອ​ເພນ​ຊອດ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"ການແຈ້ງເຕືອນ"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ຮອດ​ຂີດ​ຈຳ​ກັດ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ແລ້ວ"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ໂຫຼດ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ບໍ່​ສຳ​ເລັດ."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"ເພີ່ມ​ໃສ່​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ບໍ່?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"ເພີ່ມ​​ໃສ່​ລາ​ຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"ຫົວຂໍ້"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"ຫົວຂໍ້: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"​ກຳ​ລັງ​ໂຫລດ​ບັດ​ລາຍ​ຊື່​ຜ​ູ່​ຕິດ​ຕໍ່"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"​ບໍ່​ສາ​ມາດ​ໂຫລດ​ບັດ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ໄດ້"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"​ເບິ່ງ​ບັດ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ລາຍ​ຊື່</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ລາຍ​ຊື່</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"ບັດ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"ວັນເດືອນປີເກີດ"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ບັນທຶກ"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"​ສົ່ງ​ຕໍ່​ຂໍ້​ຄວາມ"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ຕອບຄືນ"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS ​ຖືກ​ປິດ​ແລ້ວ"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"ເພື່ອ​ສົ່ງ, ຕັ້ງ​ການສົ່ງຂໍ້​ຄວາມ​ເປັນ​ແອັບ SMS ເລີ່ມຕົ້ນ"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"ຕັ້ງການສົ່ງ​ຂໍ້​ຄວາມ​ເປັນ​ແອັບ SMS ເລີ່ມຕົ້ນ"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"ປ່ຽນແປງ"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"​ເພື່ອ​ຮັບ​ຂໍ້​ຄວາມ, ຕັ້ງການສົ່ງຂໍ້​ຄວາມໃຫ້​ເປັນ​ແອັບ SMS ​ເລີ່ມ​ຕົ້ນ​ຂອງ​ທ່ານ"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"ບໍ່​ມີ SIM ທີ່​ມັກ​ຖືກ​ເລືອກ​ໄວ້​ ເພື່ອ​ສົ່ງ​ຂໍ້​ຄວາມ SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"​​ເຈົ້າ​ຂອງ​ອຸ​ປະ​ກອນບໍ່​ອະ​ນຸ​ຍາດ​ໃຫ້​ໃຊ້​ແອັບຯ​ນີ້."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ຕົກລົງ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"​ມີ​ຜູ່​ເຂົ້າ​ຮ່ວມ​ໃນ​ການ​ສົນ​ທະ​ນາຫຼາຍ​ເກີນ​ໄປ"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">ລາຍ​ຊື່​ຕິດ​ຕໍ່​ບໍ່​ຖືກ​ຕ້ອງ</item>
+ <item quantity="one">ລາຍ​ຊື່​ຕິດ​ຕໍ່​ບໍ່​ຖືກ​ຕ້ອງ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"​ບໍ່​ສາ​ມາດ​ໂຫລດ​ຮູບ​ຈາກ​ກ້ອງ​ໄດ້"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"ທ່ານ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ສະບັບຮ່າງ"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"ໃນເວລາ​ທີ່ທານ​ເລີ່ມການ​ສົນທະນາ​ໃຫມ່, ທ່ານຈະເຫັນ​ມັນສະແດງ​ຢູ່ບ່ອນ​ນີ້"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"​ບົດ​ສົນ​ທະ​ນາ​ທີ່​ຖືກ​ຈັດ​ເກັບ​ຈະ​ສະ​ແດງ​ຢູ່​ນີ້"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"ກຳລັງ​ໂຫລດ​ບົດ​ສົນທະນາ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ຮູບພາບ"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ຄລິບສຽງ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ວິດີໂອ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"ບັດ​ລາຍຊື່​ຕິດຕໍ່"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"ຍົກເລີກ"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ລອງໃໝ່"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"​ປ້ອນ​ຊື່​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່ ຫຼື ເບີ​ໂທ​ລະ​ສັບ​ເພື່ອ​ເລີ່ມ​ຂໍ້​ຄວາມ​ໃໝ່"</string>
+ <string name="action_block" msgid="9032076625645190136">"ບລັອກ"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"ບລັອກ <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"​ປົດ​ບລັອກ <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"ບລັອກ <xliff:g id="DESTINATION">%s</xliff:g> ບໍ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"​ທ່ານ​ຈະ​ຍັງ​ສືບ​ຕໍ່​ໄດ້​ຮັບ​ຂໍ້​ຄວາມ​ຈາກ​ເບີ​ນີ້ ແຕ່​ຈະ​ບໍ່​ຖືກ​ແຈ້ງ​ເຕືອນ​ອີກ​ຕໍ່​ໄປ. ບົດ​ສົນ​ທະ​ນາ​ນີ້​ຈະ​ຖືກ​ຈັດ​ເກັບ."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ທີ່​ຖືກບລັອກ"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"​ປົດ​ບລັອກ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"ບລັອກ​ລາຍ​ຊື່​ຜູ່​ຕິດ​ຕໍ່​ແລ້ວ"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"​ເລືອກ​ຮູບ​ຈາກ​ຫ້ອງ​ສະ​ໝຸດ​ເອ​ກະ​ສານ"</string>
+ <string name="sending_message" msgid="6363584950085384929">"ກຳລັງສົ່ງຂໍ້ຄວາມ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"ສົ່ງຂໍ້ຄວາມແລ້ວ"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"ຂໍ້​ມູນ​ເຄືອ​ຂ່າຍ​ໂທ​ລະ​ສັບ​ຖືກ​ປິດ​ໄວ້. ກະ​ລຸ​ນາ​ກວດ​ສອບ​ການ​ຕັ້ງ​ຄ່າ​ຂອງ​ທ່ານ."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"​ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ໃນ​ໂໝດ​ໃນ​ຍົນ​ໄດ້"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"ບໍ່ສາມາດສົ່ງຂໍ້ຄວາມໄປໄດ້"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"ດາວ​ໂຫຼດ​ຂໍ້​ຄວາມ​ແລ້ວ"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"ຂໍ້​ມູນເຊວ​ລູ​ລາ​ຖືກ​ປິດ​ໄວ້. ສອບ​ການ​ຕັ້ງ​ຄ່າ​ຂອງ​ທ່ານ."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ບໍ່​ສາ​ມາດ​ກາວ​ໂຫຼດ​ຂໍ້​ຄວາມ​ຢູ່​ໃນ​ໂໝດ​ເຮືອ​ບິນ​ໄດ້"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"ບໍ່​ສາ​ມາດ​ດາວ​ໂຫຼດ​ໄດ້"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"​ສູນ"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"​ນຶ່ງ"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"​ສອງ"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"​ສາມ"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"​ສີ່"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"​ຫ້າ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"​ຫົກ"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"​ເຈັດ"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ແປດ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"​ເກົ້າ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ດ້ວຍ <xliff:g id="CARRIERNAME">%1$s</xliff:g> ໄດ້, ຂໍ້​ຜິດ​ພາດ <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"ບໍ່​ສາ​ມາດ​ສົ່ງ​ຂໍ້​ຄວາມ​ດ້ວຍບໍ​ລິ​ສັດ​ເຄືອ​ຂ່າຍ​ມື​ຖື​ບໍ່​ຮູ້​ຈັກ​ໄດ້, ຂໍ້​ຜິດ​ພາດ <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ບໍ​ລິ​ການ​ຍັງ​ບໍ່​ໄດ້​ເປີດ​ນຳ​ໃຊ້​ໃນ​ເຄືອ​ຂ່າຍ​ເທື່ອ"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ທີ່​ຢູ່​ປາຍ​ທາງບໍ່​ຖືກ​ຕ້ອງ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ຂໍ້​ຄວາມ​ບໍ່​ຖືກ​ຕ້ອງ"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ​ບໍ່​ຮອງ​ຮັບ​ເນື້ອ​ຫາ"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"ຂໍ້​ຄວາມ​ຍັງ​ບໍ່​ໄດ້​ສົ່ງ: ບໍ່​ຮອງ​ຮັບ​ຂໍ້​ຄວາມ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"ຂໍ້ຄວາມ​ຍັງ​ບໍ່ໄດ້​ຖືກສົ່ງ: ໃຫຍ່​ເກີນ​ໄປ"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"ຂໍ້ຄວາມໃໝ່"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"​ເບິ່ງ"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"ຮູບພາບ"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"ບໍ່​ສາ​ມາດ​ຊອກ​ຫາ​ແອັບ​ພລິ​ເຄ​ຊັນ​ທີ່​ເໝາະ​ສົມ​ໄດ້"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"ລຶບ​ຜູ່​ຮັບ"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"ຂໍ້ຄວາມໃໝ່"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"​ຍົກ​ເລີກ"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ແກ້ໄຂ​ຈຸດ​ການ​ເຊື່ອມຕໍ່"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"ຍັງບໍ່ໄດ້ຕັ້ງ"</string>
+ <string name="apn_name" msgid="1572691851070894985">"ຊື່"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"ພອດ MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"ປະເພດ APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"ລຶບ APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN ໃໝ່"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"ບັນທຶກ"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ຍົກເລີກ"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"ຊ່ອງຂໍ້ມູນ​ຊື່​ບໍ່ສາມາດ​ປ່ອຍ​ຫວ່າງໄດ້."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ບໍ່​ສາມາດ​ປ່ອຍ​ໃຫ້​ຫວ່າງ​ໄດ້."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"ຊ່ອງຂໍ້ມູນ MMC ຕ້ອງມີ 3 ໂຕເລກ."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"ຊ່ອງຂໍ້ມູນ MNC ຕ້ອງມີ 2 ຫຼື 3 ໂຕເລກ."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ກຳລັງ​ກັບ​ໄປ​ໃຊ້ການຕັ້ງຄ່າ APN ເລີ່ມຕົ້ນ."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"ກັບຄືນໄປໃຊ້ຄ່າເລີ່ມຕົ້ນ"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"​ສຳ​ເລັດ​ການ​ກັບ​ໄປໃຊ້ການ​ຕັ້ງ​ຄ່າ APN ເລີ່ມຕົ້ນແລ້ວ."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ບໍ່ໄດ້ຕັ້ງຊື່"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ຊື່ຂອງ​ຈຸດການ​ເຂົ້າເຖິງ (APN)"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN ໃໝ່"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ຜູ່​ໃຊ້​ນີ້ບໍ່​ສາ​ມາດ​ຕັ້ງ​ຄ່າ​ຊື່​ຈຸດ​ການ​ເຂົ້າ​ເຖິງ​ໄດ້"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"​ສຳ​ເນົາ​ໄວ້​ໃນຄລິບບອດຫຼື​ບໍ່?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"ສຳເນົາ"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ເຖິງ <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"ທົ່ວໄປ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"​ຂັ້ນ​ສູງ"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"ການຕັ້ງຄ່າ​ທົ່ວໄປ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"ການຕັ້ງຄ່າ​ຂັ້ນສູງ"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"​ສົ່ງ​ຂໍ້​ຄວາມ SMS ​ສະ​ເພາະ​ບຸກ​ຄົນໄປ​ຫາ​ທຸກໆ​ຜູ່​ຮັບ. ​ສະ​ເພາະ​ທ່ານ​ເທົ່າ​ນັ້ນ​ທີ່​ຈະ​ໄດ້​ຮັບ​ແຕ່​ລະ​ການ​ຕອບ​ກັບ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"​ສົ່ງ MMS ​ສະ​ບັບ​ນຶ່ງ​ໄປ​ຫາ​ທຸກໆ​ຜູ່​ຮັບ"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"​ເບີ​ໂທບໍ່​ຮູ້​ຈັກ"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"ຂໍ້ຄວາມ​ໃໝ່"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"ຂໍ້ຄວາມ​ໃໝ່."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"ຕົວ​ເລືອກ SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ຖືກ​ເລືອກ​ແລ້ວ, ຕົວ​ເລືອກ SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"ແກ້ໄຂຫົວຂໍ້"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"ເລືອກ SIM ຫຼື​ແກ້​ໄຂ​ຫົວ​ຂໍ້"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ສຳ​ພັດ​ຄ້າງ​ໄວ້ ເພື່ອ​ບັນ​ທຶກ​ສຽງ"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"​ເລີ່ມ​ບົດ​ສົນ​ທະ​ນາ​ໃໝ່"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"ຂໍ້ຄວາມ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"ລາຍ​ການການສົ່ງ​ຂໍ້​ຄວາມ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"ຂໍ້ຄວາມ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"ຂໍ້ຄວາມໃໝ່"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"ລາຍການການສົນທະນາ"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"ກຳລັງ​ໂຫລດ​ບົດ​ສົນທະນາ..."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"ກຳລັງໂຫລດຂໍ້ຄວາມ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"ເບິ່ງບົດສົນທະນາເພີ່ມເຕີມ"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"ເບິ່ງ​ຂໍ້​ຄວາມ​ເພີ່ມ​ເຕີມ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"ການ​ສົນທະນາ​ຖືກລຶບແລ້ວ"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"ການ​ສົນ​ທະ​ນາ​ຖືກ​ລຶບ​ແລ້ວ. ສຳ​ຜັດ​ເພື່ອ​ສະ​ແດງ, ການ​ສົນ​ທະ​ນາ​ຂໍ້​ຄວາມ​ທີ່​ແຕກ​ຕ່າງ"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ບລັອກ​ແລ້ວ"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"ປົດ​ບ​ລັອກ​ແລ້ວ"</string>
+ <string name="db_full" msgid="8459265782521418031">"ພື້ນ​ທີ່​ຈັດ​ເກັບ​ຕ່ຳ. ບາງ​ຂໍ້​ມູນ​ອາດ​ຈະ​ເສຍ​ໄປ."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ເລືອກ​ໄຟ​ລ໌​ຄັດ​ຕິດ"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"​ຢືນ​ຢັນ​ການ​ເລືອກ"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ເລືອກ​ແລ້ວ"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"ກະ​ລຸ​ນາ​ເອົາ​ໜຶ່ງ ຫຼື​ຫຼາຍ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ອອກ​ໄປ ແລະ​ລອງ​ໃໝ່​ອີກ."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"ທ່ານ​ສາ​ມາດ​ລອງ​ສົ່ງ​ຂໍ້​ຄວາມ​ຂອງ​ທ່ານ​ໄດ້, ແຕ່​ມັນ​ອາດ​ຈະ​ບໍ່​ຖືກ​ສົ່ງ​ໄປ ນອກ​ຈາກວ່າ​ທ່ານ​ເອົາ​ໜຶ່ງ ຫຼື​ຫຼາຍ​ໄຟ​ລ໌​ຄັດ​ຕິດ."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"ທ່ານ​ສາ​ມາດ​ສົ່ງ​ໄດ້​ແຕ່​ໜຶ່ງວິ​ດີ​ໂອ​ຕໍ່​ຂໍ້​ຄວາມ​ເທົ່າ​ນັ້ນ. ກະ​ລຸ​ນາ​ເອົາ​ວິ​ດີ​ໂອ​ເພີ່ມ​ເຕີມ​ອອກ ແລະ​ລອງ​ໃໝ່​ອີກ."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"ການສົ່ງຂໍ້​ຄວາມ​ໂຫຼດ​ໄຟ​ລ໌​ຄັດ​ຕິດ​ບໍ່ສຳເລັດ."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ແນວ​ໃດ​ກໍ່​ສົ່ງ​"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"ບໍ່​ສາ​ມາດ​ເລີ່ມ​ຕົ້ນ​ການ​ສົນ​ທະ​ນາ​ໄດ້"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ເລືອກ​ແລ້ວ"</string>
+</resources>
diff --git a/res/values-lt/arrays.xml b/res/values-lt/arrays.xml
new file mode 100644
index 0000000..3c1c116
--- /dev/null
+++ b/res/values-lt/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"nėra temos"</item>
+ <item msgid="272485471009191934">"nėra temos"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Taip"</item>
+ <item msgid="6049132459802288033">"Ne"</item>
+ <item msgid="3084376867445867895">"Gerai"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Ačiū"</item>
+ <item msgid="4881335087096496747">"Sutinku"</item>
+ <item msgid="2422296858597420738">"Puiku"</item>
+ <item msgid="4805581752819452687">"Pakeliui"</item>
+ <item msgid="4746700499431366214">"Gerai, informuosiu vėliau"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..167fe9f
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Susirašinėjimas pranešimais"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Susirašinėjimas pranešimais"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Pasirinkti pokalbį"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Nustatymai"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Siųsti pranešimą"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Pridėti priedą"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Pagalba"</string>
+ <string name="welcome" msgid="2857560951820802321">"Sveiki!"</string>
+ <string name="skip" msgid="7238879696319945853">"Praleisti"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Kitas &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Kitas"</string>
+ <string name="exit" msgid="1905187380359981199">"Išeiti"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Nustatymai &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Nustatymai"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Susirašinėjimo programai reikia leidimų pasiekti SMS pranešimus, telefoną ir kontaktus."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Leidimus galite pakeisti skiltyje „Nustatymai &gt; Programos &gt; Susirašinėjimo programa &gt; Leidimai“."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Leidimus galite pakeisti skiltyje „Nustatymai, Programos, Susirašinėjimo programa, Leidimai“."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Dažniausiai naudojami"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Visi kontaktai"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Siųsti: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Fotografuokite arba filmuokite"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Pasirinkite vaizdus iš šio įrenginio"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Įrašyti garsą"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Pasirinkti nuotrauką"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Medija pasirinkta."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Medija nepasirinkta."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Pasirinkta: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"vaizdas: <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" msgid="8142175029053370662">"vaizdas"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Įrašyti garsą"</string>
+ <string name="action_share" msgid="2143483844803153871">"Bendrinimas"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Ką tik"</string>
+ <string name="posted_now" msgid="867560789350406701">"Dabar"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> val.</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> val.</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> val.</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> val.</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> diena</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dienos</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> dienos</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dienų</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> savaitė</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> savaitės</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> savaitės</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> savaičių</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mėnesiai</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> mėnesiai</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> mėnesio</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mėnesių</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> metai</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> metai</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> metų</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> metų</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0 klasės pranešimas"</string>
+ <string name="save" msgid="5081141452059463572">"Išsaugoti"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Mažai vietos įrenginyje. Susirašinėjimo programa automatiškai ištrins senesnius pranešimus, kad atlaisvintų vietos."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Mažėja laisvos saugyklos vietos saugykloje"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Galbūt Susirašinėjimo programa negalės siųsti ar gauti pranešimų, kol įrenginyje nebus daugiau laisvos vietos."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Mažai laisvos SMS saugyklos vietos. Gali reikėti ištrinti pranešimus."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Patvirtinkite telefono numerį"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Atlikę šį vienkartinį veiksmą užtikrinsite, kad Susirašinėjimo programa tinkamai pateiks grupės pranešimus."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefono numeris"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Ištrinti visus pranešimus su medija"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Ištrinti senesnius nei <xliff:g id="DURATION">%s</xliff:g> pranešimus"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Automatiškai ištrinti pranešimus, kurie senesni nei <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Nepaisyti"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Ištrinti visus pranešimus su medija?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Ištrinti pranešimus, kurie senesni nei <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Ištrinti pranešimus, kurie senesni nei <xliff:g id="DURATION">%s</xliff:g>, ir įjungti automatinį ištrynimą?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> sakė"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Jūs sakėte"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Pranešimas nuo <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Išsiuntėte pranešimą"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Siunčiama…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Neišsiųsta. Palieskite, kad bandytumėte dar kartą."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Neišsiųsta. Bandoma dar kartą…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Persiųsti arba ištrinti"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Atlikite balso skambutį pagalbos tarnyboms. Šiuo metu negalime pateikti teksto pranešimo."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Nepavyko"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Naujas MMS pranešimas, kurį galima atsisiųsti"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Naujas MMS pranešimas"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nepavyko atsisiųsti"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Palieskite ir bandykite dar kartą"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Palieskite, kad atsisiųstumėte"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Atsisiųskite arba ištrinkite"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Atsisiunčiama..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Pranešimo galiojimo laikas pasibaigė arba jis negalimas"</string>
+ <string name="mms_info" msgid="3402311750134118165">"dydis: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, galiojimo pabaiga: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nepavyko išsiųsti. Netinkamas gavėjas."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Paslauga nesuaktyvinta tinkle"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Nepavyko išsiųsti dėl tinklo problemos"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Pranešimo galiojimo laikas pasibaigė arba jis negalimas"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Nėra temos)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Nežinomas siuntėjas"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Pristatyta"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nepavyko atsisiųsti pranešimo „<xliff:g id="SUBJECT">%1$s</xliff:g>“ nuo <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Nepavyko atlikti duomenų bazės operacijos dėl atminties trūkumo"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Pranešimas neišsiųstas"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Kai kurie pranešimai neišsiųsti naudojant Susirašinėjimo programą"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbis)</item>
+ <item quantity="few">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbiai)</item>
+ <item quantity="many">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbio)</item>
+ <item quantity="other">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbių)</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Pranešimas neatsisiųstas"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Kai kurie pranešimai neatsisiųsti naudojant Susirašinėjimo programą"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbis)</item>
+ <item quantity="few">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbiai)</item>
+ <item quantity="many">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbio)</item>
+ <item quantity="other">Pranešimų: <xliff:g id="MESSAGES_1">%d</xliff:g> (<xliff:g id="CONVERSATIONS">%d</xliff:g> pokalbių)</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Pranešimas numeriu <xliff:g id="NUMBER">%1$s</xliff:g> nebuvo išsiųstas"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Atlikite balso skambutį pagalbos tarnyboms. Šiuo metu negalime pateikti teksto pranešimo numeriu <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> naujas pranešimas</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> nauji pranešimai</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> naujo pranešimo</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> naujų pranešimų</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Pradėti"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Fotoaparatas negalimas"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Fotoaparatas negalimas"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Vaizdo įrašo fiksavimas negalimas"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Nepavyko išsaugoti medijos"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Negalima fotografuoti"</string>
+ <string name="back" msgid="1477626055115561645">"Atgal"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Suarchyvuota"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Ištrinti"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archyvuoti"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Išarchyvuoti"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Išjungti pranešimus"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Įjungti pranešimus"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Pridėti kontaktą"</string>
+ <string name="action_download" msgid="7786338136368564146">"Atsisiųsti"</string>
+ <string name="action_send" msgid="377635240181672039">"Siųsti"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Ištrinti"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Ištrinti šį pranešimą?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Šio veiksmo anuliuoti negalima."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Ištrinti"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Ištrinti šiuos pokalbius?</item>
+ <item quantity="few">Ištrinti šiuos pokalbius?</item>
+ <item quantity="many">Ištrinti šiuos pokalbius?</item>
+ <item quantity="other">Ištrinti šiuos pokalbius?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Ištrinti"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Atšaukti"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Kam"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Pasirinkti kelis vaizdus"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Patvirtinti pasirinkimą"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"Dar <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Nepavyko įrašyti garso. Bandykite dar kartą."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Nepavyko paleisti garso įrašo. Bandykite dar kartą."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Nepavyko išsaugoti garso įrašo. Bandykite dar kartą."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Palieskite ir palaikykite"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Nuotrauka"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Garso įrašo klipas"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vaizdo įrašas"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontakto kortelė"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Atsisiųsti"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Atsakyti SMS žinute"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Atsakyti MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Atsakyti"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dalyvis</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dalyviai</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> dalyvio</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dalyvių</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Aš"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontaktas užblokuotas ir suarchyvuotas"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontaktas atblokuotas ir išarchyvuotas"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Suarchyvuota: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Išarchyvuota: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Pranešimai išjungti"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Pranešimai įjungti"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Viskas nustatyta. Dar kartą palieskite „Siųsti“."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Susirašinėjimo programa sėkmingai nustatyta kaip numatytoji SMS programa."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Išmesti priedus</item>
+ <item quantity="few">Išmesti priedus</item>
+ <item quantity="many">Išmesti priedus</item>
+ <item quantity="other">Išmesti priedus</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Garso priedas"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Leisti garso priedą"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pristabdyti"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Pranešimas nuo <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Nepavyko atsiųsti pranešimo nuo <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Pranešimas nuo <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Neišsiųstas pranešimas grupei „<xliff:g id="CONTACT">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Siunčiamas pranešimas grupei „<xliff:g id="CONTACT">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Nepavyko išsiųsti pranešimo grupei „<xliff:g id="CONTACT">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Pranešimas grupei „<xliff:g id="CONTACT">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Nepavyko atsiųsti pranešimo nuo <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Pranešimas nuo <xliff:g id="SENDER">%s</xliff:g>: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Neišsiųstas pranešimas grupei „<xliff:g id="GROUP">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Siunčiamas pranešimas grupei „<xliff:g id="GROUP">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Nepavyko išsiųsti pranešimo grupei „<xliff:g id="GROUP">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Pranešimas grupei „<xliff:g id="GROUP">%s</xliff:g>“: „<xliff:g id="MESSAGE">%s</xliff:g>“. Laikas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Įvyko pranešimo klaida. Palieskite ir bandykite dar kartą."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Pokalbis su <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Pašalinti temą"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Filmuoti"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Fiksuoti nejudantį vaizdą"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Fotografuoti"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Pradėti filmuoti"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Perjungti į viso ekrano režimu veikiantį fotoaparatą"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Perjungti priekinį ir užpakalinį fotoaparatus"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Sustabdyti įrašymą ir pridėti vaizdo įrašą"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Sustabdyti vaizdo įrašymą"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Susirašinėjimo programos nuotraukos"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> nuotrauka išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> nuotraukos išsaugotos albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> nuotraukos išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> nuotraukų išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> vaizdo įrašas išsaugotas albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> vaizdo įrašai išsaugoti albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> vaizdo įrašo išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vaizdo įrašų išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> priedas išsaugotas albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> priedai išsaugoti albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> priedo išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> priedų išsaugota albume „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> priedas išsaugotas aplanke „Atsisiuntimai“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> priedai išsaugoti aplanke „Atsisiuntimai“</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> priedo išsaugota aplanke „Atsisiuntimai“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> priedų išsaugota aplanke „Atsisiuntimai“</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one">Išsaugotas <xliff:g id="QUANTITY_1">%d</xliff:g> priedas</item>
+ <item quantity="few">Išsaugoti <xliff:g id="QUANTITY_1">%d</xliff:g> priedai</item>
+ <item quantity="many">Išsaugota <xliff:g id="QUANTITY_1">%d</xliff:g> priedo</item>
+ <item quantity="other">Išsaugota <xliff:g id="QUANTITY_1">%d</xliff:g> priedų</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Nepavyko išsaugoti <xliff:g id="QUANTITY_1">%d</xliff:g> priedo</item>
+ <item quantity="few">Nepavyko išsaugoti <xliff:g id="QUANTITY_1">%d</xliff:g> priedų</item>
+ <item quantity="many">Nepavyko išsaugoti <xliff:g id="QUANTITY_1">%d</xliff:g> priedo</item>
+ <item quantity="other">Nepavyko išsaugoti <xliff:g id="QUANTITY_1">%d</xliff:g> priedų</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Išsaugotas MMS priedas"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Nustatymai"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Suarchyvuota"</string>
+ <string name="action_close" msgid="1840519376200478419">"Uždaryti"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Išplėstiniai"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Derinti"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Pranešimai"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Garsas"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Tylus"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibravimas"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Užblokuota"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS pristatymo ataskaitos"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Pateikti kiekvieno išsiųsto SMS pristatymo ataskaitos užklausą"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatiškai nuskaityti"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automatiškai nuskaityti MMS pranešimus"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Aut. nuskait. naud. tarptinklinį ryšį"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatiškai nuskaityti MMS, kai veikia tarptinklinis ryšys"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Grupinis susirašinėjimas"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Naudokite MMS, kad siųstumėte vieną pranešimą, kai yra keli gavėjai"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Numatytoji SMS programa"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Numatytoji SMS programa"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Jūsų telefono numeris"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Nežinomas"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Siunčiamų pranešimų garsai"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Pateikti SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Pateikti gautų SMS neapdorotus duomenis išorinės atminties faile"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Pateikti MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Pateikti gautų MMS neapdorotus duomenis išorinės atminties faile"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Belaidžiu ryšiu siunčiami įspėjimai"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Pranešimo parinktys"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopijuoti tekstą"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Peržiūrėti išsamią informaciją"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Ištrinti"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Persiųsti"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Išsami pranešimo informacija"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipas: "</string>
+ <string name="text_message" msgid="7415419755252205721">"teksto pranešimas"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Įvairialypės informacijos pranešimas"</string>
+ <string name="from_label" msgid="1947831848146564875">"Nuo: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kam: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Išsiųsta: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Gauta: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Tema: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Dydis: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Pirmenybė: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Aukšta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Įprasta"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Žema"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Paslėptas siuntėjo adresas"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Negalima siųsti žinutės, kol įkeliami priedai."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nepavyko įkelti priedo. Bandykite dar kartą."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Tinklas neparuoštas. Bandykite dar kartą."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Pašalinti tekstą"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Perjungti iš teksto įvedimo į skaičių įvedimą ir atvirkščiai"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Pridėti daugiau dalyvių"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Patvirtinti dalyvius"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Pradėti naują pokalbį"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Pasirinkti šį elementą"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Leisti vaizdo įrašą"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Žmonės ir parinktys"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Derinti"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Žmonės ir parinktys"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Bendrieji"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Žmonės šiame pokalbyje"</string>
+ <string name="action_call" msgid="6596167921517350362">"Skambinti"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Siųsti pranešimą"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Siųsti pranešimą&lt;br/&gt;&lt;small&gt;iš <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Siųsti nuotraukas</item>
+ <item quantity="few">Siųsti nuotraukas</item>
+ <item quantity="many">Siųsti nuotraukas</item>
+ <item quantity="other">Siųsti nuotraukas</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Siųsti garso įrašus</item>
+ <item quantity="few">Siųsti garso įrašus</item>
+ <item quantity="many">Siųsti garso įrašus</item>
+ <item quantity="other">Siųsti garso įrašus</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Siųsti vaizdo įrašus</item>
+ <item quantity="few">Siųsti vaizdo įrašus</item>
+ <item quantity="many">Siųsti vaizdo įrašus</item>
+ <item quantity="other">Siųsti vaizdo įrašus</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Siųsti kontaktų korteles</item>
+ <item quantity="few">Siųsti kontaktų korteles</item>
+ <item quantity="many">Siųsti kontaktų korteles</item>
+ <item quantity="other">Siųsti kontaktų korteles</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Siųsti priedus</item>
+ <item quantity="few">Siųsti priedus</item>
+ <item quantity="many">Siųsti priedus</item>
+ <item quantity="other">Siųsti priedus</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> priedas paruoštas siųsti</item>
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> priedai paruošti siųsti</item>
+ <item quantity="many"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> priedo paruošta siųsti</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> priedų paruošta siųsti</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Siųsti atsiliepimą"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Žr. „Google Play“ parduotuvėje"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versijos informacija"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"%1$s versija"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Atvirojo šaltinio licencijos"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Pranešimai"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Pasiektas priedų apribojimas"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Nepavyko įkelti priedo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Pridėti prie kontaktų?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Pridėti kontaktą"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Tema"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Tema: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Įkeliama kontakto kortelė"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Nepavyko įkelti kontakto kortelės"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Peržiūrėti kontakto kortelę"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kontaktas</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontaktai</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> kontakto</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktų</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktų kortelės"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Gimimo data"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Užrašai"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Persiųsti pranešimą"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Atsakyti"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS išjungta"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Kad galėtumėte išsiųsti, nustatykite Susirašinėjimo programą kaip numatytąją SMS programą"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Nustatykite Susirašinėjimo programą kaip numatytąją SMS programą"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Pakeisti"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Kad galėtumėte gauti pranešimus, nustatykite Susirašinėjimo programą kaip numatytąją SMS programą"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nepasirinkta jokių pageidaujamų SIM kortelių, kurias būtų galima naudoti SMS pranešimams siųsti"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Įrenginio savininkas neleidžia naudoti šios programos."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Gerai"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Per daug pokalbio dalyvių"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Netinkami kontaktai</item>
+ <item quantity="few">Netinkami kontaktai</item>
+ <item quantity="many">Netinkami kontaktai</item>
+ <item quantity="other">Netinkami kontaktai</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Nepavyko įkelti fotoaparato vaizdo"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Jūs: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Juodraštis"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Kai pradėsite naują pokalbį, jis bus pateiktas čia"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Archyvuoti pokalbiai rodomi čia"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Įkeliami pokalbiai..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Nuotrauka"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Garso įrašo klipas"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vaizdo įrašas"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontakto kortelė"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Anuliuoti"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Bandyti dar kartą"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Įveskite kontakto vardą arba telefono numerį, kad pradėtumėte naują pranešimą"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokuoti"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokuoti <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Panaikinti <xliff:g id="DESTINATION">%s</xliff:g> blokavimą"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blokuoti <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Ir toliau gausite pranešimus iš šio numerio, bet nebūsite apie juos įspėti. Šis pokalbis bus archyvuotas."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Užblokuoti kontaktai"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"PANAIKINTI BLOKAVIMĄ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Užblokuoti kontaktai"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Pasirinkite vaizdą iš dokumentų bibliotekos"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Siunčiamas pranešimas"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Pranešimas išsiųstas"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobiliojo ryšio duomenys išjungti. Patikrinkite nustatymus."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Negalima siųsti pranešimų lėktuvo režimu"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Nepavyko išsiųsti pranešimo"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Pranešimas atsisiųstas"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobiliojo ryšio duomenys išjungti. Patikrinkite nustatymus."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Nepavyksta atsisiųsti pranešimų lėktuvo režimu"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Pranešimo nepavyko atsisiųsti"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nulis"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Vienas"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Du"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Trys"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Keturi"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Penki"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Šeši"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Septyni"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Aštuoni"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Devyni"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Nepavyko išsiųsti pranešimo; operatorius „<xliff:g id="CARRIERNAME">%1$s</xliff:g>“, <xliff:g id="ERRORCODE">%2$d</xliff:g> klaida"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Nepavyko išsiųsti pranešimo; operatorius nežinomas, <xliff:g id="ERRORCODE">%1$d</xliff:g> klaida"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Pers.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Pranešimas neišsiųstas: paslauga nesuaktyvinta tinkle"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Pranešimas neišsiųstas: netinkamas paskirties adresas"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Pranešimas neišsiųstas: netinkamas pranešimas"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Pranešimas neišsiųstas: nepalaikomas turinys"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Pranešimas neišsiųstas: nepalaikomas pranešimas"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Pranešimas neišsiųstas: per didelis"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Naujas pranešimas"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Peržiūrėti"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Vaizdas"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nepavyko rasti tinkamos programos"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Pašalinti gavėją"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Naujas pranešimas"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Atšaukti"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Redaguoti prieigos tašką"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nenustatyta"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Pavadinimas"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS tarpinis serveris"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS prievadas"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN tipas"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Ištrinti APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Naujas APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Išsaugoti"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Atmesti"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Pavadinimo laukas negali būti tuščias."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN laukas negali būti tuščias."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC lauke turi būti įvesti 3 skaitmenys."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC lauke turi būti įvesti 2 ar 3 skaitmenys."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Atkuriami numatytieji APN nustatymai."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Iš naujo nustatyti numatytuosius nustatymus"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Numatytųjų APN nustatymų nustatymas iš naujo baigtas."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Be pavadinimo"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Prieigos taškų pavadinimai"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Naujas APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Prieigos taško pavadinimo nustatymai šiam naudotojui nepasiekiami"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopijuoti į iškarpinę?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopijuoti"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"į <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Bendrieji"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Išplėstiniai"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Bendrieji nustatymai"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Išplėstiniai nustatymai"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"„<xliff:g id="SIM_NAME">%s</xliff:g>“ SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Siųsti individualius SMS pranešimus visiems gavėjams. Atsakymus gausite tik jūs"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Siųsti vieną MMS visiems gavėjams"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nežinomas numeris"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Naujas pranešimas"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Naujas pranešimas."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM pasirinkimas"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Pasirinkta: <xliff:g id="SIM_0">%1$s</xliff:g>, SIM pasirinkimas"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Redaguoti temą"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Pasirinkite SIM arba redaguokite temą"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Palaikykite palietę, kad įrašytumėte garso įrašą"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Pradėti naują pokalbį"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Susirašinėjimo programa"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Susirašinėjimo programos sąrašas"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Susirašinėjimo programa"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Naujas pranešimas"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Pokalbių sąrašas"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Įkeliami pokalbiai"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Įkeliami pranešimai"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Peržiūrėti daugiau pokalbių"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Peržiūrėti daugiau pranešimų"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Pokalbis ištrintas"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Pokalbis ištrintas. Palieskite, kad būtų rodomas kitas Susirašinėjimo programos pokalbis."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Užblokuota"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Atblokuota"</string>
+ <string name="db_full" msgid="8459265782521418031">"Mažai laisvos saugyklos vietos. Kai kurie duomenys gali būti prarasti."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Pasirinkti priedus"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Patvirtinti pasirinkimą"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Pasirinkta: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Pašalinkite bent vieną priedą ir bandykite dar kartą."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Galite pabandyti siųsti pranešimą, bet gali nepavykti jo pristatyti, nebent pašalinsite bent vieną priedą."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Viename pranešime galite siųsti tik vieną vaizdo įrašą. Pašalinkite papildomus vaizdo įrašus ir bandykite dar kartą."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Susirašinėjimo programai nepavyko įkelti priedo."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Vis tiek siųsti"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Nepavyko pradėti pokalbio"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Pasirinkta <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-lv/arrays.xml b/res/values-lv/arrays.xml
new file mode 100644
index 0000000..0e130b7
--- /dev/null
+++ b/res/values-lv/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"bez temata"</item>
+ <item msgid="272485471009191934">"bez temata"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Jā"</item>
+ <item msgid="6049132459802288033">"Nē"</item>
+ <item msgid="3084376867445867895">"Labi"</item>
+ <item msgid="3155097332660174689">"He-he"</item>
+ <item msgid="2611328818571146775">"Paldies!"</item>
+ <item msgid="4881335087096496747">"Piekrītu"</item>
+ <item msgid="2422296858597420738">"Jauki"</item>
+ <item msgid="4805581752819452687">"Esmu ceļā"</item>
+ <item msgid="4746700499431366214">"Labi, vēlāk ar tevi sazināšos."</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..a3195b3
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,545 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Ziņojumapmaiņa"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Ziņojumapmaiņa"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Atlasiet sarunu"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Iestatījumi"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Nosūtīt ziņojumu"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Pievienot pielikumu"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Palīdzība"</string>
+ <string name="welcome" msgid="2857560951820802321">"Laipni lūdzam!"</string>
+ <string name="skip" msgid="7238879696319945853">"Izlaist"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Tālāk &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Tālāk"</string>
+ <string name="exit" msgid="1905187380359981199">"Iziet"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Iestatījumi &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Iestatījumi"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Lietotnei Ziņojumapmaiņa ir nepieciešama piekļuves atļauja īsziņām, kā arī lietotnēm Tālrunis un Kontaktpersonas."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Atļaujas varat mainīt sadaļā Iestatījumi &gt; Lietotnes &gt; Ziņojumapmaiņa &gt; Atļaujas."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Atļaujas varat mainīt sadaļā Iestatījumi &gt; Lietotnes &gt; Ziņojumapmaiņa &gt; Atļaujas."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Biežāk izmantotās"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Visas kontaktpersonas"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Nosūtīt uz: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Uzņemt attēlus vai video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Izvēlieties attēlus no šīs ierīces."</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Ierakstīt audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Fotoattēla izvēle"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Saturs ir atlasīts."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Satura atlase ir noņemta."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Atlasīts: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"attēls, <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" msgid="8142175029053370662">"attēls"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Ierakstīt audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Kopīgot"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Tikko"</string>
+ <string name="posted_now" msgid="867560789350406701">"Tikko"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> stundas</item>
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> stunda</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> stundas</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> dienas</item>
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> diena</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dienas</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="zero"><xliff:g id="COUNT">%d</xliff:g> nedēļas</item>
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> nedēļa</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> nedēļas</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="zero"><xliff:g id="COUNT">%d</xliff:g> mēneši</item>
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mēnesis</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mēneši</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="zero"><xliff:g id="COUNT">%d</xliff:g> gadi</item>
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> gads</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> gadi</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0. klases ziņojums"</string>
+ <string name="save" msgid="5081141452059463572">"Saglabāt"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Ierīcē ir maz vietas. Lai atbrīvotu vietu, lietotnē Ziņojumapmaiņa tiks automātiski dzēsti vecāki ziņojumi."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Paliek maz brīvas vietas"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Kamēr ierīcē nebūs vairāk vietas, lietotnē Ziņojumapmaiņa, iespējams, nevarēs sūtīt vai saņemt ziņojumus."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Maz vietas īsziņām. Iespējams, jums būs jādzēš īsziņas."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Tālruņa numura apstiprināšana"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Šī vienreizējā darbība ir nepieciešama, lai lietotnē Ziņojumapmaiņa tiktu pareizi piegādāti jūsu grupas ziņojumi."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Tālruņa numurs"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Dzēst visus ziņojumus, kas ietver multivides saturu"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Dzēst ziņojumus, kas vecāki par <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Automātiski dzēst ziņojumus, kas vecāki par: <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorēt"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Vai dzēst visas ziņas, kurās ietverts multivides saturs?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Vai dzēst ziņojumus, kas vecāki par: <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Vai dzēst ziņojumus, kas vecāki par: <xliff:g id="DURATION">%s</xliff:g>, un ieslēgt automātisko dzēšanu?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> rakstīja"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Jūs rakstījāt"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Ziņojums no <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Jūs nosūtījāt ziņojumu"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Notiek sūtīšana…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Neizdevās nosūtīt. Pieskarieties, lai mēģinātu vēlreiz."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Neizdevās nosūtīt. Notiek atkārtots mēģinājums…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Sūtiet vēlreiz vai dzēsiet vienumu"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Veiciet balss zvanu uz avārijas dienesta tālruņa numuru. Pašlaik nevar nosūtīt īsziņu."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Neizdevās"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Jauna lejupielādējama multiziņa"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Jauna multiziņa"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nevarēja lejupielādēt"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Pieskarieties, lai mēģinātu vēlreiz"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Pieskarieties, lai lejupielādētu"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Lejupielādējiet vai dzēsiet"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Notiek lejupielāde…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Ziņojums nav pieejams, vai ir beidzies tā derīguma termiņš"</string>
+ <string name="mms_info" msgid="3402311750134118165">"lielums: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>; derīgs līdz: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nevar nosūtīt ziņojumu. Adresāts nav derīgs."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Pakalpojums tīklā nav aktivizēts."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Nevarēja nosūtīt tīkla problēmas dēļ."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Ziņa nav pieejama, vai ir beidzies tās derīguma termiņš."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Bez temata)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Nezināms sūtītājs"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Piegādāts"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nevarēja lejupielādēt no sūtītāja <xliff:g id="FROM">%2$s</xliff:g> saņemto ziņojumu “<xliff:g id="SUBJECT">%1$s</xliff:g>”."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Nevarēja pabeigt datu bāzes darbību, jo nav vietas atmiņā."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Ziņojums netika nosūtīts."</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Lietotnē Ziņojumapmaiņa netika nosūtīti daži ziņojumi."</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="zero"><xliff:g id="MESSAGES_1">%d</xliff:g> ziņojumi <xliff:g id="CONVERSATIONS">%d</xliff:g> sarunās</item>
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> ziņojumi <xliff:g id="CONVERSATIONS">%d</xliff:g> sarunā</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ziņojumi <xliff:g id="CONVERSATIONS">%d</xliff:g> sarunās</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Ziņojums nav lejupielādēts."</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Lietotnē Ziņojumapmaiņa nav lejupielādēti daži ziņojumi."</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="zero"><xliff:g id="MESSAGES_1">%d</xliff:g> ziņojumi <xliff:g id="CONVERSATIONS">%d</xliff:g> sarunās</item>
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> ziņojumi <xliff:g id="CONVERSATIONS">%d</xliff:g> sarunā</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ziņojumi <xliff:g id="CONVERSATIONS">%d</xliff:g> sarunās</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Ziņojums netika nosūtīts uz tālruņa numuru <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Veiciet balss zvanu uz avārijas dienesta tālruņa numuru. Pašlaik nevar nosūtīt īsziņu uz tālruņa numuru <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="zero"><xliff:g id="MESSAGES">%d</xliff:g> jauni ziņojumi</item>
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> jauns ziņojums</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> jauni ziņojumi</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Ieslēgt"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera nav pieejama."</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera nav pieejama."</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video tveršana nav pieejama."</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Nevar saglabāt multivides failu."</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nevar uzņemt attēlu"</string>
+ <string name="back" msgid="1477626055115561645">"Atpakaļ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arhīvs"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Dzēst"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arhivēt"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Izņemt no arhīva"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Izslēgt paziņojumus"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Ieslēgt paziņojumus"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Pievienot kontaktpersonu"</string>
+ <string name="action_download" msgid="7786338136368564146">"Lejupielādēt"</string>
+ <string name="action_send" msgid="377635240181672039">"Sūtīt"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Dzēst"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Vai dzēst šo ziņojumu?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Šo darbību nevar atsaukt."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Dzēst"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="zero">Vai dzēst šīs sarunas?</item>
+ <item quantity="one">Vai dzēst šīs sarunas?</item>
+ <item quantity="other">Vai dzēst šīs sarunas?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Dzēst"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Atcelt"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Kam:"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Atlasīt vairākus attēlus"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Apstiprināt atlasi"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"Vēl <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Nevar ierakstīt audio. Mēģiniet vēlreiz."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Nevar atskaņot audio. Mēģiniet vēlreiz."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Nevarēja saglabāt audio. Mēģiniet vēlreiz."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Pieskarieties un turiet."</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Attēls"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audioklips"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Videoklips"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontaktpersonas kartīte"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Lejupielādēt"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Atbildēt ar īsziņu"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Atbildēt ar MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Atbildēt"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> dalībnieki</item>
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dalībnieks</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dalībnieki</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Es"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontaktpersona ir bloķēta un arhivēta"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontaktpersona ir atbloķēta un atarhivēta."</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Arhivētas: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Atcelta arhivēšana: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Paziņojumi ir izslēgti."</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Paziņojumi ir ieslēgti."</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Iestatīšana ir pabeigta. Vēlreiz pieskarieties vienumam Sūtīt."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Lietotne Ziņojumapmaiņa ir sekmīgi iestatīta kā noklusējuma īsziņu lietotne."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="zero">Atmest pielikumus</item>
+ <item quantity="one">Atmest pielikumu</item>
+ <item quantity="other">Atmest pielikumus</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audio pielikums"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Atskaņot audio pielikumu"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pārtraukt"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Ziņojums no lietotāja <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Neizdevās saņemt ziņojumu no kontaktpersonas <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Ziņojums no kontaktpersonas <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Nenosūtīts ziņojums kontaktpersonai <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Notiek ziņojuma sūtīšana kontaktpersonai <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Neizdevās nosūtīt ziņojumu kontaktpersonai <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Ziņojums kontaktpersonai <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Neizdevās saņemt ziņojumu no kontaktpersonas <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Ziņojums no kontaktpersonas <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Nenosūtīts ziņojums grupai <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Notiek ziņojuma sūtīšana grupai <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Neizdevās nosūtīt ziņojumu grupai <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Ziņojums grupai <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Laiks: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Radās problēma ar ziņojumu. Pieskarieties, lai mēģinātu vēlreiz."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Saruna ar grupu: <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Dzēst tēmu"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Uzņemt video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Uzņemt fotoattēlu"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Uzņemt attēlu"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Sākt video ierakstīšanu"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Pārslēgties uz pilnekrāna kameru"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Pārslēgties no priekšējās kameras uz aizmugurējo kameru un otrādi"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Pārtraukt ierakstīšanu un pievienot videoklipu"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Pārtraukt videoklipa ierakstīšanu"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotoattēli lietotnē Ziņojumapmaiņa"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="zero"><xliff:g id="QUANTITY_2">%d</xliff:g> fotoattēli tika saglabāti albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> fotoattēls tika saglabāts albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotoattēli tika saglabāti albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="zero"><xliff:g id="QUANTITY_2">%d</xliff:g> videoklipi tika saglabāti albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> videoklips tika saglabāts albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videoklipi tika saglabāti albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="zero"><xliff:g id="QUANTITY_2">%d</xliff:g> pielikumi tika saglabāti albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> pielikums tika saglabāts albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> pielikumi tika saglabāti albumā “<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="zero"><xliff:g id="QUANTITY_1">%d</xliff:g> pielikumi tika saglabāti mapē “Lejupielādes”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> pielikums tika saglabāts mapē “Lejupielādes”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> pielikumi tika saglabāti mapē “Lejupielādes”</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="zero">Tika saglabāti <xliff:g id="QUANTITY_1">%d</xliff:g> pielikumi.</item>
+ <item quantity="one">Tika saglabāts <xliff:g id="QUANTITY_1">%d</xliff:g> pielikums.</item>
+ <item quantity="other">Tika saglabāti <xliff:g id="QUANTITY_1">%d</xliff:g> pielikumi.</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="zero">Nevarēja saglabāt <xliff:g id="QUANTITY_1">%d</xliff:g> pielikumus.</item>
+ <item quantity="one">Nevarēja saglabāt <xliff:g id="QUANTITY_1">%d</xliff:g> pielikumu.</item>
+ <item quantity="other">Nevarēja saglabāt <xliff:g id="QUANTITY_1">%d</xliff:g> pielikumus.</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Multiziņas pielikums tika saglabāts."</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Iestatījumi"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arhīvs"</string>
+ <string name="action_close" msgid="1840519376200478419">"Aizvērt"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"Multiziņa"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Papildu"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Atkļūdot"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Paziņojumi"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Signāls"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Klusums"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrozvans"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloķēts"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Īsziņu piegādes atskaites"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Pieprasiet piegādes atskaiti par katru sūtīto īsziņu."</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automātiski izgūt"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automātiski izgūstiet multiziņas."</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Autom. izgūšana viesabon. laikā"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automātiska multiziņu izgūšana viesabonēšanas laikā"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Grupas ziņojumapmaiņa"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Izmantojiet multiziņu, lai vairākiem adresātiem nosūtītu vienu ziņu."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Noklusējuma īsziņu lietotne"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Noklusējuma īsziņu lietotne"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Jūsu tālruņa numurs"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Nezināms"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Izejošo ziņojumu signāli"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Īsziņu izrakstīšana"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Izrakstiet saņemto īsziņu jēldatus ārējā krātuves failā."</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Multiziņu izrakstīšana"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Izrakstiet saņemto multiziņu jēldatus ārējā krātuves failā."</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Bezvadu brīdinājumi"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Ziņojuma opcijas"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopēt tekstu"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Skatīt detalizētu informāciju"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Dzēst"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Pārsūtīt"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalizēta informācija par ziņojumu"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Veids: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Īsziņa"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multiziņa"</string>
+ <string name="from_label" msgid="1947831848146564875">"No: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kam: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Nosūtīts: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Saņemts: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Temats: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Lielums: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritāte: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Augsta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Parasta"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Zema"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Sūtītāja adrese ir slēpta"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Ziņojumu nevar nosūtīt, kamēr tiek ielādēti pielikumi."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nevar ielādēt pielikumu. Mēģiniet vēlreiz."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Tīkls nav gatavs lietošanai. Mēģiniet vēlreiz."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Dzēst tekstu"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Pārslēgties starp teksta un ciparu ievadīšanu"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Pievienot vēl dalībniekus"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Apstiprināt dalībniekus"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Sākt jaunu sarunu"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Atlasīt šo vienumu"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Atskaņot videoklipu"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Personas un opcijas"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Atkļūdot"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Personas un opcijas"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Vispārīgi"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Sarunas dalībnieki"</string>
+ <string name="action_call" msgid="6596167921517350362">"Zvanīt"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Sūtīt ziņojumu"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Sūtiet īsziņu,&lt;br/&gt;&lt;small&gt;izmantojot šo SIM: <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="zero">Sūtīt fotoattēlus</item>
+ <item quantity="one">Sūtīt fotoattēlus</item>
+ <item quantity="other">Sūtīt fotoattēlus</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="zero">Sūtīt audio failus</item>
+ <item quantity="one">Sūtīt audio failus</item>
+ <item quantity="other">Sūtīt audio failus</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="zero">Sūtīt videoklipus</item>
+ <item quantity="one">Sūtīt videoklipus</item>
+ <item quantity="other">Sūtīt videoklipus</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="zero">Sūtīt kontaktpersonu kartītes</item>
+ <item quantity="one">Sūtīt kontaktpersonu kartītes</item>
+ <item quantity="other">Sūtīt kontaktpersonu kartītes</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="zero">Sūtīt pielikumus</item>
+ <item quantity="one">Sūtīt pielikumus</item>
+ <item quantity="other">Sūtīt pielikumus</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="zero">Nav atlasīts neviens pielikums.</item>
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> pielikums ir gatavs nosūtīšanai.</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> pielikumi ir gatavi nosūtīšanai.</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Sūtīt atsauksmes"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Skatīt Google Play veikalā"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informācija par versiju"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versija %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Atklātā pirmkoda licences"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Paziņojumi"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Sasniegts pielikumu ierobežojums."</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Neizdevās ielādēt pielikumu."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Vai pievienot kontaktpersonām?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Pievienot kontaktpersonu"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Temats"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Temats: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Notiek kontaktpersonas kartītes ielāde…"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Nevarēja ielādēt kontaktpersonas kartīti."</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Skatiet kontaktpersonas kartīti"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="zero"><xliff:g id="COUNT_1">%d</xliff:g> kontaktpersonas</item>
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kontaktpersona</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktpersonas</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktpersonu kartītes"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Dzimšanas diena"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Piezīmes"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Ziņojuma pārsūtīšana"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Atbildēt"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Īsziņas ir atspējotas."</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Lai sūtītu īsziņas, iestatiet lietotni Ziņojumapmaiņa kā noklusējuma īsziņu lietotni."</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Iestatiet lietotni Ziņojumapmaiņa kā noklusējuma īsziņu lietotni."</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Mainīt"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Lai saņemtu īsziņas, iestatiet lietotni Ziņojumapmaiņa kā noklusējuma īsziņu lietotni."</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Īsziņu sūtīšanai nav atlasīta vēlamā SIM karte"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Ierīces īpašnieks nav atļāvis šīs lietotnes darbību."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Labi"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Sarunā ir pārāk daudz dalībnieku."</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="zero">Nederīgas kontaktpersonas</item>
+ <item quantity="one">Nederīga kontaktpersona</item>
+ <item quantity="other">Nederīgas kontaktpersonas</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Nevarēja ielādēt kameras attēlu."</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Jūs: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Melnraksts"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Jaunas sarunas tiks rādītas šeit."</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Šeit tiek rādītas arhivētās sarunas."</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Notiek sarunu ielāde..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Attēls"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audioklips"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Videoklips"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontaktpersonas kartīte"</string>
+ <string name="mms_text" msgid="1528791558806015806">"Multiziņa"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Atsaukt"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Mēģināt vēlreiz"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Ievadiet kontaktpersonas vārdu vai tālruņa numuru, lai sāktu rakstīt jaunu ziņojumu."</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloķēt"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloķēt: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Atbloķēt: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Vai bloķēt <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Jūs turpināsiet saņemt ziņojumus no šī numura, taču vairs nesaņemsiet par tiem paziņojumus. Šī saruna tiks arhivēta."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Bloķētās kontaktpersonas"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ATBLOĶĒT"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Bloķētās kontaktpersonas"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Izvēlēties attēlu no dokumentu bibliotēkas"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Notiek ziņojuma sūtīšana"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Ziņojums nosūtīts"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobilo datu savienojums ir izslēgts. Pārbaudiet iestatījumus."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Lidojuma režīmā nevar nosūtīt ziņojumus."</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Ziņojumu nevarēja nosūtīt."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Ziņojums lejupielādēts"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobilo datu savienojums ir izslēgts. Pārbaudiet iestatījumus."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Ziņojumus nevar lejupielādēt lidojuma režīmā"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Ziņojumu nevarēja lejupielādēt"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nulle"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Viens"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Divi"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Trīs"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Četri"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Pieci"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seši"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Septiņi"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Astoņi"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Deviņi"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Nevar nosūtīt ziņojumu: operatora <xliff:g id="CARRIERNAME">%1$s</xliff:g> kļūda <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Nevar nosūtīt ziņojumu: nezināma operatora kļūda <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Pārs.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Ziņojums netika nosūtīts: pakalpojums nav aktivizēts tīklā."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Ziņojums nav nosūtīts: nederīga galamērķa adrese"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Ziņojums nav nosūtīts: nederīgs ziņojums"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Ziņojums nav nosūtīts: neatbalstīts saturs"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Ziņojums nav nosūtīts: neatbalstīts ziņojums"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Ziņojums nav nosūtīts: pārāk liels"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Jauns ziņojums"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Skatīt"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Attēls"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Neizdevās atrast atbilstošu lietojumprogrammu."</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Noņemt adresātu"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Jauns ziņojums"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Atcelt"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Piekļuves punkta rediģēšana"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nav iestatīts"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nosaukums"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Multiziņu starpniekserveris"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Multiziņu ports"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN veids"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Dzēst APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Jauns APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Saglabāt"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Atmest"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Nosaukuma lauks nedrīkst būt tukšs."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN lauks nedrīkst būt tukšs."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC laukā ir jābūt 3 cipariem."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC laukā ir jābūt 2 vai 3 cipariem."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Notiek APN noklusējuma iestatījumu atjaunošana."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Atiestatīt noklusējuma vērtības"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Noklusējuma APN iestatījumu atiestatīšana ir pabeigta."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Bez nosaukuma"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Piekļuves punktu nosaukumi"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Jauns APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Piekļuves punkta nosaukuma iestatījumi šim lietotājam nav pieejami."</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Vai kopēt starpliktuvē?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopēt"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"šajā SIM: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Vispārīgi"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Papildu"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Vispārīgi iestatījumi"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Papildu iestatījumi"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM kartes “<xliff:g id="SIM_NAME">%s</xliff:g>” iestatījumi"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Sūtīt atsevišķas īsziņas visiem adresātiem (atbildes saņemsiet tikai jūs)"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Sūtīt vienu multiziņu visiem adresātiem"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nezināms numurs"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Jauns ziņojums"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Jauns ziņojums"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM kartes atlasītājs"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Atlasīta <xliff:g id="SIM_0">%1$s</xliff:g>, SIM kartes atlasītājs"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Rediģēt tematu"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Atlasīt SIM karti vai rediģēt tematu"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Pieskarieties un turiet, lai ierakstītu skaņu."</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Sākt jaunu sarunu"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Ziņojumapmaiņa"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Sarunu saraksts lietotnē Ziņojumapmaiņa"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Ziņojumapmaiņa"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Jauns ziņojums"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Sarunu saraksts"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Notiek sarunu ielāde"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Notiek ziņojumu ielāde."</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Skatīt citas sarunas"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Skatīt vairāk ziņojumu"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Saruna ir izdzēsta"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Saruna dzēsta. Pieskarieties, lai skatītu citu sarunu lietotnē Ziņojumapmaiņa."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloķēts"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Atbloķēts"</string>
+ <string name="db_full" msgid="8459265782521418031">"Krātuvē ir maz vietas. Daļa datu var tikt zaudēta."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Pielikumu atlasīšana"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Apstiprināt atlasi"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Atlasīti: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Lūdzu, noņemiet vienu vai vairākus pielikumus un mēģiniet vēlreiz."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Varat mēģināt sūtīt ziņojumu, taču tas, iespējams, netiks piegādāts, ja vien nenoņemsiet vienu vai vairākus pielikumus."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Ziņojumam var pievienot tikai vienu videoklipu. Lūdzu, noņemiet liekos videoklipus un mēģiniet vēlreiz."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Lietotnē Ziņojumapmaiņa neizdevās ielādēt pielikumu."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Tik un tā sūtīt"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Nevarēja sākt sarunu"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Atlasīts: <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-mk-rMK/arrays.xml b/res/values-mk-rMK/arrays.xml
new file mode 100644
index 0000000..eba8db7
--- /dev/null
+++ b/res/values-mk-rMK/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"без наслов"</item>
+ <item msgid="272485471009191934">"без тема"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Да"</item>
+ <item msgid="6049132459802288033">"Не"</item>
+ <item msgid="3084376867445867895">"Во ред"</item>
+ <item msgid="3155097332660174689">"Хехе"</item>
+ <item msgid="2611328818571146775">"Фала"</item>
+ <item msgid="4881335087096496747">"Се согласувам"</item>
+ <item msgid="2422296858597420738">"Убаво"</item>
+ <item msgid="4805581752819452687">"На пат сум"</item>
+ <item msgid="4746700499431366214">"Во ред, ќе ти јавам подоцна"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
new file mode 100644
index 0000000..98f871e
--- /dev/null
+++ b/res/values-mk-rMK/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Пораки"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Пораки"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Изберете разговор"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Поставки"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Испрати порака"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Додај прилог"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Помош"</string>
+ <string name="welcome" msgid="2857560951820802321">"Добре дојдовте"</string>
+ <string name="skip" msgid="7238879696319945853">"Прескокни"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Следно &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Следно"</string>
+ <string name="exit" msgid="1905187380359981199">"Излези"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Поставки &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Поставки"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"На Messaging му треба дозвола за СМС, телефон и контакти."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Дозволите може да ги промените во Поставки &gt; Апликации &gt; Messaging &gt; Дозволи."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Дозволите може да ги промените во Поставки, Апликации, Messaging, Дозволи."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Чести"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Сите контакти"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Испрати на <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Снимај слики или видео"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Изберете слики од уредот"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Снимај аудио"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Избери фотографија"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Медиумот е избран."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Медиумот е неизбран."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Избрани: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"слика <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" msgid="8142175029053370662">"слика"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Снимај аудио"</string>
+ <string name="action_share" msgid="2143483844803153871">"Сподели"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Пред малку"</string>
+ <string name="posted_now" msgid="867560789350406701">"Сега"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> мин.</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> мин.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> час</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> часа</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ден</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> дена</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> седмица</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> седмици</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> месец</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> месеци</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> година</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> години</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Порака 0 класа"</string>
+ <string name="save" msgid="5081141452059463572">"Зачувај"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Уредот нема доволно меморија. Messaging автоматски ќе ги брише постарите текстуални пораки за да ослободи простор."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Меморијата е речиси полна"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Messaging може да не испраќа или прима пораки сѐ додека не се ослободи повеќе простор на уредот."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Малку простор за складирање СМС. Можеби ќе треба да избришете пораки."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Потврдете го својот телефонски број"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Со овој еднократен чекор ќе се погрижите Messaging правилно да ви ги испорачува групните пораки."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Телефонски број"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Избриши ги сите пораки со медиуми"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Избриши ги пораките постари од <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Автоматско бришење пораки постари од <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Игнорирај"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Да се избришат сите пораки со медиуми?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Да се избришат пораките постари од <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Да се избришат пораките постари од <xliff:g id="DURATION">%s</xliff:g> и да се вклучи автоматското бришење?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> рече"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Рековте"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Порака од <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Испративте порака"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Се испраќа..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Не се испрати. Допрете за да се обидете повторно."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Не се испрати. Се обидуваме повторно…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Испратете повторно или избришете"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Направете гласовен повик до службите за итни случаи. Вашата текстуална порака не може да се испорача во ова време."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Неуспешно"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Нова ММС-порака за преземање"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Нова ММС-порака"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Не можеше да се преземе"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Допрете да се обидете повторно"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Допри за преземање"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Преземете или избришете"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Се презема..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Пораката е истечена или не е достапна"</string>
+ <string name="mms_info" msgid="3402311750134118165">"големина: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, истекување: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Не може да се испрати. Примачот не е важечки."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Услугата не е активирана на мрежата"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Не можеше да се испрати поради проблем со мрежата"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Пораката е истечена или не е достапна"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Без наслов)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Непознат испраќач"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Доставено"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Пораката <xliff:g id="SUBJECT">%1$s</xliff:g> од <xliff:g id="FROM">%2$s</xliff:g> не може да се преземе."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Не може да се заврши дејството во базата со податоци поради мала меморија"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Пораката не се испрати"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Некои пораки не се испратени во Messaging"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> пораки во <xliff:g id="CONVERSATIONS">%d</xliff:g> разговор</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> пораки во <xliff:g id="CONVERSATIONS">%d</xliff:g> разговори</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Пораката не е преземена"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Некои пораки не се преземени во Messaging"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> пораки во <xliff:g id="CONVERSATIONS">%d</xliff:g> разговор</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> пораки во <xliff:g id="CONVERSATIONS">%d</xliff:g> разговори</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Пораката до <xliff:g id="NUMBER">%1$s</xliff:g> не се испрати"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Направете гласовен повик до службите за итни случаи. Вашата текстуална порака до <xliff:g id="NUMBER">%1$s</xliff:g> не може да се испорача во ова време."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> нова порака</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> нови пораки</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Започни"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Нема фотоапарат на располагање"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Нема фотоапарат на располагање"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Снимањето видео не е достапно"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Медиумите не може да се зачуваат"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Не може да фотографира"</string>
+ <string name="back" msgid="1477626055115561645">"Назад"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архивирано"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Бришење"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архивирање"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Извади од архива"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Исклучи известувања"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Вклучи известувања"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Додај контакт"</string>
+ <string name="action_download" msgid="7786338136368564146">"Преземи"</string>
+ <string name="action_send" msgid="377635240181672039">"Испрати"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Избриши"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Да се избрише пораката?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Дејството не може да се врати."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Избриши"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Да се избрише разговоров?</item>
+ <item quantity="other">Да се избришат разговориве?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Избриши"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Откажи"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"До"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Избери повеќе слики"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Потврдете го изборот"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Аудиото не може да се сними. Обидете се повторно."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Аудиото не може да се пушти. Обидете се повторно."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Аудиото не можеше да се зачува. Обидете се повторно."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Допри и задржи"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Слика"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудиоклип"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Видео"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Картичка за контакти"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Преземи"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Одговори преку СМС"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Одговори преку ММС"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Одговори"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> учесник</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> учесници</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Јас"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Контактот е блокиран и архивиран"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Одблокиран и одархивиран контакт"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> архивирани"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> извадени од архива"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Известувањата се исклучени"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Известувањата се вклучени"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Сè е подготвено. Допрете Испрати повторно."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Messaging е успешно поставен како стандардна апликација за СМС."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Отфрли го прилогот</item>
+ <item quantity="other">Отфрли ги прилозите</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Аудио прилог"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Репродуцирај аудио прилог"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Пауза"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Порака од <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Неуспешна порака од <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Порака од <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Неиспратена порака до <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Се испраќа порака до <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Неуспешна порака до <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Порака до <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Неуспешна порака од <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Порака од <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Неиспратена порака до <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Се испраќа порака до <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Неуспешна порака до <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Порака до <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Неуспешна порака. Допрете за да се обидете повторно."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Разговор со <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Избриши тема"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Снимај видео"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Сними фотографија"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Фотографирај"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Започни со снимање видео"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Префрлете на камера на цел екран"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Префрли меѓу предна и задна камера"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Престани со снимање и прикачи видео"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Прекини со снимање видео"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Фотографии во Messaging"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> фотографија се зачува во албумот „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> фотографии се зачуваа во албумот „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> видео се зачува во албумот „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> видеа се зачуваа во албумот „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> прилог се зачува во албумот „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> прилози се зачуваа во албумот „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> прилог се зачува во „Преземања“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прилози се зачуваа во „Преземања“</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one">Зачуван е <xliff:g id="QUANTITY_1">%d</xliff:g> прилог</item>
+ <item quantity="other">Зачувани се <xliff:g id="QUANTITY_1">%d</xliff:g> прилози</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Не можеше да се зачува <xliff:g id="QUANTITY_1">%d</xliff:g> прилог</item>
+ <item quantity="other">Не можеше да се зачуваат <xliff:g id="QUANTITY_1">%d</xliff:g> прилози</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Зачуван ММС-прилог"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Поставки"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архивирано"</string>
+ <string name="action_close" msgid="1840519376200478419">"Затвори"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"ММС"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Напредни"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Отстрани грешка"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Известувања"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Звук"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"На тивко"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Вибрации"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Блокирано"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Извештаи за испорака на СМС"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Побарајте извештај за испорака за секоја СМС што ја испраќате"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Автоматско преземање"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Преземај ММС-пораки автоматски"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Автоматско преземање во роаминг"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Автоматски преземај MMS додека си во роаминг"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Групно испраќање пораки"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Користи ММС за испраќање една порака кога има повеќе примачи"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Стандардна СМС апликација"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Стандардна СМС апликација"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Вашиот телефонски број"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Непознато"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Звуци на појдовни пораки"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Извади СМС"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Извади ги примените необработени податоци за СМС на надворешна датотека за складирање"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Извади ММС"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Извади ги примените необработени податоци за ММС на надворешна датотека за складирање"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Безжични предупредувања"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Опции на порака"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Копирај текст"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Прикажи детали"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Избриши"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Проследи"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Детали на порака"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Тип: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Текстуална порака"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Мултимедијална порака"</string>
+ <string name="from_label" msgid="1947831848146564875">"Од: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"До: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Испратено: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Примени: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Предмет: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Големина: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Приоритет: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"СИМ: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Високо"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Нормално"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Ниско"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"СИМ <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Скриена адреса на испраќачот"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Не може да се испрати порака додека се вчитуваат прилозите."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Прилогот не може да се вчита. Обидете се повторно."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Мрежата не е подготвена. Обидете се повторно."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Избриши текст"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Префрлајте меѓу внесување текст и броеви"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Додај повеќе учесници"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Потврди учесници"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Започнете нов разговор"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Изберете ја ставкава"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Пушти видео"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Луѓе и опции"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Отстрани грешка"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Луѓе и опции"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Општи"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Луѓе во овој разговор"</string>
+ <string name="action_call" msgid="6596167921517350362">"Повикај"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Испрати порака"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Испратете порака &lt;br/&gt;&lt;small&gt;од <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Испрати ја фотографијата</item>
+ <item quantity="other">Испрати ги фотографиите</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Испрати го аудиото</item>
+ <item quantity="other">Испрати ги аудиата</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Испрати го видеото</item>
+ <item quantity="other">Испрати ги видеата</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Испрати ја картичката за контакт</item>
+ <item quantity="other">Испрати ги картичките за контакт</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Испрати го прилогот</item>
+ <item quantity="other">Испрати ги прилозите</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прилог е подготвен за испраќање</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прилози се подготвени за испраќање</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Испрати повратни информации"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Прикажи во продавницата на Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Информации за верзијата"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Верзија %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Лиценци со отворен код"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Известувања"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Достигнато е ограничувањето за прилози"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Не успеа да се вчита прилогот."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Да додадеме во контакти?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Додај контакт"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Предмет"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Предмет: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Се вчитува картичката за контакт"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Картичката за контакт не можеше да се вчита"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Прикажи картичка за контакти"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> контакт</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> контакти</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Картички за контакт"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Роденден"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Белешки"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Проследи порака"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Одговори"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"СМС-пораките се оневозможени"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"За испраќање, поставете го Messaging како стандардна апликација за СМС"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Поставете го Messaging како стандардна апликација за СМС"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Промени"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"За примање пораки, поставете го Messaging како стандардна апликација за СМС"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Не е избрана претпочитана СИМ за испраќање СМС-пораки"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Оваа апликација не е дозволена од сопственикот на уредот."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Во ред"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Премногу учесници во разговорот"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Неважечки контакт</item>
+ <item quantity="other">Неважечки контакти</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Слика од камерата не можеше да се вчита"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Вие: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Нацрт"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Штом започнете нов разговор, ќе го видите наведен тука"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Архивираните разговори се појавуваат тука"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Се вчитуваат разговори…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Слика"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудиоклип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Видео"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Картичка за контакти"</string>
+ <string name="mms_text" msgid="1528791558806015806">"ММС"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Врати"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Обидете се повторно"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Внесете име на контактот или телефонски број за да започнете нова порака"</string>
+ <string name="action_block" msgid="9032076625645190136">"Блокирај"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Блокирај <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Одблокирај <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Да се блокира <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Ќе продолжите да добивате пораки од овој број, но веќе нема да добивате известувања. Разговорот ќе се архивира."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Блокирани контакти"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ОДБЛОКИРАЈ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Блокирани контакти"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Изберете слика од библиотеката со документи"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Пораката се испраќа"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Пораката е испратена"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Мобилните податоци се исклучени. Проверете ги поставките."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Не може да се испраќаат пораки во авионски режим"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Пораката не може да се испрати."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Пораката е преземeна"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Мобилните податоци се исклучени. Проверете ги поставките."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Не може да се преземаат пораки во авионски режим"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Пораката не можеше да се преземе"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Нула"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Еден"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Два"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Три"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Четири"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Пет"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Шест"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Седум"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Осум"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Девет"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Пораката не може да се испрати преку <xliff:g id="CARRIERNAME">%1$s</xliff:g>, грешка <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Пораката не може да се испрати преку непознат давател на услуга, грешка <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Препрати: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Пораката не се испрати: услугата не е активирана на мрежата"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Пораката не е испратена: неважечка адреса на одредиштето"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Пораката не е испратена: неважечка порака"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Пораката не е испратена: неподдржана содржина"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Пораката не е испратена: неподдржана порака"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Пораката не е испратена: преголема"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Нова порака"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Прикажи"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Слика"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Не може да се најде соодветна апликација"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Отстрани го примачот"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Нова порака"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Откажи"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Уреди пристапна точка"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Не е поставено"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Име"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Име на пристапна точка (APN)"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"ММСЦ"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Прокси на ММС"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Порта за ММС"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Тип APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Избриши APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Ново APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Зачувај"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Отфрли"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Полето за име не може да биде празно."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN не може да биде празна."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Полето MNC мора да содржи 3 цифри."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Полето MNC мора да содржи 2 или 3 цифри."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Враќање стандардни поставки на APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Ресетирај на стандардни вредности"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Ресетирањето стандардни поставки на APN е завршено."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Без наслов"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Имиња на пристапни точки"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Поставување APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Ново APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Поставките за името на пристапната точка не се достапни за овој корисник"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Копирајте во таблата со исечоци?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Копирај"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"до <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Општи"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Напредни"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Општи поставки"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Напредни поставки"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"СИМ „<xliff:g id="SIM_NAME">%s</xliff:g>“"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Испратете поединечни СМС-пораки до сите примачи. Само вие ќе добивате одговори"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Испратете единствена ММС-порака до сите примачи"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Непознат број"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Нова порака"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Нова порака."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Бирач на СИМ"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> е избран, бирач на СИМ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Уреди предмет"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Избери СИМ или уреди предмет"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Допри и задржи за снимање аудио"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Започнете нов разговор"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Список на Messaging"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Нова порака"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Список со разговори"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Разговорите се вчитуваат"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Се вчитуваат пораки"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Прикажи повеќе разговори"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Прикажи повеќе пораки"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Конверзацијата е избришана"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Разговорот е избришан. Допрете за да се прикаже друг разговор на Messaging"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Блокирано"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Одблокирано"</string>
+ <string name="db_full" msgid="8459265782521418031">"Просторот за складирање е премногу мал. Некои податоци може да се изгубат."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Изберете прилози"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Потврдете го изборот"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Избрани <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Отстранете еден или повеќе прилози и обидете се повторно."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Може да се обидете да ја испратите пораката, но можеби нема да се испорача, освен ако не отстраните еден или повеќе прилози."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Може да испраќате само едно видео по порака. Отстранете ги дополнителните видеа и обидете се повторно."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging не успеа да го вчита прилогот."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Сепак испрати"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Не може да се започне разговор"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Избрана е <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-ml-rIN/arrays.xml b/res/values-ml-rIN/arrays.xml
new file mode 100644
index 0000000..7454e1f
--- /dev/null
+++ b/res/values-ml-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"വിഷയമില്ല"</item>
+ <item msgid="272485471009191934">"വിഷയമൊന്നുമില്ല"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ഉവ്വ്"</item>
+ <item msgid="6049132459802288033">"ഇല്ല"</item>
+ <item msgid="3084376867445867895">"ശരി"</item>
+ <item msgid="3155097332660174689">"ഹിഹി"</item>
+ <item msgid="2611328818571146775">"നന്ദി"</item>
+ <item msgid="4881335087096496747">"ഞാൻ അംഗീകരിക്കുന്നു"</item>
+ <item msgid="2422296858597420738">"കൊള്ളാം"</item>
+ <item msgid="4805581752819452687">"മറുപടി നൽകാൻ തയ്യാറാണ്"</item>
+ <item msgid="4746700499431366214">"ശരി, പിന്നീട് നിങ്ങളുമായി ബന്ധപ്പെടാൻ എന്നെ അനുവദിക്കുക"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
new file mode 100644
index 0000000..554cac3
--- /dev/null
+++ b/res/values-ml-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"സന്ദേശമയയ്‌ക്കൽ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"സന്ദേശമയയ്‌ക്കൽ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"സംഭാഷണം തിരഞ്ഞെടുക്കുക"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ക്രമീകരണങ്ങൾ"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"സന്ദേശം അയയ്ക്കൂ"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ഒരു അറ്റാച്ചുമെന്റ് ചേർക്കുക"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"സഹായം"</string>
+ <string name="welcome" msgid="2857560951820802321">"സ്വാഗതം"</string>
+ <string name="skip" msgid="7238879696319945853">"ഒഴിവാക്കുക"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"അടുത്തത് &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"അടുത്തത്"</string>
+ <string name="exit" msgid="1905187380359981199">"പുറത്തുകടക്കുക"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ക്രമീകരണം &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ക്രമീകരണം"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"SMS, ഫോൺ, കോൺടാക്റ്റുകൾ എന്നിവയിലേക്ക് സന്ദേശമയയ്ക്കലിന് അനുമതി ആവശ്യമാണ്."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ക്രമീകരണം &gt; ആപ്സ് &gt; സന്ദേശമയയ്ക്കൽ &gt; അനുമതികൾ എന്നതിൽ നിങ്ങൾക്ക് അനുമതികൾ മാറ്റാവുന്നതാണ്."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ക്രമീകരണം, ആപ്സ്, സന്ദേശമയയ്ക്കൽ, അനുമതികൾ എന്നതിൽ നിങ്ങൾക്ക് അനുമതികൾ മാറ്റാവുന്നതാണ്."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"പതിവായി കോൺടാക്‌റ്റുചെയ്യുന്നവർ"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"എല്ലാ കോൺടാക്റ്റുകളും"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> എന്നതിലേയ്ക്ക് അയയ്ക്കുക"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ചിത്രങ്ങൾ അല്ലെങ്കിൽ വീഡിയോ എടുക്കുക"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ഈ ഉപകരണത്തിൽ നിന്ന് ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ഓഡിയോ റെക്കോർഡുചെയ്യുക"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ഫോട്ടോ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"മീഡിയ തിരഞ്ഞെടുത്തിരിക്കുന്നു."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"മീഡിയ തിരഞ്ഞെടുത്തത് മാറ്റി."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> എണ്ണം തിരഞ്ഞെടുത്തു"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ചിത്രം <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" msgid="8142175029053370662">"ചിത്രം"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ഓഡിയോ റെക്കോർഡുചെയ്യുക"</string>
+ <string name="action_share" msgid="2143483844803153871">"പങ്കിടുക"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ഇപ്പോൾ"</string>
+ <string name="posted_now" msgid="867560789350406701">"ഇപ്പോൾ"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> മിനിറ്റ്</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> മിനിറ്റ്</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> മണിക്കൂർ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> മണിക്കൂർ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ദിവസം</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ദിവസം</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ആഴ്‌ച</item>
+ <item quantity="one">ഒരു ആഴ്‌ച</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> മാസം</item>
+ <item quantity="one">ഒരു മാസം</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> വർഷം</item>
+ <item quantity="one">ഒരു വർഷം</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"ക്ലാസ് 0 സന്ദേശം"</string>
+ <string name="save" msgid="5081141452059463572">"സംരക്ഷിക്കുക"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ഉപകരണത്തിൽ ഇടം കുറവാണ്. ഇടം സ്വതന്ത്രമാക്കുന്നതിന് പഴയ സന്ദേശങ്ങളെ സന്ദേശമയയ്ക്കൽ സ്വയമേവ ഇല്ലാതാക്കും."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"സംഭരണയിടം കഴിഞ്ഞു"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"നിങ്ങളുടെ ഉപകരണത്തിൽ കൂടുതൽ ഇടം ലഭ്യമാകുന്നത് വരെ സന്ദേശമയയ്ക്കലിന് സന്ദേശങ്ങൾ അയയ്ക്കാനോ സ്വീകരിക്കാനോ ആയേക്കില്ല."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS സ്റ്റോറേജ് കുറവാണ്. നിങ്ങൾക്ക് സന്ദേശങ്ങൾ ഇല്ലാതാക്കേണ്ടതായി വരാം."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"നിങ്ങളുടെ ഫോൺ നമ്പർ സ്ഥിരീകരിക്കുക"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"സന്ദേശമയയ്ക്കൽ നിങ്ങളുടെ ഗ്രൂപ്പ് സന്ദേശങ്ങൾ ശരിയായി വിതരണം ചെയ്യുമെന്ന് ഈ ഒറ്റത്തവണയുള്ള ഘട്ടം ഉറപ്പാക്കും."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ഫോണ്‍‌ നമ്പര്‍‌"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"മീഡിയയുള്ള എല്ലാ സന്ദേശങ്ങളും ഇല്ലാതാക്കുക"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> എന്നതിലും പഴയ സന്ദേശങ്ങൾ ഇല്ലാതാക്കുക"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> മുമ്പുള്ള സന്ദേശങ്ങൾ യാന്ത്രികമായി ഇല്ലാതാക്കുക"</string>
+ <string name="ignore" msgid="7063392681130898793">"നിരസിക്കുക"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"മീഡിയയുള്ള എല്ലാ സന്ദേശങ്ങളും ഇല്ലാതാക്കണോ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> മുമ്പുള്ള സന്ദേശങ്ങൾ ഇല്ലാതാക്കണോ?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> മുമ്പുള്ള സന്ദേശങ്ങൾ ഇല്ലാതാക്കുകയും യാന്ത്രിക ഇല്ലാതാക്കൽ ഓണാക്കുകയും ചെയ്യണോ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g>, പറഞ്ഞു"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"നിങ്ങൾ പറഞ്ഞു"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> എന്നയാളില്‍ നിന്നുള്ള സന്ദേശം"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"നിങ്ങൾ ഒരു സന്ദേശമയച്ചു"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"അയയ്‌ക്കുന്നു…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"അയച്ചില്ല. വീണ്ടും ശ്രമിക്കാൻ സ്‌പർശിക്കുക."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"അയച്ചില്ല. വീണ്ടും ശ്രമിക്കുന്നു..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"വീണ്ടും അയയ്ക്കുക അല്ലെങ്കിൽ ഇല്ലാതാക്കുക"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"അടിയന്തര സേവനങ്ങളിലേക്ക് വോയ്‌സ് കോൾ ചെയ്യുക. ഇപ്പോൾ നിങ്ങളുടെ ടെക്‌സ്‌റ്റ് സന്ദേശം കൈമാറാനായില്ല."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"പരാജയപ്പെട്ടു"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ഡൗൺലോഡുചെയ്യാനുള്ള പുതിയ MMS സന്ദേശം"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"പുതിയ MMS സന്ദേശം"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ഡൗൺലോഡുചെയ്യാനായില്ല"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"വീണ്ടും ശ്രമിക്കാൻ സ്‌പർശിക്കുക"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ഡൗൺലോഡുചെയ്യാൻ സ്‌പർശിക്കുക"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ഡൗൺലോഡുചെയ്യുക അല്ലെങ്കിൽ ഇല്ലാതാക്കുക"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ഡൗൺലോഡുചെയ്യുന്നു…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"സന്ദേശം കാലഹരണപ്പെട്ടു അല്ലെങ്കിൽ ലഭ്യമല്ല"</string>
+ <string name="mms_info" msgid="3402311750134118165">"വലുപ്പം: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, കാലഹരണപ്പെടുന്നത്: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"അയയ്‌ക്കാനാവില്ല. സ്വീകർത്താവ് അസാധുവാണ്."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"സേവനം നെറ്റ്‌വർക്കിൽ സജീവമാക്കിയിട്ടില്ല"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"നെറ്റ്‌വർക്ക് പ്രശ്‌നം കാരണം അയ‌യ്‌ക്കാനായില്ല"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"സന്ദേശം കാലഹരണപ്പെട്ടു അല്ലെങ്കിൽ ലഭ്യമല്ല"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(വിഷയമൊന്നുമില്ല)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"അയച്ചയാൾ അജ്ഞാതനാണ്"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ഡെലിവർ ചെയ്‌തു"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> എന്നയാളിൽ നിന്നുള്ള <xliff:g id="SUBJECT">%1$s</xliff:g> എന്ന സന്ദേശം ഡൗൺലോഡുചെയ്യാനായില്ല."</string>
+ <string name="low_memory" msgid="5300743415198486619">"മെമ്മറി കുറവായതിനാൽ ഡാറ്റാബേസ് പ്രവർത്തനം പൂർത്തിയാക്കാനായില്ല"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"സന്ദേശം അയച്ചില്ല"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"സന്ദേശമയയ്ക്കലിൽ ചില സന്ദേശങ്ങൾ അയച്ചിട്ടില്ല"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> സംഭാഷണങ്ങളിൽ <xliff:g id="MESSAGES_1">%d</xliff:g> സന്ദേശങ്ങൾ</item>
+ <item quantity="one">ഒരു സംഭാഷണത്തിൽ <xliff:g id="MESSAGES_0">%d</xliff:g> സന്ദേശങ്ങൾ</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"സന്ദേശം ഡൗൺലോഡുചെയ്‌തില്ല"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"സന്ദേശമയയ്ക്കലിൽ ചില സന്ദേശങ്ങൾ ഡൗൺലോഡ് ചെയ്തിട്ടില്ല"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> സംഭാഷണങ്ങളിൽ <xliff:g id="MESSAGES_1">%d</xliff:g> സന്ദേശങ്ങൾ</item>
+ <item quantity="one">ഒരു സംഭാഷണത്തിൽ <xliff:g id="MESSAGES_0">%d</xliff:g> സന്ദേശങ്ങൾ</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> എന്ന നമ്പറിലേക്ക് സന്ദേശം അയച്ചില്ല"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"അടിയന്തര സേവനങ്ങളിലേക്ക് വോയ്‌സ് കോൾ ചെയ്യുക. ഇപ്പോൾ നിങ്ങളുടെ ടെക്‌സ്‌റ്റ് സന്ദേശം <xliff:g id="NUMBER">%1$s</xliff:g> എന്ന നമ്പറിലേക്ക് കൈമാറാനായില്ല."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> പുതിയ സന്ദേശങ്ങൾ</item>
+ <item quantity="one">പുതിയ സന്ദേശം</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ആരംഭിക്കുക"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ക്യാമറ ലഭ്യമല്ല"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ക്യാമറ ലഭ്യമല്ല"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"വീഡിയോ എടുക്കൽ ലഭ്യമല്ല"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"മീഡിയ സംരക്ഷിക്കാനാവില്ല"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ചിത്രമെടുക്കാനാവില്ല"</string>
+ <string name="back" msgid="1477626055115561645">"മടങ്ങുക"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"ആർക്കൈവുചെയ്‌തവ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"ഇല്ലാതാക്കുക"</string>
+ <string name="action_archive" msgid="5437034800324083170">"ആര്‍ക്കൈവുചെയ്യുക"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ആർക്കൈവുചെയ്‌തത് മാറ്റുക"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"അറിയിപ്പുകൾ ഓഫാക്കുക"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"അറിയിപ്പുകൾ ഓണാക്കുക"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"കോൺടാക്റ്റ് ചേർക്കുക"</string>
+ <string name="action_download" msgid="7786338136368564146">"ഡൗൺലോഡ്"</string>
+ <string name="action_send" msgid="377635240181672039">"അയയ്‌ക്കുക"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"ഇല്ലാതാക്കുക"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ഈ സന്ദേശം ഇല്ലാതാക്കണോ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ഈ പ്രവർത്തനം പഴയപടിയാക്കാനാകില്ല."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"ഇല്ലാതാക്കുക"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">ഈ സംഭാഷണങ്ങൾ ഇല്ലാതാക്കണോ?</item>
+ <item quantity="one">ഈ സംഭാഷണം ഇല്ലാതാക്കണോ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"ഇല്ലാതാക്കുക"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"റദ്ദാക്കുക"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"സ്വീകര്‍ത്താവ്"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"ഒന്നിലധികം ചിത്രങ്ങൾ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"തിരഞ്ഞെടുത്തവ സ്ഥിരീകരിക്കുക"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"<xliff:g id="COUNT">%d</xliff:g> എണ്ണം കൂടി"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ഓഡിയോ റെക്കോർഡ് ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ഓഡിയോ പ്ലേ ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ഓഡിയോ സംരക്ഷിക്കാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"സ്‌പർശിച്ച് പിടിക്കുക"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ചിത്രം"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ഓഡിയോ ക്ലിപ്പ്"</string>
+ <string name="notification_video" msgid="4331423498662606204">"വീഡിയോ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"കോൺടാക്‌റ്റ് കാർഡ്"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ഡൗൺലോഡുചെയ്യുക"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS വഴി മറുപടി നൽകുക"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS വഴി മറുപടി നൽകുക"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"മറുപടി നൽകുക"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> പങ്കെടുക്കുന്നവർ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> പങ്കെടുക്കുന്നയാൾ</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ഞാന്‍"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"കോൺടാക്‌റ്റ് തടഞ്ഞ് ആർക്കൈവുചെയ്‌തു"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"കോൺടാക്റ്റിനെ തടഞ്ഞതും ആർക്കൈവ് ചെയ്‌തതും മാറ്റി"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> എണ്ണം ആർക്കൈവുചെ‌യ്‌തു"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> എണ്ണം ആർക്കൈവുചെയ്‌തത് മാറ്റി"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"അറിയിപ്പുകൾ ഓഫാക്കി"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"അറിയിപ്പുകൾ ഓണാക്കി"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"എല്ലാം സജ്ജീകരിച്ചു. വീണ്ടും അയയ്‌ക്കാൻ സ്‌‌‌പർശിക്കുക."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"സ്ഥിര SMS ആപ്പായി സന്ദേശമയയ്ക്കൽ സജ്ജീകരിച്ചു."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">അറ്റാച്ച്‌മെന്റുകൾ ഉപേക്ഷിക്കുക</item>
+ <item quantity="one">അറ്റാച്ച്‌മെന്റ് ഉപേക്ഷിക്കുക</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ഓഡിയോ അറ്റാച്ചുമെന്റ്"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ഓഡിയോ അറ്റാച്ചുമെന്റ് പ്ലേ ചെയ്യുക"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"താൽക്കാലികമായി നിർത്തുക"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> എന്നയാളിൽ നിന്നുള്ള സന്ദേശം : <xliff:g id="MESSAGE">%s</xliff:g> ."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> എന്നയാളിൽ നിന്നുള്ള സന്ദേശം പരാജയപ്പെട്ടു: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> എന്നയാളിൽ നിന്നുള്ള സന്ദേശം: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശം അയച്ചില്ല: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശം അയയ്‌ക്കുന്നു: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> എന്നതിലേക്കുള്ള സന്ദേശം പരാജയപ്പെട്ടു: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> എന്നതിലേക്കുള്ള സന്ദേശം: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> എന്നയാളിൽ നിന്നുള്ള സന്ദേശം പരാജയപ്പെട്ടു: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> എന്നയാളിൽ നിന്നുള്ള സന്ദേശം: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശം അയച്ചില്ല: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശം അയയ്‌ക്കുന്നു: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> എന്നതിലേക്കുള്ള സന്ദേശം പരാജയപ്പെട്ടു: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> എന്നതിലേക്കുള്ള സന്ദേശം: <xliff:g id="MESSAGE">%s</xliff:g>. സമയം: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"സന്ദേശം പരാജയപ്പെട്ടു. വീണ്ടും ശ്രമിക്കുന്നതിന് സ്‌പർശിക്കുക."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> എന്നിവരുമായുള്ള സംഭാഷണം"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"വിഷയം ഇല്ലാതാക്കുക"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"വീഡിയോ എടുക്കുക"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ഒരു സ്റ്റിൽ ചിത്രം എടുക്കുക"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ചിത്രം എടുക്കുക"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"വീഡിയോ റെക്കോർഡുചെയ്യാൻ ആരംഭിക്കുക"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"പൂർണ്ണസ്ക്രീൻ ക്യാമറയിലേക്ക് മാറുക"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"മുന്നിലെയും പിന്നിലെയും ക്യാമറയ്‌ക്കിടയിൽ മാറുക"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"റെക്കോർഡുചെയ്യൽ നിർത്തി, വീഡിയോ അറ്റാച്ചുചെയ്യുക"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"വീഡിയോ റെക്കോർഡുചെയ്യൽ നിർത്തുക"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"സന്ദേശമയയ്ക്കൽ ഫോട്ടോകൾ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ഫോട്ടോകൾ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ഫോട്ടോ \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> വീഡിയോകൾ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> വീഡിയോ \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> അറ്റാച്ചുമെന്റുകൾ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> അറ്റാച്ച്‌മെന്റ് \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ആൽബത്തിലേക്ക് സംരക്ഷിച്ചു</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> അറ്റാച്ചുമെന്റുകൾ \"ഡൗൺലോഡുകളി\"ലേക്ക് സംരക്ഷിച്ചു </item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> അറ്റാച്ചുമെന്റ് \"ഡൗൺലോഡുകളി\"ലേക്ക് സംരക്ഷിച്ചു</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> അറ്റാച്ച്‌മെന്റുകൾ സംരക്ഷിച്ചു</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> അറ്റാച്ച്‌മെന്റ് സംരക്ഷിച്ചു</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g>അറ്റാച്ച്‌മെന്റുകൾ സംരക്ഷിക്കാനായില്ല</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> അറ്റാച്ച്‌മെന്റ് സംരക്ഷിക്കാനായില്ല</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS അറ്റാച്ചുമെന്റ് സംരക്ഷിച്ചു"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ക്രമീകരണങ്ങൾ"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"ആർക്കൈവുചെയ്‌തവ"</string>
+ <string name="action_close" msgid="1840519376200478419">"അടയ്‌ക്കുക"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"വിപുലം"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ഡീബഗ്"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"അറിയിപ്പുകൾ"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ശബ്‌ദം"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"നിശബ്‌ദം"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"വൈബ്രേറ്റുചെയ്യുക"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"തടഞ്ഞിരിക്കുന്നു"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS ഡെലിവറി റിപ്പോർട്ടുകൾ"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"അയയ്‌ക്കുന്ന ഓരോ SMS-നും ഒരു ഡെലിവറി റിപ്പോർട്ട് അഭ്യർത്ഥിക്കുക"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"സ്വയമേവ വീണ്ടെടുക്കുക"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS സന്ദേശങ്ങൾ സ്വയമേവ ലഭ്യമാക്കുക"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"റോമിംഗിൽ ലഭ്യമാക്കുക"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"റോമിംഗിലായിരിക്കുമ്പോൾ സ്വയമേ MMS വീണ്ടെടുക്കുന്നു"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"ഗ്രൂപ്പ് സന്ദേശമയയ്‌ക്കൽ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ഒന്നിലധികം സ്വീകർത്താക്കൾ ഉള്ളപ്പോൾ ഒരൊറ്റ സന്ദേശം അയയ്‌ക്കാൻ MMS ഉപയോഗിക്കുക."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"സ്ഥിര SMS അപ്ലിക്കേഷൻ"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"സ്ഥിര SMS അപ്ലിക്കേഷൻ"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"നിങ്ങളുടെ ഫോൺ നമ്പർ"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"അജ്ഞാതം"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"ഔട്ട്‌ഗോയിംഗ് സന്ദേശ ശബ്‌ദങ്ങൾ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS നൽകുക"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ബാഹ്യ സംഭരണ ഫയലിലേക്ക് സ്വീകരിച്ച SMS റോ ഡാറ്റ നൽകുക"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS നൽകുക"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ബാഹ്യ സംഭരണ ഫയലിലേക്ക് സ്വീകരിച്ച MMS റോ ഡാറ്റ നൽകുക"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"വയർലെസ് അലേർട്ടുകൾ"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"സന്ദേശ ഓ‌പ്ഷനുകൾ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ടെക്‌‌സ്‌റ്റ് പകർത്തുക"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"വിശദാംശങ്ങൾ കാണുക"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"ഇല്ലാതാക്കുക"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"കൈമാറുക"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"സന്ദേശ വിശദാംശങ്ങൾ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"തരം: "</string>
+ <string name="text_message" msgid="7415419755252205721">"ടെക്‌സ്റ്റ് സന്ദേശം"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"മൾട്ടിമീഡിയ സന്ദേശം"</string>
+ <string name="from_label" msgid="1947831848146564875">"അയച്ചയാൾ: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"സ്വീകർത്താവ്: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"അയച്ചത്: "</string>
+ <string name="received_label" msgid="4442494712757995203">"ലഭിച്ചവ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"വിഷയം: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"വലുപ്പം: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"പ്രധാനപ്പെട്ടവ: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"ഉയർന്നത്"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"സാധാരണ"</string>
+ <string name="priority_low" msgid="7398724779026801851">"കുറഞ്ഞവ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"അയച്ചയാളിന്റെ വിലാസം മറച്ചിരിക്കുന്നു"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"അറ്റാച്ചുമെന്റുകൾ ലോഡുചെയ്യുന്ന സമയത്ത് സന്ദേശം അയയ്‌ക്കാനാവില്ല."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"അറ്റാച്ചുമെന്റ് ലോഡുചെയ്യാനാവില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"നെറ്റ്‌വർക്ക് തയ്യാറായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ടെക്‌സ്റ്റ് ഇല്ലാതാക്കുക"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ടെക്സ്റ്റും നമ്പറുകളും നൽകുന്നതിനിടയിൽ മാറുക"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"കൂടുതൽ പങ്കാളികളെ ചേർക്കുക"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"പങ്കെടുക്കുന്നവരെ സ്ഥിരീകരിക്കുക"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"പുതിയ സംഭാഷണം ആരംഭിക്കുക"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ഈ ഇനം തിരഞ്ഞെടുക്കുക"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"വീഡിയോ പ്ലേചെയ്യുക"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ആളുകളും ഓപ്‌ഷനുകളും"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ഡീബഗ്"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ആളുകളും ഓപ്‌ഷനുകളും"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"പൊതുവായത്"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ഈ സംഭാഷണത്തിലെ ആളുകൾ"</string>
+ <string name="action_call" msgid="6596167921517350362">"ഒരു കോൾ ചെയ്യുക"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"സന്ദേശം അയയ്ക്കുക"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g> -ൽ നിന്ന്&lt;/small&gt; സന്ദേശമയയ്‌ക്കുക"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">ഫോട്ടോകൾ അയയ്‌ക്കുക</item>
+ <item quantity="one">ഫോട്ടോ അയയ്ക്കുക</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">ഓഡിയോകൾ അയയ്‌ക്കുക</item>
+ <item quantity="one">ഓഡിയോ അയയ്ക്കുക</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">വീഡിയോകൾ അയയ്‌ക്കുക</item>
+ <item quantity="one">വീഡിയോ അയയ്‌ക്കുക</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">കോൺടാക്റ്റ് കാർഡുകൾ അയയ്‌ക്കുക</item>
+ <item quantity="one">കോൺടാക്റ്റ് കാർഡ് അയയ്ക്കുക</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">അറ്റാച്ച്‌മെന്റുകൾ അയയ്ക്കുക</item>
+ <item quantity="one">അറ്റാച്ച്‌മെന്റ് അയയ്ക്കുക</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> അറ്റാച്ച്‌മെന്റുകൾ അയയ്‌ക്കാൻ തയ്യാറാണ്</item>
+ <item quantity="one">ഒരു അറ്റാച്ച്‌മെന്റ് അയയ്‌ക്കാൻ തയ്യാറാണ്</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ഫീഡ്‍ബാക്ക് അയയ്ക്കുക"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store-ൽ കാണുക"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"പതിപ്പ് വിവരം"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"പതിപ്പ് %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ഓപ്പൺ സോഴ്‌സ് ലൈസൻസുകൾ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"അറിയിപ്പുകൾ"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"അറ്റാച്ചുമെന്റ് പരിധിയെത്തി"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"അറ്റാച്ചുമെന്റ് ലോഡുചെയ്യുന്നത് പരാജയപ്പെട്ടു."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"കോൺടാക്റ്റിലേക്ക് ചേർക്കണോ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"കോൺടാക്‌റ്റ് ചേർക്കുക"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"വിഷയം"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"വിഷയം: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"കോൺടാക്‌റ്റ് കാർഡ് ലോഡുചെയ്യുന്നു"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"കോൺടാക്‌റ്റ് കാർഡ് ലോഡുചെയ്യാനായില്ല"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"കോൺടാക്റ്റ് കാർഡ് കാണുക"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> കോൺടാക്റ്റുകൾ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> കോൺടാക്റ്റ്</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"കോൺ‌ടാക്‌റ്റ് കാർഡുകൾ"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"ജന്മദിനം"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"കുറിപ്പുകള്‍"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"സന്ദേശം കൈമാറൽ"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"മറുപടി നൽകുക"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1 ചെയ്യുക"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"അയയ്ക്കുന്നതിന്, സ്ഥിര SMS ആപ്പ് ആയി സന്ദേശമയയ്ക്കൽ സജ്ജമാക്കുക"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"സ്ഥിര SMS ആപ്പ് ആയി സന്ദേശമയയ്ക്കൽ സജ്ജമാക്കുക"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"മാറ്റുക"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"സന്ദേശങ്ങൾ സ്വീകരിക്കുന്നതിന്, സ്ഥിര SMS ആപ്പ് ആയി സന്ദേശമയയ്ക്കൽ സജ്ജമാക്കുക"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS സന്ദേശങ്ങൾ അയയ്‌ക്കാൻ പ്രസ്താവിത SIM ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ഉപകരണത്തിന്റെ ഉടമ ഈ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നില്ല."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ശരി"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"ഒരു സംഭാഷണത്തിൽ നിരവധി പേർ പങ്കെടുക്കുന്നു"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">അസാധുവായ കോൺടാക്റ്റുകൾ</item>
+ <item quantity="one">അസാധുവായ കോൺടാക്‌റ്റ്</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"ക്യാമറ ചിത്രം ലോഡുചെയ്യാനായില്ല"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"നിങ്ങൾ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ഡ്രാഫ്റ്റ്"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"ഒരു പുതിയ സംഭാഷണം ആരംഭിക്കുമ്പോൾ, ഇത് ഇവിടെ ലിസ്‌റ്റുചെയ്‌തിരിക്കുന്നത് നിങ്ങൾ കാണും"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"ആർക്കൈവുചെയ്‌ത സംഭാഷണങ്ങൾ ഇവിടെ ദൃശ്യമാകും"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"സംഭാഷണങ്ങൾ ലോഡുചെയ്യുന്നു…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ചിത്രം"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ഓഡിയോ ക്ലിപ്പ്"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"വീഡിയോ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"കോൺടാക്‌റ്റ് കാർഡ്"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"പഴയപടിയാക്കുക"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"വീണ്ടും ശ്രമിക്കുക"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"ഒരു പുതിയ സന്ദേശം ആരംഭിക്കാൻ കോൺടാക്‌റ്റ് പേരോ ഫോൺ നമ്പറോ നൽകുക"</string>
+ <string name="action_block" msgid="9032076625645190136">"തടയുക"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> എന്നയാളെ തടയുക"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> എന്നയാളെ തടഞ്ഞത് മാറ്റുക"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> തടയണോ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ഈ നമ്പറിൽ നിന്ന് സന്ദേശങ്ങൾ തുടർന്നും ലഭിക്കുമെങ്കിലും ഇനി അറിയിപ്പൊന്നും ലഭിക്കില്ല. ഈ സംഭാഷണം ആർക്കൈവുചെയ്യും."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"തടഞ്ഞ കോൺടാക്‌റ്റുകൾ"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"തടഞ്ഞത് മാറ്റുക"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"തടഞ്ഞ കോൺടാക്‌റ്റുകൾ"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"പ്രമാണ ലൈബ്രറിയിൽ നിന്ന് ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
+ <string name="sending_message" msgid="6363584950085384929">"സന്ദേശം അയക്കുന്നു"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"സന്ദേശം അയച്ചു"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"സെല്ലുലാർ ഡാറ്റ ഓഫാക്കിയിരിക്കുന്നു. നിങ്ങളുടെ ക്രമീകരണങ്ങൾ പരിശോധിക്കുക."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"വിമാന മോഡിൽ സന്ദേശങ്ങൾ അയയ്‌ക്കാനാവില്ല"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"സന്ദേശം അയയ്ക്കാനായില്ല"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"സന്ദേശം ഡൗൺലോഡുചെയ്‌തു"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"സെല്ലുലാർ ഡാറ്റ ഓഫാണ്. നിങ്ങളുടെ ക്രമീകരണം പരിശോധിക്കുക."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"വിമാന മോഡിൽ സന്ദേശങ്ങൾ ഡൗൺലോഡുചെയ്യാനാവില്ല"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"സന്ദേശം ഡൗൺലോഡുചെയ്യാനായില്ല"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"പൂജ്യം"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ഒന്ന്"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"രണ്ട്"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"മൂന്ന്"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"നാല്"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"അഞ്ച്"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ആറ്"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ഏഴ്"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"എട്ട്"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ഒൻപത്"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> എന്നതിലൂടെ സന്ദേശമയയ്‌ക്കാനായില്ല, പിശക് <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"അജ്ഞാത കാരിയറിലൂടെ സന്ദേശമയയ്‌ക്കാനായില്ല, പിശക് <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"കൈമാറിയത്: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"സന്ദേശം അയച്ചില്ല: നെറ്റ്‌വർക്കിൽ സേവനം സജീവമാക്കിയിട്ടില്ല"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"സന്ദേശം അയച്ചില്ല: ലക്ഷ്യസ്ഥാന വിലാസം അസാധുവാണ്"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"സന്ദേശം അയച്ചില്ല: സന്ദേശം അസാധുവാണ്"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"സന്ദേശം അയച്ചില്ല: ഉള്ളടക്കം പിന്തുണയ്ക്കാത്തതാണ്"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"സന്ദേശം അയച്ചില്ല: സന്ദേശം പിന്തുണയ്ക്കാത്തതാണ്"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"സന്ദേശം അയച്ചില്ല: വളരെ വലുതാണ്"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"പുതിയ സന്ദേശം"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"കാണുക"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"ചിത്രം"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"അനുയോജ്യമായൊരു അപ്ലിക്കേഷൻ കണ്ടെത്താനായില്ല"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"സ്വീകർത്താവിനെ നീക്കംചെയ്യുക"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"പുതിയ സന്ദേശം"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"റദ്ദാക്കുക"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ആക്‌സസ്സ്‌പോയിന്റ് എഡിറ്റുചെയ്യൂ"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"സജ്ജീകരിച്ചിട്ടില്ല"</string>
+ <string name="apn_name" msgid="1572691851070894985">"പേര്"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS പ്രോക്‌സി"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS പോർട്ട്"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN തരം"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN ഇല്ലാതാക്കുക"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"പുതിയ APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"സംരക്ഷിക്കുക"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"നിരസിക്കുക"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"പേരിന്റെ ഫീൽഡ് ശൂന്യമായിരിക്കരുത്."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ശൂന്യമായിരിക്കരുത്."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ഫീൽഡിൽ 3 സംഖ്യകൾ ഉണ്ടായിരിക്കണം."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ഫീൽഡിൽ 2 അല്ലെങ്കിൽ 3 സംഖ്യകൾ ഉണ്ടായിരിക്കണം."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"സ്ഥിര APN ക്രമീകരണങ്ങൾ പുനഃസ്ഥാപിക്കുന്നു"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"സ്ഥിരമായതിലേക്ക് പുനഃസജ്ജമാക്കുക"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"സ്ഥിര APN ക്രമീകരണങ്ങൾ പുനഃസജ്ജീകരിക്കൽ പൂർത്തിയാക്കി."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"പേരില്ലാത്തത്"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ആക്‌സസ്സ് പോയിന്റ് പേരുകൾ"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-കൾ"</string>
+ <string name="menu_new" msgid="8286285392706532511">"പുതിയ APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ആക്‌സസ്സ് പോയിന്റ് നെയിം ക്രമീകരണങ്ങൾ ഈ ഉപയോക്താവിനായി ലഭ്യമല്ല"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ക്ലിപ്പ്‌ബോർഡിലേക്ക് പകർത്തണോ?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"പകര്‍ത്തുക"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> എന്നതിലേക്ക്"</string>
+ <string name="general_settings" msgid="5409336577057897710">"പൊതുവായത്"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"വിപുലമായത്"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"പൊതു ക്രമീകരണങ്ങൾ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"വിപുലമായ ക്രമീകരണങ്ങൾ"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"എല്ലാ സ്വീകർത്താക്കൾക്കും വെവ്വേറെ SMS സന്ദേശങ്ങൾ അയയ്‌ക്കുക. മറുപടികളെല്ലാം നിങ്ങൾക്കുമാത്രമേ ലഭിക്കൂ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"എല്ലാ സ്വീകർത്താക്കൾക്കും ഒരൊറ്റ MMS അയയ്‌ക്കുക"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"അജ്ഞാത നമ്പര്‍"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"പുതിയ സന്ദേശം"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"പുതിയ സന്ദേശം."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM സെലക്‌ടർ"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> തിരഞ്ഞെടുത്തു, SIM സെലക്‌ടർ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"വിഷയം എഡിറ്റുചെയ്യുക"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM തിരഞ്ഞെടുക്കുക അല്ലെങ്കിൽ വിഷയം എഡിറ്റുചെയ്യുക"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ഓഡിയോ റെക്കോർഡുചെയ്യാൻ സ്‌പർശിച്ച് പിടിക്കുക"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"പുതിയ സംഭാഷണം ആരംഭിക്കുക"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"സന്ദേശമയയ്‌ക്കൽ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"സന്ദേശമയയ്ക്കൽ ലിസ്റ്റ്"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"സന്ദേശമയയ്‌ക്കൽ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"പുതിയ സന്ദേശം"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"സംഭാഷണ ലിസ്റ്റ്"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"സംഭാഷണങ്ങൾ ലോഡുചെയ്യുന്നു"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"സന്ദേശങ്ങൾ ലോഡുചെയ്യുന്നു"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"കൂടുതൽ സംഭാഷണങ്ങൾ കാണുക"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"കൂടുതൽ സന്ദേശങ്ങൾ കാണുക"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"സംഭാഷണം ഇല്ലാതാക്കി"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"സംഭാഷണം ഇല്ലാതാക്കി. മറ്റൊരു സന്ദേശമയയ്ക്കൽ സംഭാഷണം കാണിക്കുന്നതിന് സ്പർശിക്കുക"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"തടഞ്ഞു"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"തടഞ്ഞത് മാറ്റി"</string>
+ <string name="db_full" msgid="8459265782521418031">"സംഭരണത്തിനുള്ള ഇടം കുറവാണ്. ചില വിവരം നഷ്‌ടപ്പെട്ടേക്കാം."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"അറ്റാച്ചുമെന്റുകൾ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"തിരഞ്ഞെടുത്തവ സ്ഥിരീകരിക്കുക"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> എണ്ണം തിരഞ്ഞെടുത്തു"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"ഒന്നോ അതിൽ കൂടുതലോ അറ്റാച്ചുമെന്റുകൾ നീക്കംചെയ്‌തുകൊണ്ട് വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"നിങ്ങളുടെ സന്ദേശം അയയ്‌ക്കാൻ ശ്രമിക്കാമെങ്കിലും ഒന്നോ അതിലധികമോ അറ്റാച്ചുമെന്റുകൾ നീക്കംചെയ്യാതെ അത് ഡെലിവർ ചെയ്തേക്കില്ല."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"നിങ്ങൾക്ക് ഒരു സന്ദേശത്തിന് ഒരു വീഡിയോ മാത്രമേ അയയ്‌ക്കാനാകൂ. കൂടുതൽ വീഡിയോകൾ നീക്കംചെയ്‌ത് വീണ്ടും ശ്രമിക്കുക."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"അറ്റാച്ച്മെന്റ് ലോഡുചെയ്യുന്നതിൽ സന്ദേശമയയ്‌ക്കൽ പരാജയപ്പെട്ടു."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ഏതുവിധേനയും അയയ്‌ക്കുക"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"സംഭാഷണം ആരംഭിക്കാനായില്ല"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"തിരഞ്ഞെടുത്തത്, <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-mn-rMN/arrays.xml b/res/values-mn-rMN/arrays.xml
new file mode 100644
index 0000000..b47063a
--- /dev/null
+++ b/res/values-mn-rMN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"гарчиггүй"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Тийм"</item>
+ <item msgid="6049132459802288033">"Үгүй"</item>
+ <item msgid="3084376867445867895">"Тийм"</item>
+ <item msgid="3155097332660174689">"Хэхэ"</item>
+ <item msgid="2611328818571146775">"Баярлалаа"</item>
+ <item msgid="4881335087096496747">"Би зөвшөөрч байна"</item>
+ <item msgid="2422296858597420738">"Янзтай"</item>
+ <item msgid="4805581752819452687">"Очиж байна"</item>
+ <item msgid="4746700499431366214">"За, би эргээд холбоо баръя"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..fb9106b
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Зурвас"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Зурвас"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Харилцааг сонгох"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Тохиргоо"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Зурвас Илгээх"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Хавсралт нэмэх"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Тусламж"</string>
+ <string name="welcome" msgid="2857560951820802321">"Тавтай морилно уу"</string>
+ <string name="skip" msgid="7238879696319945853">"Алгасах"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Дараах &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Дараах"</string>
+ <string name="exit" msgid="1905187380359981199">"Гарах"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Тохиргоо &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Тохиргоо"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaging-д SMS, Утас, Харилцагч хаягийн зөвшөөрөл шаардлагатай."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Та зөвшөөрлийг Тохиргоо &gt; Aпп &gt; Зурвас &gt; Зөвшөөрөл хэсэгт өөрчилж болно."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Та зөвшөөрлийг Тохиргоо, Апп, Messaging, Зөвшөөрөл хэсэгт өөрчилж болно."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"БАЙНГЫН ХАРИЛЦАГЧ"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Бүх харилцагч"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> руу илгээх"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Зураг буюу видео авах"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Энэ төхөөрөмжөөс зураг сонгоно уу"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Аудио бичих"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Зураг сонгох"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Медиаг сонгосон байна."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Медиаг сонгоогүй байна."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> сонгогдсон"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"зураг <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" msgid="8142175029053370662">"зураг"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Аудио бичих"</string>
+ <string name="action_share" msgid="2143483844803153871">"Хуваалцах"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Дөнгөж сая"</string>
+ <string name="posted_now" msgid="867560789350406701">"Одоо"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> минут</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> минут</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> цаг</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> цаг</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> өдөр</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> өдөр</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> долоо хоног</item>
+ <item quantity="one"> долоо хоног</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> сар</item>
+ <item quantity="one"> нэг сар</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> жил</item>
+ <item quantity="one"> нэг жил</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Ангилал 0 зурвас"</string>
+ <string name="save" msgid="5081141452059463572">"Хадгалах"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Төхөөрөмжийн багтаамж бага байна. Messaging нь багтаамж үүсгэхийн тулд хуучин зурвасыг автоматаар устгана."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Сангийн хэмжээ дутагдаж байна"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Мessaging нь таны төхөөрөмжид хангалттай багтаамж гарах хүртэл зурвас илгээх эсвэл хүлээн авах боломжгүй."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS сан бага байна. Та зурвасуудаас устгах шаардлагатай бололтой."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Утасны дугаараа баталгаажуулна уу"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Нэг удаагийн алхам нь Messaging-г таны бүлэг зурвасыг асуудалгүйгээр илгээнэ."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Утасны дугаар"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Медиатай бүх зурвасуудыг устгах"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g>-с өмнөх зурвасуудыг устгах"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g>-с өмнөх зурвасуудыг автоматаар устгах"</string>
+ <string name="ignore" msgid="7063392681130898793">"Үл тоох"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Медиа бүхий бүх зурвасыг устгах уу?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g>-с өмнөх зурвасуудыг устгах уу?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g>-с өмнөх зурвасуудыг устгаж, автомат устгалтыг асаах уу?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> гэж хэлсэн"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Та хэлсэн байна"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g>-ээс ирсэн мессеж"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Та мессеж илгээсэн байна"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Илгээж байна ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Илгээгээгүй. Дахин оролдохын тулд хүрнэ үү."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Илгээгээгүй. Дахин оролдож байна…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Дахин илгээх буюу устгах"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Яаралтай түргэн тусламж руу залгана уу. Таны мессежийг яг одоо явуулах боломжгүй байна."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Амжилтгүй"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Татаж авах шинэ MMS мессеж"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Шинэ MMS мессеж"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Татан авч чадсангүй"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Дахин оролдохын тулд хүрнэ үү"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Татаж авахын тулд хүрнэ үү"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Татаж авах буюу устгах"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Татаж авч байна..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Зурвасын хугацаа өнгөрсөн буюу байхгүй"</string>
+ <string name="mms_info" msgid="3402311750134118165">"хэмжээ: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, хугацаа дуусах: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Илгээх боломжгүй. Хүлээн авагч буруу."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Сүлжээнд үйлчилгээг идэвхжүүлээгүй байна."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Сүлжээний асуудлаас шалтгаалан илгээж чадсангүй."</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Зурвасын хугацаа өнгөрсөн буюу байхгүй"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Гарчиггүй)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Тодорхойгүй илгээгч"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Хүргэгдсэн"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g>-с <xliff:g id="SUBJECT">%1$s</xliff:g> зурвасыг татаж чадсангүй."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Санах ой бага байгаагаас шалтгаалан датабаазын үйлдлийг гүйцээж чадсангүй"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Зурвас илгээгдээгүй"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Messaging-н зарим зурвасыг илгээгээгүй"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> Харилцан ярианд <xliff:g id="MESSAGES_1">%d</xliff:g> зурвас байна</item>
+ <item quantity="one"> Нэг харилцан ярианд <xliff:g id="MESSAGES_0">%d</xliff:g> зурвас байна</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"зурвасыг татаж болсонгүй"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Зарим зурвасыг Messaging-д татаагүй"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> Харилцан ярианд <xliff:g id="MESSAGES_1">%d</xliff:g> зурвас байна</item>
+ <item quantity="one"> Нэг харилцан ярианд <xliff:g id="MESSAGES_0">%d</xliff:g> зурвас байна</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> явуулсан мессеж амжилтгүй болсон байна"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Яаралтай түргэн тусламж руу залгана уу. Таны мессежийг <xliff:g id="NUMBER">%1$s</xliff:g> дугаарт яг одоо явуулах боломжгүй байна. "</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> шинэ зурвас</item>
+ <item quantity="one">Шинэ зурвас</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Эхлэх"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Камер ашиглах боломжгүй"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Камер ашиглах боломжгүй"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Видео бичих боломжгүй"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Медиаг хадгалах боломжгүй"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Зураг авч болохгүй байна"</string>
+ <string name="back" msgid="1477626055115561645">"Буцах"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архивлагдсан"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Устгах"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архивлах"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Архиваас гаргах"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Мэдэгдэл унтраах"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Мэдэгдлүүдийг асаах"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Харилцагч нэмэх"</string>
+ <string name="action_download" msgid="7786338136368564146">"Татаж авах"</string>
+ <string name="action_send" msgid="377635240181672039">"Илгээх"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Устгах"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Энэ зурвасыг устгах уу?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Энэ үйлдлийг буцаах боломжгүй."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Устгах"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Эдгээр харилцан яриаг устгах уу?</item>
+ <item quantity="one">Энэ харилцан яриаг устах уу?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Устгах"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Цуцлах"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Хэнд"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Олон зураг сонгох"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Сонголтыг баталгаажуулах"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Аудиог бичих боломжгүй. Дахин оролдоно уу."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Аудиог тоглуулах боломжгүй. Дахин оролдоно уу."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Аудиог хадгалж чадсангүй. Дахин оролдоно уу."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Хүрээд &amp; барих"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Зураг"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудио клип"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Видео"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Харилцагчийн карт"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Татаж авах"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS-р хариулах"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS-ээр хариулах"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Хариулах"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> оролцогч</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> оролцогч</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Би"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Харилцагчийг хориглож &amp; архивласан"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Харилцаа холбоог хориглоогүй &amp; архивлагдаагүй"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> aрхивлагдсан"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> архивлагдаагүй"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Мэдэгдлүүдийг унтраасан"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Мэдэгдлүүдийг асаасан"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Бүгдийг нь тохируулсан. Илгээх-д дахин хүрнэ үү."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Мessaging-г SMS-н үндсэн апп-аар амжилттай тохирууллаа."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Хавсралтыг цуцлах</item>
+ <item quantity="one">Хавсралтыг цуцлах</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Аудио хавсралт"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Аудио хавсралтыг тоглуулах"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Түр зогсоох"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>-аас ирсэн зурвас."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>-с илгээсэн амжилтгүй болсон мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>-с илгээсэн мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>-д илгээгээгүй байгаа мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>-д мессеж илгээж байна: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>-д илгээсэн амжилтгүй болсон мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>-д мессеж илгээх: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>-с илгээсэн амжилтгүй болсон мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>-с илгээсэн мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g>-т зориулсан илгээгээгүй байгаа мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g>-т мессеж илгээж байна: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>-т зориулсан амжилтгүй болсон мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>-т илгээх мессеж: <xliff:g id="MESSAGE">%s</xliff:g>. Цаг: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Амжилтгүй болсон мессеж. Дахин оролдоно уу."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> нартай холбоо тогтоох"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Гарчгийг устгах"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Видео авах"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Хөдөлгөөнгүй зураг авах"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Зураг авах"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Видео бичлэг хийж эхлэх"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Бүтэн дэлгэцийн камер руу сэлгэх"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Урд болон хойд камер хооронд сэлгэх"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Бичлэгийг зогсоож видеог хавсаргах"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Видео бичилтийг зогсоох"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging-н зураг"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> зургийг \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" цомогт хадгаллаа</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> зургийг \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" цомогт хадгаллаа</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> бичлэгийг \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" цомогт хадгаллаа</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> бичлэгийг \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" цомогт хадгаллаа</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> хавсралтыг \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" цомогт хадгаллаа</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> хавсралтыг \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" цомогт хадгаллаа</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> хавсралтыг \"Татаж авсан файл\"-д хадгаллаа</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> хавсралтыг \"Татаж aвсан файл\"-д хадгаллаа</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> хавсралт хадгалсан</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> хавсралт хадгалсан</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> хавсралтыг хадгалж чадсангүй</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> хавсралтыг хадгалж чадсангүй</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS хавсралтыг хадгалсан"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Тохиргоо"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архивлагдсан"</string>
+ <string name="action_close" msgid="1840519376200478419">"Хаах"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Дэлгэрэнгүй"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Дебаг"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Мэдэгдэл"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Дуу"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Чимээгүй"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Чичиргээ"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Хориглогдсон"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS хүргэлтийн репорт"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Өөрийн илгээх SMS бүрт хүргэлтийн репорт хүснэ үү"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Автомат-татагч"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS зурвасуудыг автоматаар татах"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Роумингын автомат-татагч"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Роуминг ашиглаж байх үедээ MMS-ийг автоматаар сэргээж авч байх"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Бүлгэмийн зурвас"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Олон хүлээн авагчид нэг зурвас явуулахдаа MMS ашиглаарай"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Үндсэн SMS програм"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Үндсэн SMS програм"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Таны утасны дугаар"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Тодорхойгүй"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Мессеж илгээх үеийн ая"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS хадгалах"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Хүлээн авсан SMS түүхий датаг гадаад сангийн файлд хадгалах"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS хадгалах"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Dump Хүлээн авсан MMS түүхий датаг гадаад сангийн файлд хадгалах"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Утасгүй сэрэмжлүүлэг"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Зурвасын тохиргоо"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Текстийг хуулах"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Дэлгэрэнгүй үзэх"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Устгах"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Дамжуулах"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Зурвасын мэдээлэл"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Төрөл: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Текст зурвас"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Мультимедиа зурвас"</string>
+ <string name="from_label" msgid="1947831848146564875">"Хэнээс: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Хэнд: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Илгээсэн: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Хүлээн авсан: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Гарчиг: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Хэмжээ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Ач холбогдол: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Өндөр"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Энгийн"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Бага"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Илгээгчийн хаяг нууцлагдсан"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Хавсралтуудыг ачаалах үед зурвас илгээх боломжгүй."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Хавсралтыг ачаалж чадсангүй. Дахин оролдоно уу."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Сүлжээ бэлэн биш байна. Дахин оролдоно уу."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Текстийг устгах"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Текст болон тоо оруулах хооронд сэлгэх"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Өөр оролцогчид нэмэх"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Оролцогчдыг баталгаажуулах"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Шинээр харилцан яриа үүсгэж байна"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Энэ зүйлийг сонгох"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Видео тоглуулах"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Хүмүүс &amp; тохируулга"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Дебаг"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Хүмүүс &amp; тохируулга"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Ерөнхий"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Энэ харилцаан доторх хүмүүс"</string>
+ <string name="action_call" msgid="6596167921517350362">"Дуудлага хийх"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Зурвас илгээх"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Зурвас илгээх&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>-с&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Зураг илгээх</item>
+ <item quantity="one">Зураг илгээх</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Аудио илгээх</item>
+ <item quantity="one">Аудио илгээх</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Видео илгээх</item>
+ <item quantity="one">Видео илгээх</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Харилцагчийн карт илгээх</item>
+ <item quantity="one">Харилцагчийн карт илгээх</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Хавсралт илгээх</item>
+ <item quantity="one">Хавсралт илгээх</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other">Илгээхэд бэлэн <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> хавсралт байна</item>
+ <item quantity="one">Илгээхэд бэлэн нэг хавсралт байна</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Санал хүсэлт илгээх"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play дэлгүүр дээр харах"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Хувилбарын мэдээлэл"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Хувилбар %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Нээлттэй эхийн лиценз"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Мэдэгдэл"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Хавсралтын дээд хэмжээнд хүрсэн байна"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Хавсралтыг татаж авах үйлдэл амжилтгүй болсон байна."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Харилцагчидад нэмэх үү?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Харилцагч нэмэх"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Гарчиг"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Гарчиг: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Харилцагчийн картыг ачаалж байна"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Харилцагчийн картыг дуудаж чадсангүй"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Харилцагчийн картыг харах"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> харилцагч</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> харилцагч</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Харилцагчийн картууд"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Төрсөн өдөр"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Тэмдэглэл"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Зурвасыг дамжуулах"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Хариулах"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS идэвхгүй"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Илгээхийн тулд Messaging-г үндсэн SMS апп болго"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Messaging-г үндсэн SMS апп болгох"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Солих"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Зурвас хүлээн авахын тулд Messaging-г үндсэн SMS апп-аар тохируулаарай"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS зурвас илгээхээр сонгосон ямар ч SIM алга байна"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Энэ апп-г төхөөрөмжийн эзэмшигч зөвшөөрөөгүй."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ТИЙМ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Харилцаанд хэт олон оролцогч байна"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Харилцагчийн хаяг буруу байна</item>
+ <item quantity="one">Харилцагчийн хаяг буруу байна</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Камерийн дүрсийг дуудаж чадсангүй"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Та: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Ноорог"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Шинэ харилцаа эхлүүлмэгц энэ жагсаалтад харагдах болно"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Архивлагдсан харилцаанууд энд харагдана"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Харилцааг ачаалж байна..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Зураг"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудио клип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Видео"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Харилцагчийн карт"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Буцаах"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Дахин оролдох"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Шинэ зурвас эхлүүлэхийн тулд харилцагчийн нэр буюу утасны дугаар оруулна уу"</string>
+ <string name="action_block" msgid="9032076625645190136">"Хориглох"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>-г хориглох"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g>-н хориг авах"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>-г хориглох уу?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Та энэ дугаараас үргэлжлүүлэн зурвас хүлээн авах боловч мэдэгдэл авахгүй. Энэ харилцааг архивлах болно."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Хориглогдсон харилцагчид"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ХОРИГ АВАХ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Хориглогдсон харилцагчид"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Документ сангаас зураг сонгох"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Зурвасыг илгээж байна"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Зурвас илгээгдсэн"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Үүрэн дата унтарсан. Тохиргоог шалгана уу."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Нислэгийн горимд зурвас илгээх боломжгүй"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Зурвасыг илгээж чадсангүй."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Зурвасыг татаж авсан"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Гар утасны интернет холболт идэвхгүй байна. Тохиргоог дахин шалгана уу."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Airplane mode дээр тавьсан байгаа тул мэдээллийг татаж авах боломжгүй байна"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Мэдээллийг татаж авах боломжгүй байна"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Тэг"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Нэг"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Хоёр"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Гурав"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Дөрөв"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Тав"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Зургаа"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Долоо"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Найм"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Ес"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="ERRORCODE">%2$d</xliff:g> алдаа бүхий <xliff:g id="CARRIERNAME">%1$s</xliff:g> мессежийг илгээх боломжгүй"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Үл мэдэгдэх зөөвөрлөгчөөр ирсэн мессежийг илгээх боломжгүй, алдаа <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Зурвас илгээгдээгүй: үйлчилгээ сүлжээн дээр идэвхжээгүй"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Зурвас илгээгдээгүй: чиглэлийн хаяг буруу"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Зурвас илгээгдээгүй: хүчингүй зурвас"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Зурвас илгээгдээгүй: дэмжигдээгүй контент"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Зурвас илгээгдээгүй: дэмжигдээгүй зурвас"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Мессежийг илгээж болохгүй байна: хэтэрхий их хэмжээтэй байна"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Шинэ зурвас"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Харагдац"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Зураг"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Тохирох аппликешныг олж чадсангүй"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Хүлээн авагчийг арилгах"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Шинэ зурвас"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Цуцлах"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Хандалтын цэгийг засах"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Тохируулаагүй"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Нэр"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS прокси"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS порт"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN төрөл"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN устгах"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Шинэ APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Хадгалах"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Устгах"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Нэр оруулах талбар хоосон байж болохгүй."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN хоосон байж болохгүй."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC талбар 3 цифртэй байх ёстой."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC талбар 2 буюу 3 цифртэй байх ёстой."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Үндсэн APN тохиргоог сэргээж байна"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Үндсэн рүү сэргээх"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Үндсэн APN тохиргоог дахин шинэчилж дууслаа."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Гарчиггүй"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Хандалтын Цэгийн Нэрс"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Шинэ APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Энэ хэрэглэгчийн хувьд Хандалтын цэгийн нэрийн тохиргоог ашиглах боломжгүй"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Санах ойд хуулах уу?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Хуулах"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> руу"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Ерөнхий"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Дэлгэрэнгүй"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Ерөнхий тохиргоо"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Дэлгэрэнгүй тохиргоо"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\" <xliff:g id="SIM_NAME">%s</xliff:g> \"SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Бүх хүлээн авагч руу хувийн SMS мессеж илгээх. Зөвхөн та ямар ч хариу авахгүй"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Бүх хүлээн авагч руу нэг MMS илгээх"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Тодорхойгүй дугаар"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Шинэ зурвас"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Шинэ зурвас."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM сонгогч"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> сонгосон, СИМ сонгогч"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Гарчигийг засах"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM-г сонгох эсвэл гарчигийг засах"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Дуут бичлэг хийхийн тулд холбогдох хэсэгт даран түр хүлээнэ үү"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Шинээр харилцан яриа үүсгэх"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Messaging-н жагсаалт"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Шинэ мэдээлэл"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Харилцааны жагсаалт"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Харилцан солилцсон мэдээллийг ачаалж байна"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Зурвасуудыг дуудаж байна"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Харилцан яриаг дэлгэрэнгүй харах"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Илүү олон зурвасуудыг харах"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Харилцаа холбоог устгасан байна"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Харилцан яриа устсан. Messaging-н өөр харилцан яриа харуулахын тулд хүрнэ үү"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Блоклосон байна"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Блокыг арилгасан байна"</string>
+ <string name="db_full" msgid="8459265782521418031">"Таны мэдээлэл хадгалах зай хангалтгүй байна. Зарим мэдээлэл алдагдах магадлалтай байна."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Хавсралтыг сонгох"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Сонголтыг баталгаажуулах"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> сонгосон"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Нэг эсвэл түүнээс дээш тооны хавсралтыг арилгасны дараагаар дахин оролдоно уу."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Та өөрийн мессежийг илгээх оролдлогыг хийж болох боловч нэг эсвэл түүнээс дээг тооны хавсралтыг арилгахаас нааш илгээгдэхгүй байх магадлалтай."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Та нэг мессежээр нэг л видео илгээж болно. Нэмэлт видеонуудыг устгаад, дахин оролдоно уу."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging нь хавсралтыг ачаалж чадсангүй."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Юутай ч илгээх"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Харилцан яриаг эхэлж болохгүй байна"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> сонгогдсон"</string>
+</resources>
diff --git a/res/values-mr-rIN/arrays.xml b/res/values-mr-rIN/arrays.xml
new file mode 100644
index 0000000..73cd4c2
--- /dev/null
+++ b/res/values-mr-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"कोणताही विषय नाही"</item>
+ <item msgid="272485471009191934">"कोणताही विषय नाही"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"होय"</item>
+ <item msgid="6049132459802288033">"नाही"</item>
+ <item msgid="3084376867445867895">"ठीक आहे"</item>
+ <item msgid="3155097332660174689">"हेहे"</item>
+ <item msgid="2611328818571146775">"धन्यवाद"</item>
+ <item msgid="4881335087096496747">"मी सहमत आहे"</item>
+ <item msgid="2422296858597420738">"छान"</item>
+ <item msgid="4805581752819452687">"मी वाटेत आहे"</item>
+ <item msgid="4746700499431366214">"ठीक, नंतर मला आपल्याकडे परत येऊ द्या"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
new file mode 100644
index 0000000..418199c
--- /dev/null
+++ b/res/values-mr-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"संदेशन"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"संदेशन"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"संभाषण निवडा"</string>
+ <string name="action_settings" msgid="1329008122345201684">"सेटिंग्ज"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"संदेश पाठवा"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"एखादे संलग्नक जोडा"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"मदत"</string>
+ <string name="welcome" msgid="2857560951820802321">"सुस्वागतम"</string>
+ <string name="skip" msgid="7238879696319945853">"वगळा"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"पुढील &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"पुढील"</string>
+ <string name="exit" msgid="1905187380359981199">"बाहेर पडा"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"सेटिंग्ज &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"सेटिंग्ज"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"संदेशनला SMS, फोन आणि संपर्कांसाठी परवानगी आवश्यक आहे."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"आपण सेटिंग्ज &gt; अॅप्स &gt; संदेशन &gt; परवानग्या मध्ये परवानग्या बदलू शकता."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"आपण सेटिंग्ज, अॅप्स, संदेशन, परवानग्या मध्ये परवानग्या बदलू शकता."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"वारंवार"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"सर्व संपर्क"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> वर पाठवा"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"चित्रे किंवा व्हिडिओ कॅप्चर करा"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"या डिव्हाइसवरून प्रतिमा निवडा"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ऑडिओ रेकॉर्ड करा"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"फोटो निवडा"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"मीडिया निवडण्यात आला आहे."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"मीडियाची निवड रद्द करण्यात आली आहे."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> निवडले"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"प्रतिमा <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" msgid="8142175029053370662">"प्रतिमा"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ऑडिओ रेकॉर्ड करा"</string>
+ <string name="action_share" msgid="2143483844803153871">"सामायिक करा"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"आत्ताच"</string>
+ <string name="posted_now" msgid="867560789350406701">"आत्ता"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> मिनि</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> मिनिटे</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> तास</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> तास</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> दिवस</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> दिवस</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> आठवडा</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> आठवडे</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> महिना</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> महिने</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> वर्ष</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> वर्षे</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"वर्ग 0 संदेश"</string>
+ <string name="save" msgid="5081141452059463572">"जतन करा"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"डिव्हाइसवर स्थान कमी आहे. स्थान मोकळे करण्‍यासाठी संदेशन जुने संदेश स्वयंचलितपणे हटवेल."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"संचयन स्थान संपत आहे"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"आपल्या डिव्हाइसवर आणखी स्थान उपलब्ध होईपर्यंत संदेशन संदेश पाठवू किंवा प्राप्त करू शकत नाही."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"कमी SMS संचयन. आपल्याला संदेश हटविण्याची आवश्यकता असू शकते."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"आपल्‍या फोन नंबरची पुष्‍टी करा"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"हे एक-वेळ चरण संदेशन आपले गट संदेश योग्यरित्या वितरीत करेल हे सुनिश्चित करेल."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"फोन नंबर"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"मीडिया असलेले सर्व संदेश हटवा"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> पेक्षा अधिक जुने संदेश हटवा"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> पेक्षा जुने संदेश स्वयं हटवा"</string>
+ <string name="ignore" msgid="7063392681130898793">"दुर्लक्ष करा"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"मीडिया असलेले सर्व संदेश हटवायचे?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> पेक्षा जुने संदेश हटवायचे?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> पेक्षा जुने संदेश हटवायचे आणि स्वयं-हटवा चालू करायचे?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> म्हणाले"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"आपण म्हटले"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> कडून संदेश"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"आपण एक संदेश पाठविला"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"पाठवित आहे..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"पाठविले नाही. पुन्हा प्रयत्न करण्यासाठी स्पर्श करा."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"पाठविले नाही. पुन्हा प्रयत्न करत आहे…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"पुन्हा पाठवा किंवा हटवा"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"कृपया आणीबाणी सेवांना एक व्हॉईस कॉल करा. यावेळी आपला मजकूर संदेश वितरीत केला जाऊ शकत नाही."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"अयशस्‍वी झाले"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"डाउनलोड करण्यासाठी नवीन MMS संदेश"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"नवीन MMS संदेश"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"डाउनलोड करू शकलो नाही"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"पुन्हा प्रयत्न करण्यासाठी स्पर्श करा"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"डाउनलोड करण्यासाठी स्पर्श करा"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"डाउनलोड करा किंवा हटवा"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"डाउनलोड करत आहे..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"संदेश कालबाह्य झाला किंवा उपलब्‍ध नाही"</string>
+ <string name="mms_info" msgid="3402311750134118165">"आकार: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, कालबाह्यता: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"पाठवू शकलो नाही. प्राप्तकर्ता वैध नाही."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"नेटवर्कवर सेवा सक्रिय केली नाही"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"नेटवर्क समस्येमुळे पाठवू शकलो नाही"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"संदेश कालबाह्य झाला किंवा उपलब्‍ध नाही"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(विषय नाही)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"अज्ञात प्रेषक"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"वितरित"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> कडील <xliff:g id="SUBJECT">%1$s</xliff:g> संदेश डाउनलोड करू शकलो नाही"</string>
+ <string name="low_memory" msgid="5300743415198486619">"कमी मेमरीमुळे डेटाबेस ऑपरेशन पूर्ण करू शकलो नाही"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"संदेश पाठविला नाही"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"काही संदेश संदेशन मध्ये पाठविले गेले नाहीत"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> संभाषणांमधील <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> संभाषणांमधील <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"संदेश डाउनलोड केला नाही"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"काही संदेश संदेशन मध्ये डाउनलोड झाले नाहीत"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="CONVERSATIONS">%d</xliff:g> संभाषणांमधील <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> संभाषणांमधील <xliff:g id="MESSAGES_1">%d</xliff:g> संदेश</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> वर संदेश पाठविला नाही"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"कृपया आणीबाणी सेवांना एक व्हॉईस कॉल करा. यावेळी आपला मजकूर संदेश <xliff:g id="NUMBER">%1$s</xliff:g> ला वितरीत केला जाऊ शकत नाही."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> नवीन संदेश</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> नवीन संदेश</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"प्रारंभ करा"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"कॅमेरा उपलब्ध नाही"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"कॅमेरा उपलब्ध नाही"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"व्हिडिओ कॅप्चर उपलब्ध नाही"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"मीडिया जतन करू शकत नाही"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"चित्र घेऊ शकत नाही"</string>
+ <string name="back" msgid="1477626055115561645">"परत"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"संग्रहित"</string>
+ <string name="action_delete" msgid="4076795795307486019">"हटवा"</string>
+ <string name="action_archive" msgid="5437034800324083170">"संग्रहण करा"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"संग्रहण रद्द करा"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"सूचना बंद करा"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"सूचना चालू करा"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"संपर्क जोडा"</string>
+ <string name="action_download" msgid="7786338136368564146">"डाउनलोड करा"</string>
+ <string name="action_send" msgid="377635240181672039">"पाठवा"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"हटवा"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"हा संदेश हटवायचा?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ही क्रिया पूर्ववत केली जाणे शक्य नाही."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"हटवा"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">हे संभाषण हटवायचे?</item>
+ <item quantity="other">ही संभाषणे हटवायची?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"हटवा"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"रद्द करा"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"प्रति"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"एकाधिक प्रतिमा निवडा"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"निवडीची पुष्टी करा"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ऑडिओ रेकॉर्ड करू शकत नाही. पुन्हा प्रयत्न करा."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ऑडिओ प्ले करू शकत नाही. पुन्हा प्रयत्न करा."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ऑडिओ जतन करू शकलो नाही. पुन्हा प्रयत्न करा."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"स्पर्श करा आणि धरून ठेवा"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"चित्र"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ऑडिओ क्लिप"</string>
+ <string name="notification_video" msgid="4331423498662606204">"व्हिडिओ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"संपर्क कार्ड"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"डाउनलोड करा"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS द्वारे प्रत्युत्तर द्या"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS द्वारे प्रत्‍युत्तर द्या"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"प्रत्युत्तर द्या"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> सहभागी</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> सहभागी</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"मी"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"संपर्क अवरोधित आणि संग्रहित केला"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"संपर्क अनावरोधित केला आणि संग्रहण करणे रद्द केले"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> संग्रहित केले"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> संग्रहण करणे रद्द केले"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"सूचना बंद"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"सूचना चालू"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"सर्व सेट केले. पुन्हा पाठवा ला स्पर्श करा."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"डीफॉल्ट SMS अॅप म्हणून संदेशन यशस्वीरित्या सेट झाले."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">संलग्नक टाकून द्या</item>
+ <item quantity="other">संलग्नके टाकून द्या</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ऑडिओ संलग्नक"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ऑडिओ संलग्नक प्ले करा"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"विराम द्या"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> कडून संदेश: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> कडील अयशस्‍वी संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> कडील संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> ला न पाठविलेला संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> ला संदेश पाठवित आहे: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> चा अयशस्‍वी संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> चा संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> कडील अयशस्‍वी संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> कडील संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> ला न पाठविलेला संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> ला संदेश पाठवित आहे: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> चा अयशस्‍वी संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> चा संदेश: <xliff:g id="MESSAGE">%s</xliff:g>. वेळ: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"संदेश अयशस्वी. पुन्हा प्रयत्न करण्यासाठी स्पर्श करा."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> सह संभाषण"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"विषय हटवा"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"व्‍हिडिओ कॅप्‍चर करा"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"एक स्थिर प्रतिमा कॅप्च करा"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"चित्र घ्या"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"व्हिडिओ रेकॉर्ड करणे प्रारंभ करा"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"पूर्ण स्क्रीन कॅमेर्‍यावर स्विच करा"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"पुढील आणि मागील कॅमेर्‍यात स्विच करा"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"रेकॉर्डिंग थांबवा आणि व्‍हिडिओ संलग्न करा"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"व्हिडिओ रेकॉर्ड करणे थांबवा"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"संदेशन फोटो"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> फोटो \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" अल्बममध्‍ये जतन केला</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> फोटो \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" अल्बममध्‍ये जतन केले</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> व्हिडिओ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" अल्बममध्‍ये जतन केला</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> व्हिडिओ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" अल्बममध्‍ये जतन केले</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> संलग्नक \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" अल्बममध्‍ये जतन केले</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> संलग्नके \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" अल्बममध्‍ये जतन केली</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नक \"डाउनलोड\" मध्‍ये जतन केले</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नके \"डाउनलोड\" मध्‍ये जतन केली</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नक जतन केले</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नके जतन केली</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नक जतन करणे शक्य झाले नाही</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नके जतन करणे शक्य झाले नाही</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS संलग्नक जतन केले"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"सेटिंग्ज"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"संग्रहित"</string>
+ <string name="action_close" msgid="1840519376200478419">"बंद करा"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"प्रगत"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"डीबग करणे"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"सूचना"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ध्वनी"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"मूक"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"कंपन"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"अवरोधित केले"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS वितरण अहवाल"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"आपण पाठविता त्या प्रत्‍येक SMS साठी वितरण अहवालाची विनंती करा"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"स्‍वयं-पुनर्प्राप्त करा"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS संदेश स्वयंचलितपणे पुनर्प्राप्त करा"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"रोमिंग स्‍वयं-पुनर्प्राप्त करा"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"रोमिंगमध्‍ये असताना स्वयंचलितपणे MMS पुनर्प्राप्त करा"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"गट संदेशन"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"एक संदेश एकाधिक प्राप्तकर्त्यांना पाठविण्‍यासाठी MMS चा वापर करा."</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"डीफॉल्ट SMS अॅप"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"डीफॉल्ट SMS अॅप"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"आपला फोन नंबर"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"अज्ञात"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"पाठविला जाणारा संदेश ध्वनी करतो"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS साचवा"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"प्राप्त केलेला SMS अविश्लेषित डेटा बाह्य संचयन फायलीमध्‍ये साचवा"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS साचवा"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"प्राप्त केलेला MMS अविश्लेषित डेटा बाह्य संचयन फायलीमध्‍ये साचवा"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"वायरलेस अॅलर्ट"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"संदेश पर्याय"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"मजकूर कॉपी करा"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"तपशील पहा"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"हटवा"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"अग्रेषित करा"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"संदेश तपशील"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"प्रकार: "</string>
+ <string name="text_message" msgid="7415419755252205721">"मजकूर संदेश"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"मल्‍टीमीडिया संदेश"</string>
+ <string name="from_label" msgid="1947831848146564875">"प्रेषक: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"प्रति: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"प्रेषित: "</string>
+ <string name="received_label" msgid="4442494712757995203">"प्राप्त झाले: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"विषय: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"आकार: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"प्राधान्य: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"सिम: "</string>
+ <string name="priority_high" msgid="728836357310908368">"उच्च"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"सामान्य"</string>
+ <string name="priority_low" msgid="7398724779026801851">"निम्न"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"सिम <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"लपविलेला प्रेषक पत्ता"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"संलग्नके लोड करताना संदेश पाठवू शकत नाही."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"संलग्नक लोड करू शकत नाही. पुन्हा प्रयत्न करा."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"नेटवर्क सज्ज नाही. पुन्हा प्रयत्न करा."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"मजकूर हटवा"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"मजकूर आणि संख्या प्रविष्ट करणे दरम्यान स्विच करा"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"अधिक सहभागी जोडा"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"सहभाग्यांची पुष्टी करा"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"नवीन संभाषण प्रारंभ करा"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"हा आयटम निवडा"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"व्हिडिओ प्ले करा"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"लोक आणि पर्याय"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"डीबग करणे"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"लोक आणि पर्याय"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"सामान्य"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"या संभाषणातील लोक"</string>
+ <string name="action_call" msgid="6596167921517350362">"एक कॉल करा"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"संदेश पाठवा"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g> वरून&lt;/small&gt;संदेश पाठवा"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">फोटो पाठवा</item>
+ <item quantity="other">फोटो पाठवा</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ऑडिओ पाठवा</item>
+ <item quantity="other">ऑडिओ पाठवा</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">व्हिडिओ पाठवा</item>
+ <item quantity="other">व्हिडिओ पाठवा</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">संपर्क कार्ड पाठवा</item>
+ <item quantity="other">संपर्क कार्ड पाठवा</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">संलग्नक पाठवा</item>
+ <item quantity="other">संलग्नके पाठवा</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one">पाठविण्‍यासाठी <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> संलग्नक सज्ज</item>
+ <item quantity="other">पाठविण्‍यासाठी <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> संलग्नक सज्ज</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"अभिप्राय पाठवा"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store मध्ये पहा"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"आवृत्ती माहिती"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"आवृत्ती %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"मुक्त स्त्रोत परवाने"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"सूचना"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"संलग्नक मर्यादेपर्यंत पोहोचले"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"संलग्नक लोड करण्यात अयशस्वी."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"संपर्कांवर जोडायचे?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"संपर्क जोडा"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"विषय"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"विषय: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"संपर्क कार्ड लोड करीत आहे"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"संपर्क कार्ड लोड करू शकलो नाही"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"संपर्क कार्ड पहा"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> संपर्क</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> संपर्क</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"संपर्क कार्ड"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"जन्मदिवस"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"टिपा"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"संदेश अग्रेषित करा"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"प्रत्युत्तर द्या"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS अक्षम"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"पाठविण्यासाठी, डीफॉल्ट SMS अॅप म्हणून संदेशन सेट करा"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"डीफॉल्ट SMS अॅप म्हणून संदेशन सेट करा"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"बदला"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"संदेश प्राप्त करण्यासाठी, डीफॉल्ट SMS अॅप म्हणून संदेशन सेट करा"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS संदेश पाठविण्यासाठी कोणतेही प्राधान्यकृत सिम निवडले नाही"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"या अॅप ला डिव्हाइस मालकाने अनुमती दिलेली नाही."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ठीक आहे"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"एका संभाषणात पुष्कळ सहभागी आहेत"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">अवैध संपर्क</item>
+ <item quantity="other">अवैध संपर्क</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"कॅमेरा प्रतिमा लोड करू शकलो नाही"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"आपण: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"मसुदा"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"आपण नवीन संभाषण प्रारंभ केल्यावर, आपल्याला तो येथे सूचीबद्ध केलेला दिसेल"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"संग्रहित केलेली संभाषणे येथे दिसतात"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"संभाषणे लोड करीत आहे…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"चित्र"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ऑडिओ क्लिप"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"व्हिडिओ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"संपर्क कार्ड"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"पूर्ववत करा"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"पुन्हा प्रयत्न करा"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"एक नवीन संदेश प्रारंभ करण्यासाठी एक संपर्क नाव किंवा फोन नंबर प्रविष्ट करा"</string>
+ <string name="action_block" msgid="9032076625645190136">"अवरोधित करा"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> अवरोधित करा"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> अनावरोधित करा"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> अवरोधित करायचे?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"आपण या नंबरवरून संदेश प्राप्त करणे सुरु ठेवाल परंतु आपल्याला आणखी सूचित केले जाणार नाही. हे संभाषम संग्रहित केले जाईल."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"अवरोधित संपर्क"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"अनावरोधित करा"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"अवरोधित संपर्क"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"दस्तऐवज लायब्ररीतून प्रतिमा निवडा"</string>
+ <string name="sending_message" msgid="6363584950085384929">"संदेश पाठवित आहे"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"संदेश पाठवला"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"सेल्युलर डेटा बंद आहे. आपल्या सेटिंग्ज तपासा."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"विमान मोडमध्ये संदेश पाठवू शकत नाही"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"संदेश पाठविला जाऊ शकला नाही"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"संदेश डाउनलोड केला"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"सेल्युलर डेटा बंद आहे. आपल्या सेटिंग्ज तपासा."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"विमान मोडमध्ये संदेश डाउनलोड करू शकत नाही"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"संदेश डाउनलोड करणे शक्य झाले नाही"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"शून्य"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"एक"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"दोन"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"तीन"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"चार"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"पाच"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"सहा"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"सात"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"आठ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"नऊ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> सह संदेश पाठवू शकत नाही, त्रुटी <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"अज्ञात वाहकासह संदेश पाठवू शकत नाही, त्रुटी <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"अग्रेषित: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"संदेश पाठविला नाही: नेटवर्क वर सेवा सक्रिय नाही"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"संदेश पाठविला नाही: अवैध गंतव्यस्‍थान पत्ता"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"संदेश पाठविला नाही: अवैध संदेश"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"संदेश पाठविला नाही: असमर्थित सामग्री"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"संदेश पाठविला नाही: असमर्थित संदेश"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"संदेश पाठविला नाही: खूप मोठा"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"नवीन संदेश"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"पहा"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"प्रतिमा"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"योग्य अनुप्रयोग शोधणे शक्य झाले नाही"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"प्राप्तकर्ता काढा"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"नवीन संदेश"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"रद्द करा"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"प्रवेश बिंदू संपादित करा"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"सेट केले नाही"</string>
+ <string name="apn_name" msgid="1572691851070894985">"नाव"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS प्रॉक्सी"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS पोर्ट"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN प्रकार"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN हटवा"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"नवीन APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"जतन करा"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"टाकून द्या"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"नाव फील्ड रिक्त असू शकत नाही."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN रिक्त असू शकत नाही."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC फील्ड 3 अंकी असणे आवश्यक आहे."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC फील्ड 2 किंवा 3 अंकी असणे आवश्यक आहे."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"डीफॉल्ट APN सेटिंग्ज पुनर्संचयित करत आहे."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"डीफॉल्टवर रीसेट करा"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"डीफॉल्ट APN सेटिंग्ज रीसेट करणे पूर्ण झाले."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"अशीर्षकांकित"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"प्रवेश बिंदू नावे"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"नवीन APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"या वापरकर्त्यासाठी प्रवेश बिंदू नाव सेटिंग्ज उपलब्ध नाहीत"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"क्लिपबोर्डवर कॉपी करायचे?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"कॉपी करा"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> वर"</string>
+ <string name="general_settings" msgid="5409336577057897710">"सामान्य"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"प्रगत"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"सामान्य सेटिंग्ज"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"प्रगत सेटिंग्ज"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" सिम"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"सर्व प्राप्तकर्त्यांना वैयक्तिक SMS संदेश पाठवा. केवळ आपणच कोणतीही प्रत्युत्तरे मिळवाल"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"सर्व प्राप्तकर्त्यांना एक एकल MMS पाठवा"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"अज्ञात नंबर"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"नवीन संदेश"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"नवीन संदेश."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"सिम निवडकर्ता"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> निवडले, सिम निवडकर्ता"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"विषय संपादित करा"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"सिम निवडा किंवा विषय संपादित करा"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ऑडिओ रेकॉर्ड करण्‍यासाठी स्पर्श करा आणि धरून ठेवा"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"नवीन संभाषण प्रारंभ करा"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"संदेशन"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"संदेशन सूची"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"संदेशन"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"नवीन संदेश"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"संभाषण सूची"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"संभाषणे लोड करीत आहे"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"संदेश लोड करत आहे"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"अधिक संभाषणे पहा"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"अधिक संदेश पहा"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"संभाषण हटवले"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"संभाषण हटविले. भिन्न संदेशन संभाषण दर्शविण्यासाठी स्पर्श करा"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"अवरोधित केले"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"अनावरोधित केले"</string>
+ <string name="db_full" msgid="8459265782521418031">"संचयन स्थान कमी आहे. काही डेटा गमावला जाऊ शकतो."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"संलग्नके निवडा"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"निवडीची पुष्टी करा"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> निवडली"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"कृपया एक किंवा अधिक संलग्नके काढा आणि पुन्हा प्रयत्न करा."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"आपण आपला संदेश पाठविण्याचा प्रयत्न करू शकता, परंतु आपण जोपर्यंत एक किंवा अधिक संलग्नके काढत नाही तोपर्यंत तो वितरित केला जाऊ शकत नाही."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"आपण प्रति संदेश केवळ एक व्हिडिओ पाठवू शकता. कृपया अतिरिक्त व्हिडिओ काढा आणि पुन्हा प्रयत्न करा."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"संलग्नक लोड करण्यात संदेशन अयशस्वी झाले."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"तरीही पाठवा"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"संभाषण प्रारंभ करू शकलो नाही"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> निवडले"</string>
+</resources>
diff --git a/res/values-ms-rMY/arrays.xml b/res/values-ms-rMY/arrays.xml
new file mode 100644
index 0000000..10f0c1c
--- /dev/null
+++ b/res/values-ms-rMY/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"tiada subjek"</item>
+ <item msgid="272485471009191934">"tiada subjek"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ya"</item>
+ <item msgid="6049132459802288033">"Tidak"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Terima kasih"</item>
+ <item msgid="4881335087096496747">"Saya bersetuju"</item>
+ <item msgid="2422296858597420738">"Nice"</item>
+ <item msgid="4805581752819452687">"Saya dalam perjalanan"</item>
+ <item msgid="4746700499431366214">"OK, saya akan hubungi anda semula"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..80cb5c4
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Pemesejan"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Pemesejan"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Pilih perbualan"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Tetapan"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Hantar Mesej"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Tambah lampiran"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Bantuan"</string>
+ <string name="welcome" msgid="2857560951820802321">"Selamat datang"</string>
+ <string name="skip" msgid="7238879696319945853">"Langkau"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Seterusnya &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Seterusnya"</string>
+ <string name="exit" msgid="1905187380359981199">"Keluar"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Tetapan &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Tetapan"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Pemesejan memerlukan kebenaran untuk mengakses SMS, Telefon dan Kenalan."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Anda boleh menukar kebenaran dalam Tetapan &gt; Apl &gt; Pemesejan &gt; Kebenaran."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Anda boleh menukar kebenaran dalam Tetapan, Apl, Pemesejan, Kebenaran."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Kerap"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Semua kenalan"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Hantar ke <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Abadikan gambar atau video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Pilih imej dari peranti ini"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Rakam audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Pilih foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Media dipilih."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Media tidak dipilih."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> dipilih"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imej <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" msgid="8142175029053370662">"imej"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Rakam audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Kongsi"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Sebentar tadi"</string>
+ <string name="posted_now" msgid="867560789350406701">"Sekarang"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> jam</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> jam</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hari</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hari</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> minggu</item>
+ <item quantity="one">seminggu</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> bulan</item>
+ <item quantity="one">sebulan</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tahun</item>
+ <item quantity="one">setahun</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mesej kelas 0"</string>
+ <string name="save" msgid="5081141452059463572">"Simpan"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Peranti kekurangan ruang. Pemesejan akan memadamkan mesej yang lebih lama secara automatik untuk mengosongkan ruang."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Ruang storan semakin berkurangan"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Pemesejan mungkin tidak akan menghantar atau menerima mesej sehingga lebih banyak ruang tersedia pada peranti anda."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Storan SMS kurang. Anda mungkin perlu memadamkan mesej."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Sahkan nombor telefon anda"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Langkah sekali ini akan memastikan Pemesejan menghantar mesej kumpulan anda dengan betul."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Nombor telefon"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Padamkan semua mesej dengan media"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Padamkan mesej yang lebih lama daripada <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Auto padam mesej yang lebih lama daripada <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Abaikan"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Padam semua mesej dengan media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Padam mesej yang lebih lama daripada <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Padam mesej yang lebih lama daripada <xliff:g id="DURATION">%s</xliff:g> dan hidupkan autopadam?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> kata"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Anda kata"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mesej daripada <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Anda telah menghantar mesej"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Menghantar…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Tidak dihantar. Sentuh untuk mencuba lagi."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Tidak dihantar. Mencuba lagi..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Hantar semula atau padam"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Sila buat panggilan suara ke perkhidmatan kecemasan. Mesej teks anda tidak dapat dihantar pada masa ini."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Gagal"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Mesej MMS baharu untuk dimuat turun"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Mesej MMS baharu"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Tidak dapat memuat turun"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Sentuh untuk mencuba lagi"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Sentuh untuk memuat turun"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Muat turun atau padam"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Memuat turun..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Mesej telah tamat tempoh atau tidak tersedia"</string>
+ <string name="mms_info" msgid="3402311750134118165">"saiz: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, tarikh luput: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Tidak dapat menghantar. Penerima tidak sah."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Perkhidmatan tidak diaktifkan pada rangkaian"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Tidak dapat menghantar kerana masalah rangkaian"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Mesej telah tamat tempoh atau tidak tersedia"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Tiada subjek)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Pengirim tidak dikenali"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Dikirimkan"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Tidak dapat memuat turun <xliff:g id="SUBJECT">%1$s</xliff:g> daripada <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Tidak dapat menyelesaikan operasi pangkalan data akibat memori rendah"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mesej tidak dihantar"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Sesetengah mesej tidak dihantar dalam Pemesejan"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mesej dalam <xliff:g id="CONVERSATIONS">%d</xliff:g> perbualan</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mesej dalam satu perbualan</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mesej tidak dimuat turun"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Sesetengah mesej tidak dimuat turun dalam Pemesejan"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mesej dalam <xliff:g id="CONVERSATIONS">%d</xliff:g> perbualan</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mesej dalam satu perbualan</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mesej ke <xliff:g id="NUMBER">%1$s</xliff:g> tidak dihantar"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Sila buat panggilan suara ke perkhidmatan kecemasan. Mesej teks anda ke <xliff:g id="NUMBER">%1$s</xliff:g> tidak dapat dihantar pada masa ini."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> mesej baharu</item>
+ <item quantity="one">Mesej baharu</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Mula"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera tidak tersedia"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera tidak tersedia"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Pengabadian video tidak tersedia"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Tidak dapat menyimpan media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Tidak boleh mengambil gambar"</string>
+ <string name="back" msgid="1477626055115561645">"Kembali"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Diarkibkan"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Padam"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arkib"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Nyaharkib"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Matikan pemberitahuan"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Hidupkan pemberitahuan"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Tambah kenalan"</string>
+ <string name="action_download" msgid="7786338136368564146">"Muat turun"</string>
+ <string name="action_send" msgid="377635240181672039">"Hantar"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Padam"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Padamkan mesej ini?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Tindakan ini tidak boleh dibuat asal."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Padam"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Padamkan perbualan ini?</item>
+ <item quantity="one">Padamkan perbualan ini?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Padam"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Batal"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Ke"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Pilih berbilang imej"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Sahkan pilihan"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Tidak dapat merakam audio. Cuba lagi."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Tidak dapat memainkan audio. Cuba lagi."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Tidak dapat menyimpan audio. Cuba lagi."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Sentuh &amp; tahan"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Gambar"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Klip audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video dalam strim"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kad kenalan"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Muat turun"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Balas melalui SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Balas melalui MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Balas"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> peserta</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> peserta</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Saya"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kenalan yang disekat &amp; diarkib"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kenalan dinyahsekat &amp; dinyaharkib"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> diarkibkan"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> dinyaharkib"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Pemberitahuan dimatikan"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Pemberitahuan dihidupkan"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Semuanya sedia. Sentuh Hantar lagi."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Pemesejan berjaya dijadikan sebagai apl SMS lalai."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Buang lampiran</item>
+ <item quantity="one">Buang lampiran</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Lampiran audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Mainkan lampiran audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Jeda"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mesej daripada <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mesej gagal daripada <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mesej daripada <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mesej tidak dihantar kepada <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Menghantar mesej kepada <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mesej gagal kepada <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mesej kepada <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mesej gagal daripada <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mesej daripada <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mesej tidak dihantar kepada <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Menghantar mesej kepada <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mesej gagal kepada <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mesej kepada <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Masa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mesej gagal. Sentuh untuk mencuba semula."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Perbualan dengan <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Padam subjek"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Rakam video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Abadikan imej pegun"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Ambil gambar"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Mula merakam video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Tukar kepada kamera skrin penuh"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Bertukar antara kamera hadapan dan belakang"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Berhenti merakam dan lampirkan video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Berhenti merakam video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto pemesejan"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foto disimpan ke album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto disimpan ke album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video disimpan ke album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video disimpan ke album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> lampiran disimpan ke album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> lampiran disimpan ke album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> lampiran disimpan ke \"Muat Turun\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> lampiran disimpan ke \"Muat Turun\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> lampiran disimpan</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> lampiran disimpan</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Tidak dapat menyimpan <xliff:g id="QUANTITY_1">%d</xliff:g> lampiran</item>
+ <item quantity="one">Tidak dapat menyimpan <xliff:g id="QUANTITY_0">%d</xliff:g> lampiran</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Lampiran MMS yang disimpan"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Tetapan"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Diarkibkan"</string>
+ <string name="action_close" msgid="1840519376200478419">"Tutup"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Lanjutan"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Nyahpepijat"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Pemberitahuan"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Bunyi"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Senyap"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Bergetar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Disekat"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Laporan penghantaran SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Minta laporan penghantaran untuk setiap SMS yang anda hantar"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Auto dapat kembali"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Dapatkan semula mesej MMS secara automatik"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Dapatkan kembali perayauan secara automatik"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Dapatkan MMS secara automatik semasa merayau"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Pemesejan kumpulan"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Gunakan MMS untuk menghantar mesej tunggal apabila terdapat berbilang penerima"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Apl SMS lalai"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Apl SMS lalai"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Nombor telefon anda"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Tidak diketahui"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Bunyi mesej keluar"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Buang SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Buang data mentah SMS yang diterima ke dalam fail storan luaran"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Buang MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Buang data mentah MMS yang diterima ke dalam fail storan luaran"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Makluman wayarles"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Pilihan mesej"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Salin teks"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Lihat butiran"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Padam"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Kirim semula"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Butiran mesej"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Jenis: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mesej teks"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mesej multimedia"</string>
+ <string name="from_label" msgid="1947831848146564875">"Daripada: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kepada: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Dihantar: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Diterima: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subjek: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Saiz: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Keutamaan: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Tinggi"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Biasa"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Rendah"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Alamat pengirim tersembunyi"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Tidak boleh menghantar mesej semasa memuatkan lampiran."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Tidak dapat memuatkan lampiran. Cuba lagi."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Rangkaian tidak sedia. Cuba lagi."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Padam teks"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Beralih antara memasukkan teks dan nombor"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Tambah peserta lagi"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Sahkan peserta"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Mulakan perbualan baharu"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Pilih item ini"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Mainkan video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Orang &amp; pilihan"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Nyahpepijat"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Orang &amp; pilihan"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Umum"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Orang dalam perbualan ini"</string>
+ <string name="action_call" msgid="6596167921517350362">"Buat panggilan"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Hantar mesej"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Hantar mesej&lt;br/&gt;&lt;small&gt;from <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Hantar foto</item>
+ <item quantity="one">Hantar foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Hantar audio</item>
+ <item quantity="one">Hantar audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Hantar video</item>
+ <item quantity="one">Hantar video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Hantar kad kenalan</item>
+ <item quantity="one">Hantar kad kenalan</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Hantar lampiran</item>
+ <item quantity="one">Hantar lampiran</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> lampiran sedia untuk dihantar</item>
+ <item quantity="one">Satu lampiran sedia untuk dihantar</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Hantar maklum balas"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Lihat di Gedung Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Maklumat versi"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versi %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Lesen sumber terbuka"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Pemberitahuan"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Had lampiran dicapai"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Gagal memuatkan lampiran."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Tambah pada Kenalan?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Tambah Kenalan"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subjek"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subjek: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Memuatkan kad kenalan"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Tidak dapat memuatkan kad kenalan"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Lihat kad kenalan"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kenalan</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kenalan</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kad kenalan"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Tarikh lahir"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Nota"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Kirim semula mesej"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Balas"</string>
+ <string name="plus_one" msgid="9010288417554932581">"1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS dilumpuhkan"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Untuk menghantar, jadikan Pemesejan apl SMS lalai"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Jadikan Pemesejan apl SMS lalai"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Tukar"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Untuk menerima mesej, jadikan Pemesejan apl SMS lalai"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Tiada SIM pilihan yang dipilih untuk menghantar mesej SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Apl ini tidak dibenarkan oleh pemilik peranti."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Terlalu ramai peserta dalam perbualan"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Kenalan tidak sah</item>
+ <item quantity="one">Kenalan tidak sah</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Tidak dapat memuatkan imej kamera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Anda: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draf"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Sebaik sahaja anda memulakan perbualan baharu, anda akan melihat perbualan itu disenaraikan di sini"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Perbualan diarkibkan dipaparkan di sini"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Memuatkan perbualan..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Gambar"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Klip audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kad kenalan"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Buat asal"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Cuba semula"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Masukkan nama kenalan atau nombor telefon untuk memulakan mesej baharu"</string>
+ <string name="action_block" msgid="9032076625645190136">"Sekat"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Sekat <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Nyahsekat <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Sekat <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Anda akan terus menerima mesej daripada nombor ini tetapi tidak akan dimaklumkan lagi. Perbualan ini akan diarkibkan."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Kenalan yang disekat"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"NYAHSEKAT"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Kenalan yang disekat"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Pilih imej dari perpustakaan dokumen"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Menghantar mesej"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mesej dihantar"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Data selular dimatikan. Periksa tetapan anda."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Tidak dapat menghantar mesej dalam mod Pesawat"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Mesej tidak dapat dihantar"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mesej dimuat turun"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Data selular dimatikan. Periksa tetapan anda."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Tidak dapat memuat turun mesej dalam mod Pesawat"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Mesej tidak dapat dimuat turun"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Sifar"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Satu"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dua"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tiga"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Empat"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Lima"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Enam"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Tujuh"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Lapan"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Sembilan"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Tidak dapat menghantar mesej dengan <xliff:g id="CARRIERNAME">%1$s</xliff:g>, ralat <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Tidak dapat menghantar mesej dengan pembawa yang tidak diketahui, ralat <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"K.S.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mesej tidak dihantar: perkhidmatan tidak diaktifkan di rangkaian"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mesej tidak dihantar: alamat destinasi tidak sah"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mesej tidak dihantar: mesej tidak sah"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mesej tidak dihantar: kandungan tidak disokong"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mesej tidak dihantar: mesej tidak disokong"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mesej tidak dihantar: terlalu besar"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mesej baharu"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Paparan"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imej"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Tidak menemui aplikasi yang sesuai"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Alih keluar penerima"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mesej baharu"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Batal"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edit titik capaian"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Tidak ditetapkan"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nama"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proksi MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Jenis APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Padam APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN Baharu"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Simpan"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Buang"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Medan Nama tidak boleh kosong."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN tidak boleh kosong."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Medan MCC mestilah 3 digit."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Medan MNC hendaklah mempunyai 2 atau 3 digit."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Memulihkan tetapan lalai APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Tetapkan semula kepada lalai"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Penetapan semula tetapan lalai APN selesai."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Tidak bertajuk"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nama Titik Capaian"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN Baharu"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Tetapan Nama Titik Capaian tidak tersedia untuk pengguna ini"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Salin ke papan keratan?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Salin"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ke <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Umum"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Lanjutan"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Tetapan umum"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Tetapan terperinci"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Hantar mesej SMS individu kepada semua penerima. Hanya anda yang akan menerima sebarang balasan"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Hantar MMS tunggal kepada semua penerima"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nombor tidak diketahui"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mesej baharu"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mesej baharu"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Pemilih SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> dipilih, pemilih SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edit subjek"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Pilih SIM atau edit subjek"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Sentuh dan tahan untuk merakam audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Mulakan perbualan baharu"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Pemesejan"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Senarai Pemesejan"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Pemesejan"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mesej baharu"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Senarai perbualan"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Memuatkan perbualan"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Memuatkan mesej"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Lihat lagi perbualan"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Lihat pesanan lagi"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Perbualan dipadamkan"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Perbualan dipadamkan. Sentuh untuk menunjukkan perbualan Pemesejan yang lain"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Disekat"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Dinyahsekat"</string>
+ <string name="db_full" msgid="8459265782521418031">"Ruang storan rendah. Sesetengah data mungkin hilang."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Pilih lampiran"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Sahkan pilihan"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> dipilih"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Sila alih keluar satu atau lebih lampiran, kemudian cuba lagi."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Anda boleh cuba menghantar mesej tetapi mesej itu mungkin tidak dihantar kecuali anda mengalih keluar satu atau beberapa lampiran."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Anda hanya boleh menghantar satu video bagi setiap mesej. Sila alih keluar video tambahan dan cuba lagi."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Pemesejan gagal memuatkan lampiran."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Hantar juga"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Tidak dapat memulakan perbualan"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> dipilih"</string>
+</resources>
diff --git a/res/values-my-rMM/arrays.xml b/res/values-my-rMM/arrays.xml
new file mode 100644
index 0000000..331ac85
--- /dev/null
+++ b/res/values-my-rMM/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"အကြောင်းအရာ မရှိ"</item>
+ <item msgid="272485471009191934">"အကြောင်းအရာမဲ့"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ဟုတ်"</item>
+ <item msgid="6049132459802288033">"မဟုတ်"</item>
+ <item msgid="3084376867445867895">"အိုကေ"</item>
+ <item msgid="3155097332660174689">"ဟဲဟဲ"</item>
+ <item msgid="2611328818571146775">"ကျေးဇူးပဲ"</item>
+ <item msgid="4881335087096496747">"ကျွန်ုပ် သဘောတူသည်"</item>
+ <item msgid="2422296858597420738">"ကောင်း"</item>
+ <item msgid="4805581752819452687">"ကျွန်ုပ်၏ လမ်းပေါ်မှာ"</item>
+ <item msgid="4746700499431366214">"အိုကေ၊ ကျွန်ုပ် နောက်မှာ ပြန်လာပါရစေ"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
new file mode 100644
index 0000000..35a6586
--- /dev/null
+++ b/res/values-my-rMM/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"စာပို့ခြင်း"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"စာပို့ခြင်း"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"စကားဝိုင်း ရွေးချယ်ပါ"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ဆက်တင်များ"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"စာ ပို့ရန်"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ပူးတွချက် တစ်ခု ထည့်ရန်"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"အကူအညီ"</string>
+ <string name="welcome" msgid="2857560951820802321">"မင်္ဂလာပါ"</string>
+ <string name="skip" msgid="7238879696319945853">"ကျော်ရန်"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"နောက်တစ်ခု"</string>
+ <string name="next" msgid="4496962051601713843">"နောက်တစ်ခု"</string>
+ <string name="exit" msgid="1905187380359981199">"ထွက်ရန်"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ချိန်ညှိချက်များ &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ချိန်ညှိချက်များ"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"စာတိုပို့ခြင်က SMS၊ ဖုန်း နှင့် အဆက်အသွယ်များထံ ခွင့်ပြုချက် လိုအပ်သည်။"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"သင်သည် ခွင့်ပြုချက်များကို ဆက်တင်များထဲမှာ ပြောင်းလဲနိုင် &gt; အက်ပ်များ &gt; စာတိုပို့ခြင်း &gt; ခွင့်ပြုချက်များ။"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"သင်သည် ခွင့်ပြုချက်များကို ဆက်တင်များ၊ အက်ပ်များ၊ စာတိုပို့ခြင်း၊ ခွင့်ပြုချက်များထဲတွင် ပြောင်းလဲနိုင်သည်။"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"မကြာခဏ"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ဆက်သွယ်အားလုံး"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g>သို့ ပို့ရန်"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ပုံများ သို့မဟုတ် ဗီဒီယို ရိုက်ယူရန်"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ဤစက်ပစ္စည်းမှ ပုံများ ရွေးရန်"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"အသံကို ဖမ်းရန်"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ပုံရွေးရန်"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"မီဒီယာကိုရွေးချယ်ထားသည်။"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"မီဒီယာကို မရွေးချယ်ထားပါ။"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ခု ရွေးချယ်ထား"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ပုံ <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" msgid="8142175029053370662">"ပုံ"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"အသံကို ဖမ်းရန်"</string>
+ <string name="action_share" msgid="2143483844803153871">"မျှဝေရန်"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ယခုလေးတင်"</string>
+ <string name="posted_now" msgid="867560789350406701">"ယခု"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> မိနစ်</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> မိနစ်</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> နာရီ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> နာရီ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ရက်</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ရက်</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ပတ်</item>
+ <item quantity="one">၁ ပတ်</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> လ</item>
+ <item quantity="one">တစ်လ</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> နှစ်</item>
+ <item quantity="one">တစ်နှစ်</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"အဆင် ၀ စာတို"</string>
+ <string name="save" msgid="5081141452059463572">"သိမ်းဆည်းရန်"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ကိရိယာထဲတွင် နေရာ နည်းနေသည်။ နေရာလွတ်ပေးရန် စာတိုပို့ခြင်းက စာတို စာသား အဟောင်းကို အလိုအလျောက် ဖယ်ရှားပစ်မည်။"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"သိုလှောင်ခန်း နေရာ ကုန်ပါတော့မည်"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"သင်၏ ကိရိယာထဲတွင် နေရာလွတ် နောက်ထပ် ပေါ်မလာမချင်း စာတိုပို့ခြင်းက စာတိုများကို ပို့ခြင်း သို့မဟုတ် လက်ခံခြင်း လုပ်နိုင်မည် မဟုတ်ပါ။"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS သိုလှောင်ခန်း နည်းနေသည်။ သင်သည် စာများကို ဖျက်ချင် ဖျက်ရနိုင်သည်။"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"သင့် ဖုန်းနံပါတ်ကို အတည်ပြုပါ"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"တကြိမ် လုပ်ရသည့် ဒီခြေလှမ်းက စာတိုပို့ခြင်းမှ သင်၏ အုပ်စု စာတိုများကို စနစ်တကျ ပို့ပေးမှာကို စိတ်ချနိုင်ပါသည်။"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ဖုန်း နံပါတ်"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"မီဒီယာနှင့် စာများ အားလုံးကို ဖျက်မည်"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> ထက် ပိုကြာသော စာများကို ဖျက်မည်"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> မတိုင်ခင်က စာများအား အလိုအလျောက် ဖျက်ရန်"</string>
+ <string name="ignore" msgid="7063392681130898793">"လစ်လျှူရှုရန်"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"မီဒီယာနှင့် စာတိုများ ဖျက်မည်လား?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> ထက် ပိုကြာသော စာများ ဖျက်မည်လား?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> မတိုင်ခင်က စာများအား ဖျက်ပြီး စာများ အလိုအလျောက်ဖျက်နိုင်မှုကို ဖွင့်ထားမည်လား?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ပြောတာက"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"သင်ပြောတာက"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> ထံမှစာ"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"သင်စာပို့သည်"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"ပို့နေ၏…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"မပို့နိုင်ပါ။ ထပ်မံကြိုးစားရန် ထိပါ။"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"မပို့နိုင်ပါ။ ထပ်မံကြိုးစားနေ၏…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ပြန်ပို့ရန် သို့မဟုတ် ဖျက်ရန်"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"အရေးပေါ်ဝန်ဆောင်မှုများထံ အသံဖြင့် ခေါ်ပါ။ ယခုအချိန်တွင် သင့် စာတို မပို့နိုင်ပါ။"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"မအောင်မြင်ခဲ့"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"MMS စာအသစ်ကို ဒေါင်းလုဒ် လုပ်ရန်"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"MMS စာအသစ်"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ဒေါင်းလုဒ် မလုပ်နိုင်ခဲ့ပါ"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ထပ်စမ်းရန် ထိပါ"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ဒေါင်းလုပ်ရယူရန် ထိပါ"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ဒေါင်းလုဒ် လုပ်ရန် သို့မဟုတ် ဖျက်ရန်"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ဒေါင်းလုပ် လုပ်နေစဉ်"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"စာသက်တမ်းကုန်ဆုံးသွား၏ သို့မဟုတ် စာမရှိတော့ပါ။"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ဆိုက်: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>၊ သက်တမ်း ကုန်ဆုံး: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"မပို့နိုင်ပါ။ လက်ခံမည့်သူ မရယူနိုင်ပါ။"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"ကွန်ရက်တွင် ဝန်ဆောင်မှု စဖွင့်သတ်မှတ်ခြင်း မပြုရသေးပါ"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ကွန်ရက် ပြဿနာကြောင့် မပို့နိုင်ခဲ့ပါ။"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"စာ သက်တမ်းကုန်ဆုံးသွား၏ သို့မဟုတ် စာမရှိတော့ပါ။"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(အကြောင်းအရာမရှိ)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"ပေးပို့သူ မသိရ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ပေးသွင်းပြီး"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="SUBJECT">%1$s</xliff:g>စာကို <xliff:g id="FROM">%2$s</xliff:g>မှ ဒေါင်းလုဒ် မလုပ်နိုင်ခဲ့ပါ"</string>
+ <string name="low_memory" msgid="5300743415198486619">"မှတ်ဉာဏ် နည်းနေ၍ ဒေတာဘေ့စ် လုပ်ကိုင်စရာ မပြီးဆုံးနိုင်ခဲ့ပါ။"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"စာ မပို့ခဲ့ပါ"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"အချို့ စာတိုများကို စာပို့ခြင်းထဲတွင် မပို့ခဲ့ပါ။"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"> စာတိုများ <xliff:g id="MESSAGES_1">%d</xliff:g> ခုမှာ စကားပြောခန်း <xliff:g id="CONVERSATIONS">%d</xliff:g> ခု အတွင်းတွင် ဖြစ်၏</item>
+ <item quantity="one"> စကားပြောခန်းတစ်ခုတွင်းရှိ စာတိုများ <xliff:g id="MESSAGES_0">%d</xliff:g> ခု</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"စာကို ဒေါင်းလုပ်မလုပ်ထားပါ"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"အချို့ စာတိုများကို စာပို့ခြင်းထဲသို့ ဒေါင်းလုဒ် မလုပ်ခဲ့ပါ။"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"> စာတိုများ <xliff:g id="MESSAGES_1">%d</xliff:g> ခုမှာ စကားပြောခန်း <xliff:g id="CONVERSATIONS">%d</xliff:g> ခု အတွင်းတွင် ဖြစ်၏</item>
+ <item quantity="one"> စကားပြောခန်းတစ်ခုတွင်းရှိ စာတိုများ <xliff:g id="MESSAGES_0">%d</xliff:g> ခု</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> သို့ စာတို ပို့မရပါ"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"အရေးပေါ်ဝန်ဆောင်မှုများထံ အသံဖြင့် ခေါ်ပါ။ <xliff:g id="NUMBER">%1$s</xliff:g> သို့ ယခုအချိန်တွင် သင့် စာတို မပို့နိုင်ပါ။"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> စာအသစ်</item>
+ <item quantity="one">စာအသစ်</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"စတင်ရန်"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ကင်မရာ အသုံးမပြုနိုင်ပါ"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ကင်မရာ အသုံးမပြုနိုင်ပါ"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ဗွီဒီယို မရိုက်ယူနိုင်ပါ"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"မီဒီယာအား မသိမ်းဆည်းနိုင်ပါ"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ဓာတ်ပုံရိုက်၍ မရနိုင်ပါ"</string>
+ <string name="back" msgid="1477626055115561645">"နောက်သို့"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"မော်ကွန်းတင်ပြီး"</string>
+ <string name="action_delete" msgid="4076795795307486019">"ဖျက်ရန်"</string>
+ <string name="action_archive" msgid="5437034800324083170">"မော်ကွန်း"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"မော်ကွန်းမှထုတ်ယူရန်"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"အက​ြောင်းကြားချက်များ ပိတ်ရန်"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"အကြောင်းကြားချက်များ ဖွင့်ရန်"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"အဆက်အသွယ် ထည့်ရန်"</string>
+ <string name="action_download" msgid="7786338136368564146">"ဒေါင်းလုဒ် လုပ်ရန်"</string>
+ <string name="action_send" msgid="377635240181672039">"ပို့ရန်"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"ဖျက်ရန်"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ဤ စာကို ဖျက်မလား?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ဒီလုပ်ဆောင်ချက်ကို ြပန်ဖျက်မရနိုင်ပါ။"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"ဖျက်ရန်"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">ဤစကားဝိုင်းများအား ဖျက်မည်လား?</item>
+ <item quantity="one">ဤစကားဝိုင်းအား ဖျက်မည်လား?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"ဖျက်ရန်"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"ထားတော့"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"သို့"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"ပုံများစွာ ရွေးချယ်ရန်"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"ရွေးချယ်မှုကို အတည်ပြုရန်"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"အသံ ဖမ်းမရပါ။ ထပ်မံကြိုးစားပါ။"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"အသံဖွင့်မရပါ။ ထပ်မံကြိုးစားပါ။"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"အသံ သိမ်းမရပါ။ ထပ်မံကြိုးစားပါ။"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ထိပြီး &amp; ကိုင်ထားရန်"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ပုံ"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"အသံ ကလီပ်"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ဗီဒီယို"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"အဆက်အသွယ် ကဒ်"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ဒေါင်းလုဒ် လုပ်ရန်"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS မှတစ်ဆင့် ဖြေကြားရန်"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS ဖြင့် ဖြေကြားရန်"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ဖြေကြားရန်"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"> ပါဝင်သူ <xliff:g id="COUNT_1">%d</xliff:g> ယောက်</item>
+ <item quantity="one"> ပါဝင်သူ <xliff:g id="COUNT_0">%d</xliff:g> ယောက်</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ကျွန်ုပ်"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"အဆက်အသွယ်ဖြတ်တောက်ပြီး &amp; သိမ်းဆည်းပြီး"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"အဆက်အသွယ်အား မပိတ်ဆို့တော့ပါ &amp;amp မှော်ကွန်းမတင်တော့ပါ"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> မှတ်တမ်းတင်သိမ်းဆည်းခဲ့၏"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> သိမ်းထားခြင်းမှ ပြန်ထုတ်ယူသည်"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"အသိပေးချက်များ ပိတ်ထား"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"အသိပေးချက်များ ဖွင့်ထား"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"အဆင်သင့်ဖြစ်ပြီ။ Send အား ထိပါ။"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"စာတို့ပို့ခြင်းကို SMS ပုံသေ app အဖြစ် အောင်မြင်စွာ သတ်မှတ်လိုက်ပြီ။"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">ပူးတွဲပါဖိုင်များအား ဖယ်ရှားပါ</item>
+ <item quantity="one">ပူးတွဲပါဖိုင်အား ဖယ်ရှားပါ</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"အသံ ပူးတွဲဖိုင်"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ပူးတွဲပါအသံကိုဖွင့်ပါ"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"ဆိုင်းငံ့ရန်"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> ထံမှ စာတို: <xliff:g id="MESSAGE">%s</xliff:g> ။"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> မှ စာတိုပို့ခြင်းမအောင်မြင်ပါ။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> မှ စာတို။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတိုမပို့ရသေးပါ။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတိုပို့နေသည်။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတိုပို့ခြင်းမအောင်မြင်ပါ။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတို။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> မှ စာပို့ခြင်းမအောင်မြင်ပါ။အချိန်: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>မှ စာတိုပို့နေသည်။ အချိန်: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတိုမပို့ရသေးပါ။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတိုပို့ နေသည်။အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့စာတိုပို့ခြင်းမအောင်မြင်ပါ။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> သို့ စာတိုပို့နေသည်။ အချိန်: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"စာတိုပို့ခြင်းမအောင်မြင်ပါ။ ပြန်လည်ကြိုးစားရန် ထိပါ။"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g>နှင့် စကားဝိုင်းများ"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"အကြောင်းအရာကို ဖျက်ရန်"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ဗီဒီယို ဖမ်းယူရန်"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ပုံသေ တစ်ခုကို ရိုက်ယူရန်"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ပုံ ရိုက်ယူရန်"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ဗီဒီယို ဖမ်းယူမှု စတင်ရန်"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"မျက်နှာပြင် အပြည့် ကင်မရာသို့ ပြောင်းရန်"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"ကင်မရာ အရှေ့ဘက်နှင့် အနောက်ဘက် ပြောင်းရန်"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"မှတ်တမ်းတင်မှုရပ်ကာ ဗီဒီယို ချိတ်ရန်"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ဗွီဒီယိုရိုက်ကူးခြင်း ရပ်ရန်"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"ဓာတ်ပုံများကို စာတိုပို့ခြင်း"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ဓါတ်ပုံများကို \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ဓါတ်ပုံကို \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ဗီဒီယိုများကို \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ဗီဒီယိုကို \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ပူးတွဲပါဖိုင်များကို \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ပူးတွဲပါဖိုင်ကို \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" အယ်လ်ဘမ်သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ပူးတွဲပါဖိုင်များကို \"ဒေါင်းလုဒ်များ\" သို့ သိမ်းဆည်းပြီ:ပါပြီ</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ပူးတွဲပါဖိုင်ကို \"ဒေါင်းလုဒ်များ\" သို့ သိမ်းဆည်းပြီးပါပြီ</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"> ပူးတွဲပါဖိုင် <xliff:g id="QUANTITY_1">%d</xliff:g> ဖိုင် သိမ်းဆည်းပြီး၏</item>
+ <item quantity="one"> ပူးတွဲပါဖိုင် <xliff:g id="QUANTITY_0">%d</xliff:g> ဖိုင် သိမ်းဆည်းပြီး၏</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"> ပူးတွဲပါဖိုင် <xliff:g id="QUANTITY_1">%d</xliff:g> အား မသိမ်းဆည်းနိုင်ခဲ့ပါ</item>
+ <item quantity="one"> ပူးတွဲပါဖိုင် <xliff:g id="QUANTITY_0">%d</xliff:g> ဖိုင်အား မသိမ်းဆည်းနိုင်ခဲ့ပါ</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"သိမ်းဆည်းထားသည့် MMS ပူးတွဲချက်"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ဆက်တင်များ"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"မော်ကွန်းတင်ပြီး"</string>
+ <string name="action_close" msgid="1840519376200478419">"ပိတ်ရန်"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"ရုပ်သံစာ"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"အဆင့်မြင့်"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ဒီဘာဂ်"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"အကြောင်းကြားချက်များ"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"အသံ"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"အသံတိတ်ရန်"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"တုန်ခါရန်"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ပိတ်ဆို့ထား"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS ဖြန့်ဝေမှု အစီရင်ခံစာများ"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"သင် ပို့လိုက်သော စာတိုင်း အတွက် ပို့ပြီးကြောင်း အစီရင်ခံစာကို တောင်းဆိုမည်"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"အလိုအလျောက် ပြန်လည်ရယူမည်"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS စာများကို အလိုအလျောက် ရယူရန်"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ရုန်းမင်းလုပ်စဉ် အလိုအလျောက် ပြန်လည်ထုတ်ယူမည်"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ရုန်းမင်းသုံးစဉ် MMS အလိုအလျောက် ရယူရန်"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"အဖွဲ့လိုက် စာပို့ခြင်း"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"စာတစ်စောင်ပို့ရန် လက်ခံသူအမြောက်အမြားရှိနေလျှင် ရုပ်သံစာကို သုံးရန်"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"စာတိုပို့ရန်သုံး အပလီကေးရှင်း"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"စာတိုပို့ရန်သုံး အပလီကေးရှင်း"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"သင့် ဖုန်းနံပါတ်"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"မသိရ"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"အပြင်သို့စာပေးပို့သည့် အသံများ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"စိုစွတ် SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"စိုစွတ် SMS အချက်လက်အကြမ်းများကို ပြင်ပသိုလှောင်မှုဖိုင် သို့လက်ခံ ရရှိပါသည်"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"စိုစွတ် MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"စိုစွတ် MMS အချက်လက်အကြမ်းများကို ပြင်ပသိုလှောင်မှုဖိုင် သို့လက်ခံ ရရှိပါသည်"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ကြိုးမဲ့ သတိပေးချက်များ"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"စာ အတွက် ရွေးစရာများ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"စာကူးယူရန်"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"အသေးစိတ်များကို ကြည့်မည်"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"ဖျက်ရန်"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"တစ်ဆင့်ပို့ရန်"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"စာသေးစိတ်များ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"အမျိုးအစား: "</string>
+ <string name="text_message" msgid="7415419755252205721">"စာတို"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"မာလ်တီမီဒီယာ စာ"</string>
+ <string name="from_label" msgid="1947831848146564875">"မှ: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"သို့: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ပို့ပြီး: "</string>
+ <string name="received_label" msgid="4442494712757995203">"လက်ခံရရှိခဲ့: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"အကြောင်းအရာ: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"အရွယ်အစား: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ဦးစားပေးမှု: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"ဆင်းမ်: "</string>
+ <string name="priority_high" msgid="728836357310908368">"မြင့်"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"ပုံမှန်"</string>
+ <string name="priority_low" msgid="7398724779026801851">"နိမ့်"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"ဆင်းမ် <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"ဝှက်ထားသော စာပို့သူ လိပ်စာ"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"ပူးတွဲချက်များဖွင့်စဉ် စာပို့၍မရပါ။"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ပူးတွဲဖိုင် မရယူနိုင်ပါ။ ထပ်မံကြိုးစားပါ။"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ကွန်ရက်အဆင်သင့် မဖြစ်သေးပါ။ ထပ်မံကြိုးစားပါ။"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"စာသားကို ဖျက်ရန်"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"စာသား နှင့် ဂဏန်းများ ရိုက်ထည့်မှု အကြားမှာ ပြောင်းလဲရန်"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"ပါဝင်သူများထပ်ထည့်မည်"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"ပါဝင်သူများ အတည်ပြုရန်"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"ပြောဆိုမှု အသစ် စတင်ရန်"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ဒီအရာကို ရွေးရန်"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ဗီဒီယို ဖွင့်ရန်"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"လူများ &amp; ရွေးစရာများ"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ဒီဘာဂ်"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"လူများ &amp; ရွေးစရာများ"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"အထွေထွေ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ဒီစကားဝိုင်း ထဲက လူများ"</string>
+ <string name="action_call" msgid="6596167921517350362">"ခေါ်ဆိုမှု ပြုလုပ်ရန်"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"စာတို ပို့ရန်"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"စာတိုကို&lt;br/&gt;&lt;&gt; <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; ထံမှပို့ရန်"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">ဓာတ်ပုံများ ပို့ရန်</item>
+ <item quantity="one">ဓာတ်ပုံ ပို့ရန်</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">အသံဖိုင်များ ပို့ရန်</item>
+ <item quantity="one">အသံဖိုင် ပို့ရန်</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">ဗီဒီယိုများ ပို့ရန်</item>
+ <item quantity="one">ဗီဒီယို ပို့ရန်</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">အဆက်အသွယ်ကဒ်များ ပို့ရန်</item>
+ <item quantity="one">အဆက်အသွယ်ကဒ် ပို့ရန်</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">ပူးတွဲပါဖိုင်များ ပို့ရန်</item>
+ <item quantity="one">ပူးတွဲပါဖိုင် ပို့ရန်</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"> ပူးတွဲဖိုင် <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ဖိုင် ပို့ရန် အဆင်သင့်ဖြစ်၏</item>
+ <item quantity="one">ပူးတွဲဖိုင်တစ်ဖိုင် ပို့ရန် အဆင်သင့်ဖြစ်၏</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"တုံ့ပြန်ချက် ပို့ရန်"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google ပလေး စတိုးတွင် ကြည့်ရန်"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ဗားရှင်း အင်ဖို"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"ဗားရှင်း %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"အခမဲ့ ရင်းမြစ် လိုင်စင်များ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"အကြောင်းကြားချက်များ"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ချိတ်တွဲမှု ကန့်သတ်ချက်သို့ရောက်ရှိပါပြီ"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ချိတ်တွဲမှု ဖွင့်ရန်မအောင်မြင်ပါ။"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"အဆက်အသွယ်များသို့ ထည့်ရမလား?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"အဆက်အသွယ် ထည့်ရန်"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"ဘာသာရပ်"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"အကြောင်းအရာ: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"အဆက်အသွယ် ကဒ်ကို တင်ပေးနေ"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"နာမည်ကဒ် မရယူနိုင်ပါ"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"အဆက်အသွယ် ကဒ်ကို ကြည့်ရန်"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> အဆက်အသွယ်</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> အဆက်အသွယ်</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"နာမည် ကဒ်များ"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"မွေးနေ့"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"မှတ်စုများ"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"စာကို တစ်ဆင့်ပို့ရန်"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"စာပြန်ရန်"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+၁"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS ပိတ်ထား"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"ပို့ရန်အတွက် စာတိုပို့ခြင်းကို SMS ပုံသေ app အဖြစ် သတ်မှတ်ပေးပါ"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"စာတိုပို့ခြင်းကို SMS ပုံသေ app အဖြစ် သတ်မှတ်ရန်"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"ပြောင်းရန်"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"စာတိုများကို လက်ခံရန်အတွက်၊ စာတိုပို့ခြင်းကို SMS ပုံသေ app အဖြစ် သတ်မှတ်ပေးပါ"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS စာများ ပို့ရန်အတွက် ဦးစားပေး ဆင်းမ်ကို မရွေးရသေးပါ"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ဒီappကို ကိရိယာ ပိုင်ရှင်က ခွင့် မပြုပါ။"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"အိုကေ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"စကားဝိုင်း တစ်ခုထဲမှာ ပါဝင်သူ များလွန်းနေ"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">အဆက်အသွယ်များ မမှန်ကန်ပါ</item>
+ <item quantity="one">အဆက်အသွယ် မမှန်ကန်ပါ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"ကင်မရာ ပုံ မရယူနိုင်ပါ"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"သင်: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"မူကြမ်း"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"စကားပြောစတင်သည်နှင့်၊ ဤစာရင်းတွင် ပေါ်လာသည်ကို သင်မြင်ရလိမ့်မည်။"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"ဤတွင်မြင်ရသည့် ပြောဆိုမှုများအား သိမ်းဆည်းရန်"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"ပြောဆိုမှုများကို တင်နေ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ပုံ"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"အသံ ကလီပ်"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ဗီဒီယို"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"အဆက်အသွယ် ကဒ်"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"ပြန်ဖျက်ရန်"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ထပ်စမ်းရန်"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"စာတို အသစ် စပို့ရန် အမည် သို့မဟုတ် ဖုန်းနံပါတ် ရိုက်ထည့်ပါ"</string>
+ <string name="action_block" msgid="9032076625645190136">"ပိတ်ဆို့ရန်"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"အဆက်အသွယ်ဖြတ်ရန် <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"အဆက်အသွယ်ပြန်လုပ် <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"ပိတ်ဆို့ရမလား<xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ဤနံပါတ်မှ စာတိုများ သင် ဆက်လက်ရရှိနေမည်ဖြစ်သော်လည်း အကြောင်းကြားချက်ပေးပို့တော့မည် မဟုတ်ပါ။ ဤပြောဆိုမှုများအား သိုလှောင်သိမ်းဆည်းထားမည်ဖြစ်၏။"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"အဆက်အသွယ် ဖြတ်ပြီး"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"အဆက်အသွယ်ပြန်လုပ်"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"အဆက်အသွယ် ဖြတ်ပြီး"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"စာကြည့်တိုက်မှ ပုံရွေးရန်"</string>
+ <string name="sending_message" msgid="6363584950085384929">"စာတိုပို့နေသည်"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"စာတိုပို့ပြီးပါပြီ"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"ဆဲလူလာ ဒေတာ ပိတ်ထား၏။ ဆက်တင်တွင် စစ်ဆေးပါ။"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"လေယာဉ်ပျံပေါ်သုံးစနစ်တွင် စာများ မပို့နိုင်ပါ"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"စာများ ပို့၍မရပါ။"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"စာတိုအားဒေါင်းလုတ်လုပ်ပြီး"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"ဆဲလူလာ ဒေတာ ပိတ်ထား၏။ ဆက်တင်တွင် စစ်ဆေးပါ။"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"လေယာဉ်ပျံမုဒ်တွင် စာတိုများကို ဒေါင်းလုပ်လုပ်၍မရနိုင်ပါ"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"စာတိုအား ဒေါင်းလုပ်လုပ်၍မရပါ"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"သုည"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"တစ်"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"နှစ်"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"သုံး"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"လေး"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ငါး"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ခြောက်"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ခုနစ်"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ရှစ်"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ကိုး"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g>ဖြင့် စာတိုမပို့နိုင်ပါ၊ အမှား <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"အမည်မသိသယ်ဆောင်သူဖြင့် စာတိုမပို့နိုင်ပါ၊ အမှား <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"တစ်ဆင့်ပို့: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"စာတိုကို မပို့ခဲ့: ဝန်ဆောင်မှုကို ကွန်ရက် ပေါ်မှာ စဖွင့်မသုံးရသေး"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"စာမပို့နိုင်ပါ − လက်ခံယူမည့် လိပ်စာ မှားနေ၏"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"စာမပို့နိုင်ပါ− စာ မမှန်ကန်ပါ။"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"စာမပို့နိုင်ပါ − ထောက်ပံ့မထားသော အကြောင်းအရာဖြစ်၏"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"စာမပို့နိုင်ပါ− ထောက်ပံ့မထားသော စာဖြစ်၏"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"စာပိုဒ်အလွန်ရှည်ပါက မက်ဆေ့ချ် ပို့မရပါ။"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"စာ အသစ်"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"ကြည့်ရှုရန်"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"ပုံ"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"သင့်တော်သည့် အပလီကေးရှင်းကို ရှာမရခဲ့"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"အဝေးမှ လက်ခံရရှိသူ"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"စာ အသစ်"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"ဖျက်သိမ်းရန်"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ဝင်ရောက်မည့်ပွိုင့်အား ပြင်ဆင်ရန်"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"မသတ်မှတ်ထားပါ"</string>
+ <string name="apn_name" msgid="1572691851070894985">"အမည်"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS ပရော့က်စီ"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS ပို့တ်နံပါတ်"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN အမျိုးအစား"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN ကိုဖျက်မည်"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN အသစ်"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"သိမ်းဆည်းရန်"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ဖျက်သိမ်းရန်"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"အမည်အကွက်မှာ ကွက်လပ်ဖြစ်မနေစေရပါ"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN မှာ ကွက်လပ်ဖြစ်မနေစေရပါ"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MNC အကွက်မှာ ဂဏန်း ၃ လုံးဖြစ်ရမည်"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC အကွက်မှာ ဂဏန်း ၂ လုံး သို့မဟုတ် ၃ လုံးဖြစ်ရမည်"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"မူရင်း APN ဆက်တင်များကိုပြန်လည်ရယူစေမည်"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"စနစ်အား မူလစက်ရုံအခြအေနေထံ ပြန်လည်သတ်မှတ်ရန်"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"APN ၏မူရင်းအပြင်အဆင်များကို အစသို့ပြန်လည်စတင်မှုအား ပြီးဆုံး၏။"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ခေါင်းစဉ်မဲ့"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ချိတ်ဆက်သောပွိုင့်အမည်များ"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN အသစ်"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ဤအသုံးပြုသူ အတွက် ချိတ်ဆက်သောပွိုင့်အမည်၏ ဆက်တင်များကို မရယူနိုင်"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ကလီပ်ဘုတ်သို့ ကူးရမလား?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"ကူးယူရန်"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g>သို့"</string>
+ <string name="general_settings" msgid="5409336577057897710">"အထွေထွေ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"အဆင့်မြင့်သော"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"အထွေထွေ ဆက်တင်များ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"အဆင့်မြင့် ဆက်တင်များ"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ရရှိသူ အားလုံးတို့ထံ တစ်ဦးချင်း SMS စာများကို ပို့ပါ။ သင်သည်သာလျှင် ​ဖြေကြားချက် မှန်သမျှကို ရရှိမည်။"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"ရရှိသူ အားလုံးထံသို့ တစ်ခုတည်းသော MMS ကို ပို့ပါ"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"မသိရ နံပါတ်"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"စာ အသစ်"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"စာ အသစ်"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM ရွေးချယ်ပေးရေးလက္ခဏာရပ်"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ရွေးချယ်ပြီး၊ SIM ရွေးချယ်ပေးသူ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"အကြောင်းအရာကို တည်းဖြတ်ပါ။"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM ကို‌ရွေးချယ်ပါ သို့မဟုတ် အကြောင်းအရာတည်းဖြတ်ခြင်းကိုရွေးချယ်ပါ။"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"အသံအား ဖမ်းယူထားရန် ထိပြီးဖိထားပါ"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"ပြောဆိုမှု အသစ် စတင်ရန်"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"စာတိုပို့ခြင်း"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"စာတိုပို့ခြင်း စာရင်း"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"စာတိုပို့ခြင်း"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"စာ အသစ်"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"ပြောဆိုမှုစာရင်း"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"စကားဝိုင်းများကို ဖွင့်နေသည်"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"စာများကို တင်နေ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"စကားပြောဆက်သွယ်မှုများကို ထပ်ပြီး ကြည့်မည်"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"နောက်ထပ် မက်ဆေ့များကြည့်ရန်"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"စကားဝိုင်းကို ဖျက်လိုက်ပြီ"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"ပြောဆိုမှုကို ဖျက်ခဲ့သည်။ အခြား စာတိုပို့ခြင်း ပြောဆိုမှုကို ပြရန် တို့ထိပါ။"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ပိတ်ဆို့ထား"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"ပြန်ဖွင့်ထားခြင်း"</string>
+ <string name="db_full" msgid="8459265782521418031">"သိုလှောင်ထားသည့်နေရာ နည်းနေပါသည်။ ဒေတာတချို့ပျောက်ဆုံးပါလိမ့်မည်။"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ချိတ်တွဲမှုများ ရွေးချယ်မည်"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"ရွေးချယ်မှုကို အတည်ပြုရန်"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ခရွေးချယ်ပြီး"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"ကျေးဇူးပြု၍ တစ်ခု သို့မဟုတ် တစ်ခုထက်ပိုသော ချိတ်တွဲမှုများကို ဖယ်ရှားပြီး နောက်တစ်ကြိမ်ကြိုးစားပါ။"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"သင့်စာတိုကို ပေးပို့ရန် သင်ကြိုးစားနိုင်ပါသည်၊ သို့သော်သင်သည် တစ်ခုသို့မဟုတ် တစ်ခုထက်ပိုသော ချိတ်တွဲမှုများကို ဖယ်ရှားခြင်းမပြုပါက ၎င်းကို ပေးပို့မှုမရှိခြင်း ဖြစ်နိုင်သည်။"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"သင်သည် မက်ဆေ့ဂျ်တစ်ခုလျှင် ဗွီဒီယိုတကားသာ ပေးပို့နိုင်သည်။ အပိုဆောင်း ဗွီဒီယိုများအား ဖယ်ရှားပြီး ထပ်မံကြိုးစားပါ။"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"စာတိုကိုပို့စဉ် ပူးတွဲချက်ကို တင် မရခဲ့ပါ။"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"မည်သို့ပင်ဖြစ်စေ ပို့မည်"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"စကားဝိုင်းအား မစတင်နိုင်ပါ"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ရွေးချယ်ပြီး၏"</string>
+</resources>
diff --git a/res/values-nb/arrays.xml b/res/values-nb/arrays.xml
new file mode 100644
index 0000000..c9efc6f
--- /dev/null
+++ b/res/values-nb/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"emne mangler"</item>
+ <item msgid="272485471009191934">"ikke noe emne"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ja"</item>
+ <item msgid="6049132459802288033">"Nei"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Takk"</item>
+ <item msgid="4881335087096496747">"Jeg er enig"</item>
+ <item msgid="2422296858597420738">"Bra"</item>
+ <item msgid="4805581752819452687">"Jeg er på vei"</item>
+ <item msgid="4746700499431366214">"OK, jeg tar kontakt med deg senere"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..7736c84
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Meldinger"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Meldinger"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Markér samtalen"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Innstillinger"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Send meldingen"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Legg ved et vedlegg"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Hjelp"</string>
+ <string name="welcome" msgid="2857560951820802321">"Velkommen"</string>
+ <string name="skip" msgid="7238879696319945853">"Hopp over"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Neste &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Neste"</string>
+ <string name="exit" msgid="1905187380359981199">"Avslutt"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Innstillinger &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Innstillinger"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Meldinger trenger tillatelse til å bruke SMS, Telefon og Kontakter."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Du kan endre tillatelser i Innstillinger &gt; Apper &gt; Meldinger &gt; Tillatelser."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Du kan endre tillatelser i Innstillinger &gt; Apper &gt; Meldinger &gt; Tillatelser."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Ofte brukte"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Alle kontakter"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Send til <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Ta bilder eller spill inn video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Velg bilder fra denne enheten"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Spill inn lyd"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Velg bilde"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Mediene er valgt."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Valget av mediene er fjernet."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> valgt"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"bilde <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" msgid="8142175029053370662">"bilde"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Spill inn lyd"</string>
+ <string name="action_share" msgid="2143483844803153871">"Del"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Nå nettopp"</string>
+ <string name="posted_now" msgid="867560789350406701">"Nå"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> minutter</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> minutt</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> timer</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> time</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"> dager</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dag</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> uker</item>
+ <item quantity="one">én uke</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> måneder</item>
+ <item quantity="one">én måned</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> år</item>
+ <item quantity="one">ett år</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Klasse 0-melding"</string>
+ <string name="save" msgid="5081141452059463572">"Lagre"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Enheten har lite lagringsplass. Meldinger-appen sletter eldre meldinger automatisk for å frigjøre lagringsplass."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Lite ledig lagringsplass"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Du kan kanskje ikke sende eller motta SMS i Meldinger før mer plass er tilgjengelig på enheten din."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Du har lite lagringsplass for SMS. Du må slette andre meldinger."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Bekreft telefonnummeret ditt"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Dette engangstrinnet sikrer at Meldinger leverer gruppemeldingene dine på riktig måte."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonnummer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Slett alle meldinger med medier"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Slett meldinger som er eldre enn <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Slett meldinger som er eldre enn <xliff:g id="DURATION">%s</xliff:g> automatisk"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorer"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Vil du slette alle meldinger med media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Vil du slette meldinger som er eldre enn <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Vil du slette meldinger som er eldre enn <xliff:g id="DURATION">%s</xliff:g> og slå av automatisk sletting?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> sa"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Du sa"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Melding fra <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Du sendte en melding"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Sender ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ikke sendt. Trykk for å prøve på nytt."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ikke sendt. Prøver på nytt …"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Send på nytt eller slett"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Ring til alarmsentralen. Tekstmeldingen kunne ikke leveres for øyeblikket."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Mislykket"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Ny MMS-melding for nedlasting"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Ny MMS-melding"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Kan ikke laste ned"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Trykk for å prøve på nytt"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Trykk for å laste ned"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Last ned eller slett"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Laster ned …"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Meldingen er utløpt eller ikke tilgjengelig"</string>
+ <string name="mms_info" msgid="3402311750134118165">"størrelse: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, utløper: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Kan ikke sende. Mottaker er ikke gyldig."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Tjenesten er ikke aktivert på nettverket"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Kunne ikke sende på grunn av et nettverksproblem"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Meldingen er utløpt eller ikke tilgjengelig"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Mangler emne)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Ukjent avsender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Levert"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Kunne ikke laste ned meldingen <xliff:g id="SUBJECT">%1$s</xliff:g> fra <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Databasehandlingen kunne ikke fullføres på grunn av lite minne."</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Meldingen er ikke sendt"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Enkelte meldinger er ikke sendt i Meldinger"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> meldinger i <xliff:g id="CONVERSATIONS">%d</xliff:g> samtaler</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> meldinger i én samtale</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Meldingen er ikke lastet ned"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Enkelte meldinger er ikke lastet ned i Meldinger"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> meldinger i <xliff:g id="CONVERSATIONS">%d</xliff:g> samtaler</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> meldinger i én samtale</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Melding til <xliff:g id="NUMBER">%1$s</xliff:g> ble ikke sendt"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Ring til alarmsentralen. Tekstmeldingen til <xliff:g id="NUMBER">%1$s</xliff:g> kunne ikke leveres for øyeblikket."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nye meldinger</item>
+ <item quantity="one">Ny melding</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Start"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kameraet er ikke tilgjengelig"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kameraet er ikke tilgjengelig"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Videoinnspilling er ikke tilgjengelig"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Kan ikke lagre media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Kan ikke ta bilde"</string>
+ <string name="back" msgid="1477626055115561645">"Tilbake"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arkivert"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Slett"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arkivér"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Fjern fra arkivet"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Slå av varsler"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Slå på varsler"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Legg til kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Last ned"</string>
+ <string name="action_send" msgid="377635240181672039">"Send"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Slett"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Vil du slette denne meldingen?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Denne handlingen kan ikke angres."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Slett"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Vil du slette disse samtalene?</item>
+ <item quantity="one">Vil du slette denne samtalen?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Slett"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Avbryt"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Til"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Velg flere bilder"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Bekreft utvalget"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Kan ikke ta opp lyd. Prøv på nytt."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Kan ikke spille av lyd. Prøv på nytt."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Kunne ikke lagre lydfil. Prøv på nytt."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Berør og hold nede"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Bilde"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Lydklipp"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontaktkort"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Last ned"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Svar via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Svar via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Svar"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> deltakere</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> deltaker</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Meg"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakten er blokkert og arkivert"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt ublokkerte &amp; uarkiverte"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> er arkivert"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> er fjernet fra arkivet"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Varsler er slått av"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Varsler er slått av"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Alt klart. Trykk på Send én gang til."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Meldinger er nå angitt som standard SMS-app."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Forkast vedlegg</item>
+ <item quantity="one">Forkast vedlegget</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Lydvedlegg"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Spill av lydvedlegg"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Sett på pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Melding fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mislykket melding fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Melding fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Usendt melding til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Sender melding til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mislykket melding til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Melding til <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mislykket melding fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Melding fra <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Usendt melding til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Sender melding til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mislykket melding til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Melding til <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidspunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Meldingen ble ikke sendt. Trykk for å prøve på nytt."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Samtale med <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Slett emnet"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Ta video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Ta et stillbilde"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Ta bilde"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Start et videoopptak"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Bytt til fullskjermkamera"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Bytt mellom for- og baksidekamera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stopp opptak og legg ved video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Slutt å filme"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Bilder i Meldinger"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> bilder er lagret i albumet «<xliff:g id="ALBUMNAME_3">%s</xliff:g>»</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bilde er lagret i albumet «<xliff:g id="ALBUMNAME_1">%s</xliff:g>»</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videoer er lagret i albumet «<xliff:g id="ALBUMNAME_3">%s</xliff:g>»</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video er lagret i albumet «<xliff:g id="ALBUMNAME_1">%s</xliff:g>»</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> tillegg er lagret i albumet «<xliff:g id="ALBUMNAME_3">%s</xliff:g>»</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> tillegg er lagret i albumet «<xliff:g id="ALBUMNAME_1">%s</xliff:g>»</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> tillegg er lagret i «Nedlastinger»</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> tillegg er lagret i «Nedlastinger»</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> vedlegg er lagret</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> vedlegg er lagret</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Kunne ikke lagre <xliff:g id="QUANTITY_1">%d</xliff:g> vedlegg</item>
+ <item quantity="one">Kunne ikke lagre <xliff:g id="QUANTITY_0">%d</xliff:g> vedlegg</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS-vedlegget er lagret"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Innstillinger"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arkivert"</string>
+ <string name="action_close" msgid="1840519376200478419">"Lukk"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avansert"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Feilsøk"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Varsler"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Lyd"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Stille"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrer"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokkert"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Leveringsrapporter for SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Be om leveringsrapport for hver SMS du sender"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatisk henting"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Hent MMS-meldinger automatisk"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automatisk henting under roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Hent MMS automatisk ved romaning"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Gruppemeldinger"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Bruk MMS for å sende én melding med flere mottakere"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Standard SMS-app"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Standard SMS-app"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Telefonnummeret ditt"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Ukjent"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Lyder for utgående meldinger"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Dump SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Dump rådata fra mottatte SMS-er i en ekstern lagringsfil"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Dump MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Dump rådata fra mottatte MMS-er i en ekstern lagringsfil"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Trådløse varsler"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Meldingsalternativer"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopiér teksten"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Se detaljer"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Slett"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Videresend"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Meldingsinformasjon"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Bildemelding"</string>
+ <string name="from_label" msgid="1947831848146564875">"Fra: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Til: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Sendt: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Mottatt: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Emne: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Størrelse: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-kort: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Høy"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Vanlig"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Lav"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM-kort <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Skjult avsenderadresse"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Meldingen kan ikke sendes mens vedleggene lastes inn."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Kan ikke laste inn vedlegg. Prøv på nytt."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Nettverket er ikke klart. Prøv på nytt."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Slett teksten"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Veksle mellom å skrive inn tekst og tall"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Legg til flere deltakere"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Bekreft deltakere"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Start ny samtale"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Velg dette elementet"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Spill av videoen"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Personer-appen og alternativer"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Feilsøk"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Personer-appen og alternativer"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Generelt"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Folk i denne samtalen"</string>
+ <string name="action_call" msgid="6596167921517350362">"Start en samtale"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Send melding"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Send melding&lt;br/&gt;&lt;small&gt;fra <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Send bilder</item>
+ <item quantity="one">Send bilde</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Send lydklipp</item>
+ <item quantity="one">Send lydklipp</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Send videoer</item>
+ <item quantity="one">Send video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Send kontaktkort</item>
+ <item quantity="one">Send kontaktkort</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Send vedlegg</item>
+ <item quantity="one">Send vedlegg</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> vedlegg er klare til å sendes</item>
+ <item quantity="one">Ett vedlegg er klart til å sendes</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Send tilbakemelding"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Se i Google Play-butikken"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versjonsinformasjon"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versjon %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Lisenser for åpen kildekode"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Varsler"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Vedleggsgrensen er nådd"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Kunne ikke laste inn vedlegg."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Vil du legge til nummeret i kontakter?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Legg til kontakten"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Emne"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Emne: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Laster inn kontaktkortet"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kunne ikke laste kontaktkort"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Se kontaktkortet"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakter</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktkort"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Fødselsdag"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notater"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Videresend meldingen"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Svar"</string>
+ <string name="plus_one" msgid="9010288417554932581">"1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS er deaktivert"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"For å sende meldinger, angi Meldinger som standard SMS-app"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Angi Meldinger som standard SMS-app"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Endre"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"For å motta meldinger, angi Meldinger som standard SMS-app"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Ingen foretrukne SIM-kort er valgt for å sende SMS-meldinger"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Denne appen er ikke tillatt av enhetseieren."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"For mange deltakere i en samtale"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Ugyldige kontakter</item>
+ <item quantity="one">Ugyldig kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kunne ikke laste inn kamerabilde"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Du: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Utkast"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Når du starter en ny samtale, vises den her"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arkiverte samtaler vises her"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Laster inn samtaler ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Bilde"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Lydklipp"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Instream-video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontaktkort"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Angre"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Prøv på nytt"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Skriv inn et kontaktnavn eller -telefonnummer for å starte en ny melding"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokkér"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokkér <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Opphev blokkeringen av <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Vil du blokkere <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Du fortsetter å motta meldinger fra dette nummeret, men blir ikke lenger varslet. Denne samtalen blir arkivert."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokkerte kontakter"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"OPPHEV BLOKKERING"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokkerte kontakter"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Velg et bilde fra dokumentbiblioteket"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Sender meldingen"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Meldingen er sendt"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobildata er slått av. Sjekk innstillingene dine."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Kan ikke sende meldinger i flymodus"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Kan ikke sende meldingen"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Meldingen er nedlastet"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobildata er slått av. Sjekk innstillingene dine."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Kan ikke laste ned meldinger i flymodus"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Meldingen kunne ikke lastes ned"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Null"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Én"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"To"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tre"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Fire"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Fem"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seks"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sju"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Åtte"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Ni"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Kan ikke sende meldinger med <xliff:g id="CARRIERNAME">%1$s</xliff:g> – feil <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Kan ikke sende meldinger med ukjent operatør – feil <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"V.send: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Meldingen er ikke sendt: Tjenesten er ikke aktivert på nettverket"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Meldingen ble ikke sendt: ugyldig måladresse"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Meldingen ble ikke sendt: ugyldig melding"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Meldingen ble ikke sendt: innholdet støttes ikke"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Meldingen ble ikke sendt: meldingen støttes ikke"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Meldingen ble ikke sendt: Den var for stor."</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Ny melding"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Se"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Bilde"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Kunne ikke finne en passende app"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Fjern mottakeren"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Ny melding"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Avbryt"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Rediger tilgangspunkt"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ikke angitt"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Navn"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN-navn"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy-tjener for MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-type"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Slett APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Ny APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Lagre"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Forkast"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Navnefeltet kan ikke være tomt."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN kan ikke være tomt."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-feltet må bestå av tre siffer."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC-feltet må bestå av to eller tre siffer."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Gjenoppretter standard APN-innstillinger."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Tilbakestill til standard"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Tilbakestilling av standard APN-innstillinger er fullført."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Uten navn"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Navn på tilgangspunkt"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-er"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Ny APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Innstillingene for navn på tilgangspunkt er ikke tilgjengelig for denne brukeren"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Vil du kopiere til utklippstavlen?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiér"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"til <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Generelt"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avansert"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Generelle innstillinger"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Avanserte innstillinger"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"«<xliff:g id="SIM_NAME">%s</xliff:g>»-SIM-kort"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Send SMS-meldinger til alle mottakere. Bare du mottar svarene."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Send én enkelt MMS til alle mottakere"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Ukjent nummer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Ny melding"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Ny melding."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-kortvelger"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> er valgt – SIM-kortvelger"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Rediger emne"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Velg SIM-kort eller rediger emnet"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Trykk og hold inne for å ta opp lyd"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Start ny samtale"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Meldinger"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Samtaleliste"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Meldinger"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Ny melding"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Samtaleliste"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Laster inn samtaler"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Laster inn e-postmeldinger"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Se flere samtaler"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Se flere e-poster"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Samtalen er slettet"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Samtalen er slettet. Trykk for å se en annen samtale i Meldinger"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blokkert"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Blokkeringen er opphevet"</string>
+ <string name="db_full" msgid="8459265782521418031">"Lite lagringsplass ledig. Noen data kan gå tapt."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Velg vedlegg"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Bekreft utvalget"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> er valgt"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Fjern ett eller flere vedlegg og prøv på nytt."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Du kan prøve å sende meldingen din, men det er ikke sikkert at den kommer frem med mindre du fjerner ett eller flere vedlegg."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Du kan bare sende én video per melding. Fjern overflødige videoer og prøv igjen."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Meldinger kunne ikke laste inn vedlegget."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Send likevel"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Kunne ikke starte samtalen"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> er valgt"</string>
+</resources>
diff --git a/res/values-ne-rNP/arrays.xml b/res/values-ne-rNP/arrays.xml
new file mode 100644
index 0000000..614436d
--- /dev/null
+++ b/res/values-ne-rNP/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"शीर्षक नभएको"</item>
+ <item msgid="272485471009191934">"विषय छैन"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"हो"</item>
+ <item msgid="6049132459802288033">"हैन"</item>
+ <item msgid="3084376867445867895">"ठीक छ"</item>
+ <item msgid="3155097332660174689">"हाहा"</item>
+ <item msgid="2611328818571146775">"धन्यवाद"</item>
+ <item msgid="4881335087096496747">"म सहमत छु"</item>
+ <item msgid="2422296858597420738">"राम्रो"</item>
+ <item msgid="4805581752819452687">"मेरो बाटोमा"</item>
+ <item msgid="4746700499431366214">"ठीक छ, तपाईंलाई पछिको लागि मलाई अनुमति दिनुहोस्"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..c9e30e6
--- /dev/null
+++ b/res/values-ne-rNP/strings.xml
@@ -0,0 +1,521 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"सन्देश पठाइदै"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"सन्देश पठाइदै"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"कुराकानी चयन गर्नुहोस्"</string>
+ <string name="action_settings" msgid="1329008122345201684">"सेटिङ्हरू"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"सन्देश पठाउनुहोस्"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"एक अनुलग्नक थप गर्नुहोस्"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"मद्दत"</string>
+ <string name="welcome" msgid="2857560951820802321">"स्वागतम"</string>
+ <string name="skip" msgid="7238879696319945853">"छोड्नुहोस्"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"अर्को &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"अर्को"</string>
+ <string name="exit" msgid="1905187380359981199">"बाहिर"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"सेटिङहरू &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"सेटिङहरू"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"सन्देश प्रवाहलाई SMS, फोन र सम्पर्क ठेगानाहरूमा अनुमतिको आवश्यक पर्छ।"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"तपाईँ अनुमतिहरूलाई सेटिङ अनुप्रयोगहरू &gt; सन्देश प्रवाह &gt; अनुमतिहरूमा परिवर्तन गर्न सक्नु हुनेछ।"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"तपाईँ अनुमतिहरूलाई सेटिङ अनुप्रयोगहरू, सन्देश प्रवाह, अनुमतिहरूमा परिवर्तन गर्न सक्नु हुनेछ।"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"नियमित"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"सबै सम्पर्कहरू"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> मा पठाउनुहोस्"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"तस्बिर वा भिडियो लिनुहोस्"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"यस उपकरणबाट तस्बिरहरू छान्नुहोस्"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ध्वनि रेकर्ड गर्नुहोस्"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"फोटो छान्नुहोस्"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"यस मिडिया चयन गरिएको छ।"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"यस मिडियाको चयन हटाइएको छ।"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> चयन गरियो"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"छवि <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" msgid="8142175029053370662">"छवि"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ध्वनि रेकर्ड गर्नुहोस्"</string>
+ <string name="action_share" msgid="2143483844803153871">"साझेदारी गर्नुहोस्"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"अहिले भर्खर"</string>
+ <string name="posted_now" msgid="867560789350406701">"अब"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> मिनेट</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> मिनेट</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> घन्टा</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> घन्टा</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> दिन</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> दिन</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> हप्ता</item>
+ <item quantity="one"> एक हप्ता</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> महिना</item>
+ <item quantity="one"> एक महिना</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> वर्ष</item>
+ <item quantity="one"> एक वर्ष</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"वर्ग ० सन्देश"</string>
+ <string name="save" msgid="5081141452059463572">"सुरक्षित गर्नुहोस्"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"यन्त्रमा ठाउँ कम छ। ठाउँ खाली गर्न सन्देश प्रवाहले पुराना सन्देशहरूलाई स्वचालित रूपमा मेट्ने छ।"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"भण्डारण ठाउँ सकिँदै छ"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"तपाईँको यन्त्रमा थप ठाउँ उपलब्ध नभएसम्म सन्देश प्रवाहले सन्देश पठाउन वा प्राप्त नगर्न सक्छ।"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS भण्डारणमा कमी। तपाईँले सन्देशहरु हटाउनु पर्नेहुन्छ।"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"आफ्नो फोन नम्बर पुष्टि गर्नुहोस्"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"यो एक पटके चरणले सन्देश प्रवाहले तपाईँको समूह सन्देशहरू उचित रूपमा डेलिभर गर्ने छ भन्ने सुनिश्चित गर्ने छ।"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"फोन नम्बर"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"मीडियाको साथमा सबै सन्देशहरु मेटाउनुहोस्"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> भन्दा पुराना सन्देशहरुलाई मेट्नुहोस्"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> भन्दा पुरानो सन्देशहरू स्वत: मेट्नुहोस्"</string>
+ <string name="ignore" msgid="7063392681130898793">"बेवास्ता गर्नुहोस्"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"मिडिया भएको सन्देशहरू मेट्ने?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> भन्दा पुरानो सन्देशहरू मेट्ने?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> भन्दा पुरानो सन्देशहरू मेटाउनुहुन्छ र स्वचालित-मेट्ने सक्षम गर्नुहुन्छ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ले भन्नुभयो"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"तपाईंले भन्नुभयो"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> बाट सन्देश"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"तपाईंले एक सन्देश पठाउनुभयो"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"पठाउँदै..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"पठाइएको छैन। पुन: प्रयास गर्न छुनुहोस्।"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"पठाइएको छैन। पुन: प्रयास गर्दै ..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"पुन: पठाउनुहोस् वा मेट्नुहोस्"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"कृपया आकस्मिक सेवाहरूमा आवाज कल गर्नुहोस्। तपाईंको पाठ सन्देश यो समयमा डेलिभर हुन सक्दैन।"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"विफल भयो"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"डाउनलोड गर्न नयाँ MMS सन्देश"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"नयाँ MMS सन्देश"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"डाउनलोड गर्न सकिएन"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"पुनः प्रयास गर्न छुनुहोस्"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"डाउनलोड गर्न छुनुहोस्"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"डाउनलोड वा मेट्नुहोस्"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"डाउनलोड हुँदै..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"सन्देशको अवधि सकियो वा उपलब्ध छैन"</string>
+ <string name="mms_info" msgid="3402311750134118165">"आकार: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, समाप्ति: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"पठाउन सकिएन। प्रापक मान्य छैन।"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"नेटवर्कमा सेवा सक्रिय छैन"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"नेटवर्कको समस्याको कारण पठाउन सकिएन"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"सन्देशको अवधि सकियो वा उपलब्ध छैन"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(कुनै विषय छैन)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"अज्ञात प्रेषक"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"पुर्याइयो"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> बाट सन्देश <xliff:g id="SUBJECT">%1$s</xliff:g> डाउनलोड गर्न सकिएन।"</string>
+ <string name="low_memory" msgid="5300743415198486619">"कम मेमोरीका कारण डेटाबेस कार्य पुरा गर्न सकिएन।"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"सन्देश पठाइएन"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"केही सन्देशहरू सन्देश प्रवाहमा पठाइएनन्"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> वार्तालापमा <xliff:g id="MESSAGES_1">%d</xliff:g> सन्देशहरू</item>
+ <item quantity="one"> एउटा वार्तालापमा <xliff:g id="MESSAGES_0">%d</xliff:g> सन्देशहरू</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"सन्देश डाउनलोड भएन"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"सन्देशहरू सन्देश प्रवाहमा डाउनलोड भएनन्"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> वार्तालापमा <xliff:g id="MESSAGES_1">%d</xliff:g> सन्देशहरू</item>
+ <item quantity="one"> एउटा वार्तालापमा <xliff:g id="MESSAGES_0">%d</xliff:g> सन्देशहरू</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> मा सन्देश पठाइएन"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"कृपया आकस्मिक सेवाहरूमा आवाज कल गर्नुहोस्। <xliff:g id="NUMBER">%1$s</xliff:g> मा तपाईंको पाठ सन्देश यो समयमा डेलिभर हुन सक्दैन।"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> नयाँ सन्देशहरू</item>
+ <item quantity="one"> नयाँ सन्देश</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"सुरु गर्नुहोस्"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"क्यामेरा उपलब्ध छैन"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"क्यामेरा उपलब्ध छैन"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"भिडियो क्याप्चर उपलब्ध छैन"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"मिडिया बचत गर्न सकिँदैन"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"तस्वीर लिन सक्दैन"</string>
+ <string name="back" msgid="1477626055115561645">"पछाडि"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"अभिलेख बनाइयो"</string>
+ <string name="action_delete" msgid="4076795795307486019">"मेटाउनुहोस्"</string>
+ <string name="action_archive" msgid="5437034800324083170">"अभिलेख"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"अभिलेखबाट निकाल्नुहोस्"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"सूचनाहरू बन्द गर्नुहोस्"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"सूचनाहरू सुचारु गर्नुहोस्"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"सम्पर्क थप्नुहोस्"</string>
+ <string name="action_download" msgid="7786338136368564146">"डाउनलोड गर्नुहोस्"</string>
+ <string name="action_send" msgid="377635240181672039">"पठाउनुहोस्"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"मेटाउनुहोस्"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"यस सन्देश मेटाउने हो?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"यो कार्य पूर्ववत् पार्न सकिँदैन।"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"मेटाउनुहोस्"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other"> यी वार्तालापहरू मेटाउने हो?</item>
+ <item quantity="one"> यो वार्तालाप मेटाउने हो?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"मेटाउनुहोस्"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"रद्द गर्नुहोस्"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"प्रापक"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"बहु तस्बिर चयन गर्नुहोस्"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"चयन यकिन गर्नुहोस्"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"अडियो रेकर्ड गर्न सकिँदैन। पुन: प्रयास गर्नुहोस्।"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"अडियो बजाउन सकिएन। पुन: प्रयास गर्नुहोस्।"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"अडियो सुरक्षित गर्न सकेन। फेरी प्रयास गर्नुहोस्।"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"छुनुहोस् र होल्ड गर्नुहोस्"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"चित्र"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ध्वनि क्लिप"</string>
+ <string name="notification_video" msgid="4331423498662606204">"भिडियो"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"सम्पर्क कार्ड"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"डाउनलोड गर्नुहोस्"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS मार्फत जवाफ दिनुहोस्"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS मार्फत जवाफ दिनुहोस्"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"जवाफ पठाउनुहोस्"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> सहभागीहरू</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> सहभागी</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"म"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"सम्पर्क अवरुद्ध; अभिलेख राखियो"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"सम्पर्क खुल्ला गरियो &amp;amp अभिलेख हटाइयो"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g>अभिलेख गरियो"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> अभिलेख हटाउनुहोस्"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"सूचनाहरू निष्क्रिय पारिए"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"सूचनाहरू सक्रिय पारिए"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"सबै ठीक छ। पुन: पठाउन छुनुहोस्।"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"सन्देश प्रवाह सफलतापूर्वक पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट भयो।"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other"> संलग्नकहरू खारेज गर्नुहोस्</item>
+ <item quantity="one"> संलग्नक खारेज गर्नुहोस्</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"अडियो संलग्नक"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"अडियो संलग्नक प्ले गर्नुहोस्"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"पज गर्नुहोस्"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> बाट प्राप्त सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>।"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> बाट विफल सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> बाट सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> मा नपठाइएको सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> मा सन्देश पठाउँदै: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> मा विफल सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> मा सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> बाट विफल सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> बाट सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> मा नपठाइएको सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> मा सन्देश पठाउँदै: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> मा विफल सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> मा सन्देश: <xliff:g id="MESSAGE">%s</xliff:g>. समय: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"असफल सन्देश। पुन: प्रयास गर्न छुनुहोस्।"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> सँग कुराकानी"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"विषय मेट्नुहोस्"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"भिडियो खिच्नुहोस्"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"स्थिर छाँया लिदै"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"फोटो लिनुहोस्"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"भिडियो रेकर्ड सुरु गर्नुहोस्"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"पूरा पर्दा क्यामेरा खोल्नुहोस्"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"अगाडि र पछाडि क्यामेरा बीच बदल्नुहोस्"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"भिडियो रेकर्ड गर्न र संलग्न रोक्नुहोस्"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"भिडियो रेकर्ड रोक्नुहोस्"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"सन्देश प्रवाह तस्बिरहरू"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> तस्बिरहरू \"<xliff:g id="ALBUMNAME_3">%s</xliff:g> \" एल्बममा सुरक्षित गरियो</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> तस्बिर \"<xliff:g id="ALBUMNAME_1">%s</xliff:g> \" एल्बममा सुरक्षित गरियो</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> भिडियोहरू \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" एल्बममा सुरक्षित गरियो</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> भिडियो \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" एल्बममा सुरक्षित गरियो</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> संलग्नकहरू \" <xliff:g id="ALBUMNAME_3">%s</xliff:g> \" एल्बममा सुरक्षित गरियो</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> संलग्नक \" <xliff:g id="ALBUMNAME_1">%s</xliff:g> \" एल्बममा सुरक्षित गरियो</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नकहरू \"डाउनलोडहरू\" मा सुरक्षित गरियो</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> संलग्नक \"डाउनलोडहरू\" मा सुरक्षित गरियो</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नकहरू सुरक्षित गरियो</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> संलग्नक सुरक्षित गरियो</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"> <xliff:g id="QUANTITY_1">%d</xliff:g> संलग्नकहरू सुरक्षित गर्न सकेन</item>
+ <item quantity="one"> <xliff:g id="QUANTITY_0">%d</xliff:g> संलग्नक सुरक्षित गर्न सकेन</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"संलग्न MMS सुरक्षित गरियो"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"सेटिङहरू"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"अभिलेख बनाइयो"</string>
+ <string name="action_close" msgid="1840519376200478419">"बन्द गर्नुहोस्"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"उन्नत"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"डिबग"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"सूचनाहरू"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"आवाज"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"मौन"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"कम्पन गर्नुहोस्"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"अवरूद्ध गरियो"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS वितरण प्रतिवेदनहरू"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"तपाईँले पठाउनु हुने हरेक SMS को लागि पुगेको खबर अनुरोध गर्नुहोस्"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"स्वत:प्राप्ति"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS सन्देशहरु स्वत: रूपमा पुनः प्राप्त गर्नुहोस्"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"रोमिङ स्वतः पुनः प्राप्ति"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"रोमिङमा हुँदा MMS स्वचालित रूपमा प्राप्त गर्नुहोस्"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"समूहिक सन्देश पठाइँदै"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"जब त्यहाँ बहुप्राप्तकर्ताहरू हुन्छन् तब सन्देश पठाउनको लागि MMS प्रयोग गर्नुहोस्।"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"पूर्वनिर्धारित SMS अनुप्रयोग"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"पूर्वनिर्धारित SMS अनुप्रयोग"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"तपाईँको फोन नम्बर"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"अज्ञात"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"बाहिर जाने सन्देशका लागि ध्वनिहरू"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS फाल्नुहोस्"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"प्राप्त भएको MMS अप्रशोधित डेटा बाह्य भण्डारण फाइलमा फाल्नुहोस्"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS फाल्दिनुहोस्"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"प्राप्त भएको MMS अप्रशोधित डेटा बाह्य भण्डारण फाइलमा फाल्नुहोस्"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ताररहित सतर्कताहरू"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"सन्देश विकल्पहरू"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"पाठ प्रतिलिपि गर्नुहोस्"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"विवरणहरू हेर्नुहोस्"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"मेटाउनुहोस्"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"फर्वार्ड गर्नुहोस्"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"सन्देश विवरणहरू"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"प्रकार: "</string>
+ <string name="text_message" msgid="7415419755252205721">"पाठ सन्देश"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"मल्टिमिडिया सन्देश"</string>
+ <string name="from_label" msgid="1947831848146564875">"प्रेषक: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"प्रापक: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"पठाइयो: "</string>
+ <string name="received_label" msgid="4442494712757995203">"प्राप्त: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"विषय: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"आकार: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"प्राथमिकता: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"उच्च"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"सामान्य"</string>
+ <string name="priority_low" msgid="7398724779026801851">"कम"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"प्रेषकको ठेगाना लुकेको"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"संलग्नहरू लोड गर्दा सन्देश पठाउन सक्दैन।"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"संलग्नक लोड गर्न सकिएन। पुन: प्रयास गर्नुहोस्।"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"नेटवर्क तयार छैन। पुन: प्रयास गर्नुहोस्।"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"पाठ मेट्नुहोस्"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"पाठ र संख्या बीच प्रविष्टि गर्न स्विच गर्नुहोस्"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"थप सहभागी थप्नुहोस्"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"सहभागीहरूको पुष्टि गर्नुहोस्"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"नयाँ कुराकानी सुरू गर्नुहोस्"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"यो वस्तु चयन गर्नुहोस्"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"भिडियो चलाउनुहोस्"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"मानिस र विकल्पहरू"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"डिबग"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"मानिसहरू र विकल्पहरु"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"सामान्य"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"यस कुराकानीमा मानिसहरू"</string>
+ <string name="action_call" msgid="6596167921517350362">"कल गर्नुहोस्"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"सन्देश पठाउनुहोस्"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"सन्देश पठाउनुहोस्&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&amp;gt बाट"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other"> तस्बिरहरू पठाउनुहोस्</item>
+ <item quantity="one"> तस्बिर पठाउनुहोस्</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other"> अडियोहरू पठाउनुहोस्</item>
+ <item quantity="one"> अडियो पठाउनुहोस्</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other"> भिडियोहरू पठाउनुहोस्</item>
+ <item quantity="one"> भिडियो पठाउनुहोस्</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other"> सम्पर्क कार्डहरू पठाउनुहोस्</item>
+ <item quantity="one"> सम्पर्क कार्ड पठाउनुहोस्</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other"> संलग्नक पठाउनुहोस्</item>
+ <item quantity="one"> संलग्नक पठाउनुहोस्</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> संलग्नकहरू पठाउनको लागि तयार</item>
+ <item quantity="one">एउटा संलग्नक पठाउनको लागि तयार</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"प्रतिक्रिया पठाउनुहोस्"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store मा हेर्नुहोस्"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"संस्करण जानकारी"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for subtitle_format_for_version_number (446082111129170749) -->
+ <skip />
+ <string name="menu_license" msgid="1157954180701135202">"खुला स्रोत इजाजतपत्रहरू"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"अधिसूचनाहरू"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"संलग्नकको सीमा पुग्यो"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"संलग्नक लोड गर्न असफल।"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"सम्पर्कहरूमा थप्नुहुन्छ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"सम्पर्क थप्नुहोस्"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"विषय"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"विषय: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"सम्पर्क कार्ड लोड गर्दै"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"सम्पर्क कार्ड लोड गर्न सकिएन"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"सम्पर्क कार्ड हेर्नुहोस्"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> सम्पर्कहरू</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> सम्पर्क</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"सम्पर्क कार्डहरू"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"जन्मदिन"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"टिप्पणीहरू"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"सन्देश फर्वार्ड गर्नुहोस्"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"जवाफ पठाउनुहोस्"</string>
+ <string name="plus_one" msgid="9010288417554932581">"‌+१"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS असक्षम गरियो"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"पठाउनका लागि सन्देश प्रवाहलाई पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट गर्नुहोस्"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"सन्देश प्रवाहलाई पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट गर्नुहोस्"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"परिवर्तन गर्नुहोस्"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"सन्देशहरू प्राप्त गर्न, सन्देश प्रवाहलाई पूर्वनिर्धारित SMS अनुप्रयोगको रूपमा सेट गर्नुहोस्"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS सन्देशहरू पठाउनको लागि मान्यता प्राप्त SIM चयन गरिएको छैन।"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"यो अनुप्रयोग उपकरण मालिकद्वारा अनुमति छैन।"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ठीक छ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"कुराकानीमा धेरै सहभागीहरू"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">अमान्य सम्पर्कहरू</item>
+ <item quantity="one"> अमान्य सम्पर्क</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"क्यामेराको तस्बिर लोड गर्न सकिएन"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"तपाईँ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"मस्यौदा"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"तपाईँ नयाँ कुराकानी सुरु भएपछि, तपाईँ यसलाई यहाँ सूचीबद्ध देख्नुहुने छ"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"अभिलेख कुराकानी यहाँ देखा पर्नेछ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"कुराकानीहरू लोड हुँदै..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"तस्बिर"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"अडियो क्लिप"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"भिडियो"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"सम्पर्क कार्ड"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"अनडु गर्नुहोस्"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"पुनःप्रयास गर्नुहोस्"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"सम्पर्क नाम वा फोन नम्बर प्रविष्ट गरी नयाँ सन्देश सुरु गर्नुहोस्"</string>
+ <string name="action_block" msgid="9032076625645190136">"रोक्नुहोस्"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"अवरुद्ध <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"अनब्लक <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"अवरुद्ध गर्नुस् <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"तपाईँ यो नम्बरबाट सन्देश प्राप्त गरिरहनु हुनेछ तर तपाईँलाई सूचीत गरिने छैन। यो कुराकानीलाई संग्रह गरिने छ।"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"अवरुद्ध सम्पर्कहरू"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"UNBLOCK"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"अवरुद्ध सम्पर्कहरू"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"कागजात लाइब्रेरीबाट तस्बिर छान्नुहोस्"</string>
+ <string name="sending_message" msgid="6363584950085384929">"सन्देश पठाउँदै"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"सन्देश पठाइयो"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"सेलुलर डेटा बन्द छ। आफ्नो सेटिङ जाँच गर्नुहोस्।"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"हवाइजहाज मोडमा सन्देश पठाउन सक्नुहुन्‍न्"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"सन्देश पठाउन सकिएन"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"सन्देश डाउनलोड गरियो"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"सेलुलर डाटा बन्द गरिएको छ। आफ्नो सेटिङहरू जाँच्नुहोस्।"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"हवाइजहाज मोडमा सन्देशहरू डाउनलोड गर्न सक्दैन"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"सन्देश डाउनलोड गर्न सकिएन"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"शून्य"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"एक"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"दुई"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"तीन"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"चार"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"पाँच"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"छ"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"सात"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"आठ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"नौ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> मार्फत सन्देश पठाउन सक्दैन, त्रुटि <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"अज्ञात वाहकमार्फत सन्देश पठाउन सक्दैन, त्रुटि <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"फर्वाड गर्नुहोस्: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"सन्देश पठाइएको छैन: सञ्जालमा सेवा सक्रिय छैन"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"सन्देश पठाइएको छैन: अवैध गन्तव्य ठेगाना"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"सन्देश पठाइएको छैन: अमान्य सन्देश"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"सन्देश पठाइएको छैन: असमर्थित सामाग्री"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"सन्देश पठाइएको छैन: असमर्थित सन्देश"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"सन्देश पठाइएन: निकै ठूलो छ"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"नयाँ सन्देश"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"हेर्नुहोस्"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"तस्बिर"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"उपयुक्त अनुप्रयोग फेला पार्न सकिएन"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"प्राप्तकर्ता हटाउनुहोस्"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"नयाँ सन्देश"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"रद्द गर्नुहोस्"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"पहुँच बिन्दु सम्पादन गर्नुहोस्"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"सेट गरेको छैन"</string>
+ <string name="apn_name" msgid="1572691851070894985">"नाम"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS प्रोक्सी"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS पोर्ट"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN प्रकार"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN मेटाउनुहोस्"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"नयाँ APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"सुरक्षित गर्नुहोस्"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"त्याग्नुहोस्"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"नाम क्षेत्र खाली हुन सक्दैन।"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN खाली हुन सक्दैन।"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC क्षेत्र कम्तिमा ३ अङ्कको हुनु पर्छ।"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC क्षेत्रमा २ वा ३ अंकहरू हुनु पर्दछ।"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"पूर्वनिर्धारित APN सेटिङ्हरू पुनःप्राप्त गर्दै।"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"पूर्वनिर्धारितमा पुनःसेट गर्नुहोस्"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"पूर्वनिर्धारित APN सेटिङ्हरू पुनः सेट गर्ने काम पूरा भयो।"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"शीर्षक विहिन"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"पहुँच बिन्दु नामहरू"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"नयाँ APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"पहुँच बिन्दु नामको सेटिङ्हरू यो प्रयोगकर्ताको लागि उपलब्ध छैन"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"क्लिपबोर्डमा प्रतिलिपि गर्ने?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"प्रतिलिपि गर्नुहोस्"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> लाई"</string>
+ <string name="general_settings" msgid="5409336577057897710">"सामान्य"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"उन्नत"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"साधारण सेटिङ्हरू"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"उन्नत सेटिङ्हरू"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"सबै प्रापकलाई व्यक्तिगत SMS सन्देशहरू पठाउनुहोस्। केवल तपाईँले कुनै पनि उत्तर प्राप्त गर्नुहुने छ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"सबै प्रापकलाई एउटा एकल MMS पठाउनुहोस्"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"अज्ञात संख्या"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"नयाँ सन्देश"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"नयाँ सन्देश।"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"सिम चयनकर्ता"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> चयन गरियो, SIM चयनकर्ता"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"विषय सम्पादन गर्नुहोस्"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"सिम चयन गर्नुहोस् वा विषय सम्पादन गर्नुहोस्"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"अडियो रेकर्ड गर्न छुनुहोस् र होल्ड गर्नुहोस्"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"नयाँ कुराकानी सुरू गर्नुहोस्"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"सन्देश प्रवाह"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"सन्देश प्रवाह सूची"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"सन्देश प्रवाह"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"नयाँ सन्देश"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"कुराकानी सूची"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"कुराकानीहरू लोड हुँदै..."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"सन्देशहरू लोड हुँदै"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"थप कुराकानीहरू हेर्नुहोस्"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"थप सन्देशहरू हेर्नुहोस्"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"कुराकानी मेटिएको छ"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"वार्तालाप मेटियो। एउटा फरक सन्देश प्रवाह वार्तालाप देखाउन छुनुहोस्"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"अवरूद्ध गरियो"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"अवरोध हटाइयो"</string>
+ <string name="db_full" msgid="8459265782521418031">"भण्डारण ठाउँ कम छ। केही डाटा हराउन सक्छ।"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"संलग्नकहरू चयन गर्नुहोस्"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"चयन पुष्टि गर्नुहोस्"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> चयन गरियो"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"कृपया एक वा बढी संलग्नकहरू हटाउनुहोस् र फेरि प्रयास गर्नुहोस्।"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"तपाईं आफ्नो सन्देश पठाउन प्रयास गर्न सक्नुहुन्छ, तर तपाईंले एक वा बढी संलग्नकहरू नहटाएसम्म यो डेलिभर नहुन सक्छ।"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"तपाईँले प्रति सन्देश एउटा मात्र भिडियो पठाउन सक्नुहुन्छ। अतिरिक्त भिडियो हटाउनुहोस् र फेरि प्रयास गर्नुहोस्।"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"संलग्नक लोड गर्न सन्देश प्रवाह असफल भयो।"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"जसरी भए पनि पठाउनुहोस्"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"कुराकानी सुरु गर्न सकेन"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> चयन गरियो"</string>
+</resources>
diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml
new file mode 100644
index 0000000..adfdcbe
--- /dev/null
+++ b/res/values-nl/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"geen onderwerp"</item>
+ <item msgid="272485471009191934">"geenonderwerp"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ja"</item>
+ <item msgid="6049132459802288033">"Nee"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Haha"</item>
+ <item msgid="2611328818571146775">"Hartelijk dank"</item>
+ <item msgid="4881335087096496747">"Ik ga akkoord"</item>
+ <item msgid="2422296858597420738">"Mooi"</item>
+ <item msgid="4805581752819452687">"Ik ben onderweg"</item>
+ <item msgid="4746700499431366214">"Hier kom ik later op terug"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..4f1a53c
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Berichten"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Berichten"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Gesprek selecteren"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Instellingen"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Bericht verzenden"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Een bijlage toevoegen"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Help"</string>
+ <string name="welcome" msgid="2857560951820802321">"Welkom"</string>
+ <string name="skip" msgid="7238879696319945853">"Overslaan"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Volgende &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Volgende"</string>
+ <string name="exit" msgid="1905187380359981199">"Sluiten"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Instellingen &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Instellingen"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Berichten heeft toegang tot Sms, Telefoon en Contacten nodig."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"U kunt rechten wijzigen via \'Instellingen\' &gt; \'Apps\' &gt; \'Berichten\' &gt; \'Rechten\'."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"U kunt rechten wijzigen via \'Instellingen\' &gt; \'Apps\' &gt; \'Berichten\' &gt; \'Rechten\'."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Veelgebruikt"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Alle contacten"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Verzenden naar <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Foto\'s of video vastleggen"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Afbeeldingen kiezen op dit apparaat"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Audio opnemen"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Foto kiezen"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Het medium is geselecteerd."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Het medium is gedeselecteerd."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> geselecteerd"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"afbeelding <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" msgid="8142175029053370662">"afbeelding"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Audio opnemen"</string>
+ <string name="action_share" msgid="2143483844803153871">"Delen"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Zojuist"</string>
+ <string name="posted_now" msgid="867560789350406701">"Nu"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min.</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> uur</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> uur</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dagen</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dag</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> weken</item>
+ <item quantity="one">één week</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> maanden</item>
+ <item quantity="one">één maand</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> jaar</item>
+ <item quantity="one">één jaar</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Berichtklasse 0"</string>
+ <string name="save" msgid="5081141452059463572">"Opslaan"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Er is weinig ruimte vrij op het apparaat. Oudere berichten worden automatisch verwijderd om ruimte vrij te maken."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Opslagruimte is bijna vol"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Berichten verzendt of ontvangt mogelijk geen berichten totdat er meer ruimte beschikbaar is op uw apparaat."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Weinig opslagruimte voor sms-berichten beschikbaar. Je moet mogelijk berichten verwijderen."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Uw telefoonnummer bevestigen"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Deze eenmalige stap zorgt ervoor dat Berichten uw groepsberichten correct aflevert."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefoonnummer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Alle berichten met media verwijderen"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Berichten ouder dan <xliff:g id="DURATION">%s</xliff:g> verwijderen"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Berichten ouder dan <xliff:g id="DURATION">%s</xliff:g> automatisch verwijderen"</string>
+ <string name="ignore" msgid="7063392681130898793">"Negeren"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Alle berichten met media verwijderen?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Berichten ouder dan <xliff:g id="DURATION">%s</xliff:g> verwijderen?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Berichten ouder dan <xliff:g id="DURATION">%s</xliff:g> verwijderen en automatisch verwijderen inschakelen?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> zei"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"U zei"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Bericht van <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"U heeft een bericht verzonden"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Verzenden…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Niet verzonden. Tik om het opnieuw te proberen."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Niet verzonden. Nieuwe poging…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Opnieuw verzenden of verwijderen"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Bel de hulpdiensten. Uw sms kan momenteel niet worden afgeleverd."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Mislukt"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nieuw mms-bericht downloaden"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nieuw mms-bericht"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Kan niet downloaden"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tik voor nieuwe poging"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Tik om te downloaden"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Downloaden of verwijderen"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Downloaden..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Bericht verlopen of niet beschikbaar"</string>
+ <string name="mms_info" msgid="3402311750134118165">"grootte: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, verloopt: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Kan niet verzenden. Ontvanger niet geldig."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Service niet geactiveerd in netwerk"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Kan niet verzenden wegens een netwerkprobleem"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Bericht verlopen of niet beschikbaar"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Geen onderwerp)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Onbekende afzender"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Bezorgd"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Kan bericht <xliff:g id="SUBJECT">%1$s</xliff:g> niet downloaden van <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Kan databasebewerking niet voltooien wegens onvoldoende geheugen"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Bericht niet verzonden"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Sommige berichten niet verzonden in Berichten"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> berichten in <xliff:g id="CONVERSATIONS">%d</xliff:g> gesprekken</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> berichten in één gesprek</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Bericht niet gedownload"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Sommige berichten zijn niet gedownload in Berichten"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> berichten in <xliff:g id="CONVERSATIONS">%d</xliff:g> gesprekken</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> berichten in één gesprek</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Bericht naar <xliff:g id="NUMBER">%1$s</xliff:g> niet verzonden"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Bel de hulpdiensten. Uw sms aan <xliff:g id="NUMBER">%1$s</xliff:g> kan momenteel niet worden afgeleverd."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nieuwe berichten</item>
+ <item quantity="one">Nieuw bericht</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Starten"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Camera niet beschikbaar"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Camera niet beschikbaar"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video-opname niet beschikbaar"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Kan media niet opslaan"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Kan geen foto maken"</string>
+ <string name="back" msgid="1477626055115561645">"Terug"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Gearchiveerd"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Verwijderen"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archiveren"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Terugzetten uit archief"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Meldingen uitschakelen"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Meldingen inschakelen"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Contact toevoegen"</string>
+ <string name="action_download" msgid="7786338136368564146">"Downloaden"</string>
+ <string name="action_send" msgid="377635240181672039">"Verzenden"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Verwijderen"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Dit bericht verwijderen?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Deze actie kan niet ongedaan worden gemaakt."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Verwijderen"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Deze gesprekken verwijderen?</item>
+ <item quantity="one">Dit gesprek verwijderen?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Verwijderen"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Annuleren"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Aan"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Meerdere afbeeldingen selecteren"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Selectie bevestigen"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Kan audio niet opnemen. Probeer het opnieuw."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Kan audio niet afspelen. Probeer het opnieuw."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Kan audio niet opslaan. Probeer het opnieuw."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Blijven aanraken"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Afbeelding"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audioclip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Visitekaartje"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Downloaden"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Reageren via sms"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Reageren via mms"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Beantwoorden"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> deelnemers</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> deelnemer</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ik"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contact geblokkeerd en gearchiveerd"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Blokkering van contact opgeheven en teruggezet uit archief"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> gearchiveerd"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> teruggezet uit archief"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Meldingen uitgeschakeld"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Meldingen ingeschakeld"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Alles is ingesteld. Tik nogmaals op \'Verzenden\'."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Berichten is ingesteld als de standaard sms-app"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Bijlagen verwijderen</item>
+ <item quantity="one">Bijlage verwijderen</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Audiobijlage"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Audiobijlage afspelen"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Onderbreken"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Bericht van <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mislukt bericht van <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Bericht van <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Niet-verzonden bericht aan <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Bericht verzenden naar <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mislukt bericht aan <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Bericht aan <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mislukt bericht van <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Bericht van <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Niet-verzonden bericht aan <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Bericht verzenden naar <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mislukt bericht aan <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Bericht aan <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tijd: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Bericht mislukt. Tik om het opnieuw te proberen."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Gesprek met <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Onderwerp verwijderen"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Video opnemen"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Een stilstaand beeld opnemen"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Foto maken"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Video opnemen"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Overschakelen naar camera op volledig scherm"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Schakelen tussen camera aan voorzijde en camera aan achterzijde"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Opname stoppen en video bijvoegen"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Video-opname stoppen"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto\'s in Berichten"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foto\'s opgeslagen in het album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto opgeslagen in het album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video\'s opgeslagen in het album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video opgeslagen in het album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> bijlagen opgeslagen in het album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bijlage opgeslagen in het album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bijlagen opgeslagen in \"Downloads\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bijlage opgeslagen in \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bijlagen opgeslagen</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bijlage opgeslagen</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Kan <xliff:g id="QUANTITY_1">%d</xliff:g> bijlagen niet opslaan</item>
+ <item quantity="one">Kan <xliff:g id="QUANTITY_0">%d</xliff:g> bijlage niet opslaan</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Opgeslagen mms-bijlage"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Instellingen"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Gearchiveerd"</string>
+ <string name="action_close" msgid="1840519376200478419">"Sluiten"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"Mms"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Geavanceerd"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Fouten opsporen"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Meldingen"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Geluid"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Stil"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Trillen"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Geblokkeerd"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Bezorgingsrapporten voor sms"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Ontvangstbevestiging vragen voor elke verzonden sms"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatisch ophalen"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Mms-berichten automatisch ophalen"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automatisch ophalen tijdens roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatisch MMS ophalen tijdens roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Groepsberichten"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Mms gebruiken om één bericht te verzenden wanneer er meerdere ontvangers zijn"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Standaard sms-app"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Standaard sms-app"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Uw telefoonnummer"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Onbekend"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Geluiden voor uitgaand bericht"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Sms dumpen"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Onbewerkte gegevens voor ontvangen sms\'jes in een extern opslagbestand dumpen"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Mms dumpen"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Onbewerkte gegevens voor ontvangen mms\'jes in een extern opslagbestand dumpen"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Waarschuwingen voor draadloos"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Berichtopties"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Tekst kopiëren"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Details weergeven"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Verwijderen"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Doorsturen"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Berichtdetails"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Type: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Sms-bericht"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mms-bericht"</string>
+ <string name="from_label" msgid="1947831848146564875">"Van: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Aan: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Verzonden: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Ontvangen: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Onderwerp: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Grootte: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioriteit: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"Simkaart: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Hoog"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normaal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Laag"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"Simkaart <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Verborgen adres verzender"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Kan bericht niet verzenden terwijl bijlagen worden geladen."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Kan bijlage niet laden. Probeer het opnieuw."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Netwerk is niet gereed. Probeer het opnieuw."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Tekst verwijderen"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Schakelen tussen letters en cijfers invoeren"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Meer deelnemers toevoegen"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Deelnemers bevestigen"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Nieuw gesprek starten"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Dit item selecteren"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Video afspelen"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Mensen en opties"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Fouten opsporen"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Mensen en opties"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Algemeen"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Mensen in dit gesprek"</string>
+ <string name="action_call" msgid="6596167921517350362">"Bellen"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Bericht verzenden"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Bericht verzenden&lt;br/&gt;&lt;small&gt;via <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Foto\'s verzenden</item>
+ <item quantity="one">Foto verzenden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Audio-opnamen verzenden</item>
+ <item quantity="one">Audio-opname verzenden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Video\'s verzenden</item>
+ <item quantity="one">Video verzenden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Contactkaarten verzenden</item>
+ <item quantity="one">Contactkaart verzenden</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Bijlagen verzenden</item>
+ <item quantity="one">Bijlage verzenden</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> bijlagen klaar voor verzenden</item>
+ <item quantity="one">Eén bijlage klaar voor verzenden</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Feedback verzenden"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Bekijken in de Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versie-informatie"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versie %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Open-sourcelicenties"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Meldingen"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Bijlagelimiet bereikt"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Kan bijlage niet laden."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Toevoegen aan contacten?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Contact toevoegen"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Onderwerp"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Onderwerp: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Contactkaart laden"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kan visitekaartje niet laden"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Contactkaart weergeven"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contacten</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contact</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Visitekaartjes"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Verjaardag"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notities"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Bericht doorsturen"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Beantwoorden"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Sms uitgeschakeld"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Stel Berichten in als standaard sms-app om berichten te verzenden"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Berichten instellen als standaard sms-app"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Wijzigen"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Stel Berichten in als standaard sms-app om berichten te ontvangen"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Geen voorkeurs-simkaart geselecteerd voor het verzenden van sms\'jes"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Deze app is niet toegestaan door de eigenaar van het apparaat."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Te veel deelnemers in een gesprek"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Ongeldige contacten</item>
+ <item quantity="one">Ongeldig contact</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kan camera-afbeelding niet laden"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"U: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Concept"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Zodra u een nieuw gesprek start, wordt dit hier vermeld"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Gearchiveerde gesprekken worden hier weergegeven"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Gesprekken laden..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Afbeelding"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audioclip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Visitekaartje"</string>
+ <string name="mms_text" msgid="1528791558806015806">"Mms"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Ongedaan maken"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Opnieuw proberen"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Geef de naam of het telefoonnummer van een contact op om een nieuw bericht te starten"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokkeren"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> blokkeren"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Blokkering van <xliff:g id="DESTINATION">%s</xliff:g> opheffen"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> blokkeren?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"U blijft berichten van dit nummer ontvangen, maar krijgt geen meldingen meer. Dit gesprek wordt gearchiveerd."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Geblokkeerde contacten"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"BLOKKERING OPHEFFEN"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Geblokkeerde contacten"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Afbeelding kiezen uit documentbibliotheek"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Bericht wordt verzonden"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Bericht verzonden"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobiele gegevens zijn ingeschakeld. Controleer uw instellingen."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Kan geen berichten verzenden in vliegtuigmodus"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Kan bericht niet verzenden"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Bericht gedownload"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobiele gegevens zijn uitgeschakeld. Controleer uw instellingen."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Kan geen berichten downloaden in de vliegtuigmodus"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Bericht kan niet worden gedownload"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nul"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Eén"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Twee"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Drie"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Vier"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Vijf"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Zes"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Zeven"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Acht"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Negen"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Kan bericht niet verzenden via <xliff:g id="CARRIERNAME">%1$s</xliff:g>, fout <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Kan bericht niet verzenden via onbekende provider, fout <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Bericht niet verzonden: service niet geactiveerd in netwerk"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Bericht niet verzonden: ongeldig bestemmingsadres"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Bericht niet verzonden: ongeldig bericht"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Bericht niet verzonden: niet-ondersteunde inhoud"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Bericht niet verzonden: niet-ondersteund bericht"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Bericht niet verzonden: te groot"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nieuw bericht"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Weergeven"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Afbeelding"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Kan geen geschikte applicatie vinden"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Ontvanger verwijderen"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nieuw bericht"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Annuleren"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Toegangspunt bewerken"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Niet ingesteld"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Naam"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-poort"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-type"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN verwijderen"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nieuwe APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Opslaan"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Verwijderen"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Het veld \'Naam\' mag niet leeg zijn."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"De APN mag niet leeg zijn."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-veld moet drie cijfers bevatten."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC-veld moet twee of drie cijfers bevatten."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Standaard-APN-instellingen herstellen."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Standaardinstellingen herstellen"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Herstellen van standaard-APN-instellingen voltooid."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Naamloos"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Namen toegangspunten"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN\'s"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nieuwe APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Instellingen voor toegangspuntnamen zijn niet beschikbaar voor deze gebruiker"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopiëren naar klembord?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiëren"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"naar <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Algemeen"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Geavanceerd"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Algemene instellingen"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Geavanceerde instellingen"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Sim van \'<xliff:g id="SIM_NAME">%s</xliff:g>\'"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Afzonderlijke sms\'jes verzenden naar alle ontvangers. U bent de enige die eventuele reacties ontvangt"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Eén mms verzenden naar alle ontvangers"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Onbekend nummer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nieuw bericht"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nieuw bericht."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Simkaart selecteren"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> geselecteerd, simkaartkiezer"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Onderwerp bewerken"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Simkaart selecteren of onderwerp bewerken"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Blijf aanraken om audio op te nemen"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Nieuw gesprek starten"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Berichten"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lijst in Berichten"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Berichten"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nieuw bericht"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Gesprekslijst"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Gesprekken laden"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Berichten laden"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Meer gesprekken bekijken"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Meer berichten bekijken"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Gesprek verwijderd"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Gesprek verwijderd. Tik om een ander Berichten-gesprek weer te geven"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Geblokkeerd"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Blokkering opgeheven"</string>
+ <string name="db_full" msgid="8459265782521418031">"Er is weinig opslagruimte beschikbaar. Er kunnen gegevens verloren gaan."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Bijlagen selecteren"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Selectie bevestigen"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> geselecteerd"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Verwijder een of meer bijlagen en probeer het opnieuw."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"U kunt proberen uw bericht te verzenden, maar het bericht wordt mogelijk niet bezorgd, tenzij u een of meer bijlagen verwijdert."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"U kunt slechts één video verzenden per bericht. Verwijder extra video\'s en probeer het opnieuw."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Berichten kan de bijlage niet laden."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Toch verzenden"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Kan gesprek niet starten"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> geselecteerd"</string>
+</resources>
diff --git a/res/values-pa-rIN/arrays.xml b/res/values-pa-rIN/arrays.xml
new file mode 100644
index 0000000..1cf23e9
--- /dev/null
+++ b/res/values-pa-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ਕੋਈ ਵਿਸ਼ਾ ਨਹੀਂ"</item>
+ <item msgid="272485471009191934">"ਕੋਈ ਵਿਸ਼ਾ ਨਹੀਂ"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ਹਾਂ"</item>
+ <item msgid="6049132459802288033">"ਨਹੀਂ"</item>
+ <item msgid="3084376867445867895">"ਠੀਕ"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"ਧੰਨਵਾਦ"</item>
+ <item msgid="4881335087096496747">"ਮੈਂ ਸਹਿਮਤ ਹਾਂ"</item>
+ <item msgid="2422296858597420738">"ਵਧੀਆ"</item>
+ <item msgid="4805581752819452687">"ਰਸਤੇ ਵਿੱਚ ਹਾਂ"</item>
+ <item msgid="4746700499431366214">"ਠੀਕ, ਮੈਂ ਤੁਹਾਨੂੰ ਬਾਅਦ ਵਿੱਚ ਸੰਪਰਕ ਕਰਾਂਗਾ"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
new file mode 100644
index 0000000..3be69e1
--- /dev/null
+++ b/res/values-pa-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Messaging"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Messaging"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"ਗੱਲਬਾਤ ਚੁਣੋ"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ਸੈਟਿੰਗਾਂ"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"ਸੁਨੇਹਾ ਭੇਜੋ"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ਇੱਕ ਅਟੈਚਮੈਂਟ ਜੋੜੋ"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"ਸਹਾਇਤਾ"</string>
+ <string name="welcome" msgid="2857560951820802321">"ਸੁਆਗਤ ਹੈ"</string>
+ <string name="skip" msgid="7238879696319945853">"ਛੱਡੋ"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"ਅਗਲਾ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"ਅਗਲਾ"</string>
+ <string name="exit" msgid="1905187380359981199">"ਬਾਹਰ ਜਾਓ"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ਸੈਟਿੰਗਾਂ &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ਸੈਟਿੰਗਾਂ"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Messaging ਨੂੰ SMS, ਫੋਨ ਅਤੇ ਸੰਪਰਕਾਂ ਲਈ ਅਨੁਮਤੀ ਦੀ ਲੋੜ ਹੈ।"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ &gt; ਐਪਸ &gt; Messaging &gt; ਅਨੁਮਤੀਆਂ ਵਿੱਚ ਅਨੁਮਤੀਆਂ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ, ਐਪਸ, Messaging, ਅਨੁਮਤੀਆਂ ਵਿੱਚ ਅਨੁਮਤੀਆਂ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"ਫ੍ਰੀਕਵੈਂਟਸ"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"ਸਾਰੇ ਸੰਪਰਕ"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> ਨੂੰ ਭੇਜੋ"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"ਤਸਵੀਰਾਂ ਜਾ ਵੀਡੀਓ ਕੈਪਚਰ ਕਰੋ"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ਇਸ ਡਿਵਾਈਸ ਤੋਂ ਚਿੱਤਰ ਚੁਣੋ"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ਔਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ਫੋਟੋ ਚੁਣੋ"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"ਮੀਡੀਆ ਚੁਣਿਆ ਗਿਆ ਹੈ।"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"ਮੀਡੀਆ ਅਚੋਣਵਾਂ ਕੀਤਾ ਗਿਆ ਹੈ।"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ਚੁਣਿਆ ਗਿਆ"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ਚਿੱਤਰ <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" msgid="8142175029053370662">"ਚਿੱਤਰ"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ਔਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="action_share" msgid="2143483844803153871">"ਸ਼ੇਅਰ ਕਰੋ"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ਬਿਲਕੁਲ ਹੁਣੇ"</string>
+ <string name="posted_now" msgid="867560789350406701">"ਹੁਣ"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ਮਿੰਟ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ਮਿੰਟ</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ਘੰਟੇ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ਘੰਟੇ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ਦਿਨ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ਦਿਨ</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ਹਫ਼ਤੇ</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ਹਫ਼ਤੇ</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ਮਹੀਨੇ</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ਮਹੀਨੇ</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> ਸਾਲ</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ਸਾਲ</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"ਕਲਾਸ 0 ਸੁਨੇਹਾ"</string>
+ <string name="save" msgid="5081141452059463572">"ਸੁਰੱਖਿਅਤ ਕਰੋ"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"ਡਿਵਾਈਸ ਵਿੱਚ ਘੱਟ ਸਪੇਸ ਹੈ। Messaging ਆਟੋਮੈਟਿਕਲੀ ਸਪੇਸ ਖਾਲੀ ਕਰਨ ਲਈ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾ ਦੇਵੇਗਾ।"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"ਸਟੋਰੇਜ ਸਪੇਸ ਖ਼ਤਮ ਹੋ ਰਿਹਾ ਹੈ"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Messaging ਸੁਨੇਹੇ ਭੇਜ ਜਾਂ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕਰ ਸਕਦਾ ਜਦੋਂ ਤੱਕ ਕਿ ਤੁਹਾਡੀ ਡਿਵਾਈਸ ਤੇ ਹੋਰ ਸਪੇਸ ਉਪਲਬਧ ਨਾ ਹੋਵੇ।"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"ਘੱਟ SMS ਸਟੋਰੇਜ। ਤੁਹਾਨੂੰ ਸੁਨੇਹੇ ਮਿਟਾਉਣ ਦੀ ਲੋੜ ਪੈ ਸਕਦੀ ਹੈ।"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"ਆਪਣੇ ਫੋਨ ਨੰਬਰ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ਇਹ ਵਨ-ਟਾਈਮ ਸਟੈਪ ਯਕੀਨੀ ਬਣਾਏਗਾ ਕਿ Messaging ਤੁਹਾਡੇ ਸਮੂਹ ਦੇ ਸੁਨੇਹੇ ਸਹੀ ਢੰਗ ਨਾਲ ਡਿਲੀਵਰ ਕਰੇਗਾ।"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ਫੋਨ ਨੰਬਰ"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"ਮੀਡੀਆ ਵਾਲੇ ਸਾਰੇ ਸੁਨੇਹੇ ਮਿਟਾਓ"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾਓ"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਆਟੋ-ਮਿਟਾਓ"</string>
+ <string name="ignore" msgid="7063392681130898793">"ਅਣਡਿੱਠ ਕਰੋ"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"ਕੀ ਮੀਡੀਆ ਵਾਲੇ ਸਾਰੇ ਸੁਨੇਹੇ ਮਿਟਾਉਣੇ ਹਨ?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"ਕੀ <xliff:g id="DURATION">%s</xliff:g> ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾਉਣੇ ਹਨ?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"ਕੀ <xliff:g id="DURATION">%s</xliff:g> ਤੋਂ ਪੁਰਾਣੇ ਸੁਨੇਹੇ ਮਿਟਾਉਣੇ ਹਨ ਅਤੇ ਆਟੋ-ਮਿਟਾਓ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> ਨੇ ਕਿਹਾ"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"ਤੁਸੀਂ ਕਿਹਾ"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> ਦਾ ਸੁਨੇਹਾ"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"ਤੁਸੀਂ ਇੱਕ ਸੁਨੇਹਾ ਭੇਜਿਆ"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"ਭੇਜ ਰਿਹਾ ਹੈ..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"ਨਹੀਂ ਭੇਜਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਛੋਹਵੋ।"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"ਨਹੀਂ ਭੇਜਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਿਹਾ ਹੈ..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ਦੁਬਾਰਾ ਭੇਜੋ ਜਾਂ ਮਿਟਾਓ"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"ਕਿਰਪਾ ਕਰਕੇ ਐਮਰਜੈਂਸੀ ਸੇਵਾਵਾਂ ਨੂੰ ਇੱਕ ਵੌਇਸ ਕਾਲ ਕਰੋ। ਤੁਹਾਡਾ ਟੈਕਸਟ ਸੁਨੇਹਾ ਇਸ ਵੇਲੇ ਡਿਲੀਵਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕੇਗਾ।"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"ਅਸਫਲ"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਨਵਾਂ MMS ਸੁਨੇਹਾ"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"ਨਵਾਂ MMS ਸੁਨੇਹਾ"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ਡਾਊਨਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਛੋਹਵੋ"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ਡਾਊਨਲੋਡ ਕਰਨ ਲਈ ਛੋਹਵੋ"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ਡਾਊਨਲੋਡ ਕਰੋ ਜਾਂ ਮਿਟਾਓ"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ਡਾਊਨਲੋਡ ਕਰ ਰਿਹਾ ਹੈ..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"ਸੁਨੇਹੇ ਦੀ ਮਿਆਦ ਪੁੱਗੀ ਜਾਂ ਉਪਲਬਧ ਨਹੀਂ"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ਆਕਾਰ: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, ਮਿਆਦ ਪੁੱਗਣ ਦਾ ਸਮਾਂ: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"ਭੇਜ ਨਹੀਂ ਸਕਦਾ। ਪ੍ਰਾਪਤਕਰਤਾ ਪ੍ਰਮਾਣਿਕ ਨਹੀਂ ਹੈ।"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"ਨੈਟਵਰਕ ਤੇ ਸੇਵਾ ਸਕਿਰਿਆ ਨਹੀਂ ਕੀਤੀ"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ਨੈਟਵਰਕ ਸਮੱਸਿਆ ਦੇ ਕਾਰਨ ਨਹੀਂ ਭੇਜ ਸਕਿਆ"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"ਸੁਨੇਹੇ ਦੀ ਮਿਆਦ ਪੁੱਗੀ ਜਾਂ ਉਪਲਬਧ ਨਹੀਂ"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(ਕੋਈ ਵਿਸ਼ਾ ਨਹੀਂ)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"ਅਗਿਆਤ ਭੇਜਣ ਵਾਲਾ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ਡਿਲੀਵਰ ਕੀਤਾ"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> ਦਾ <xliff:g id="SUBJECT">%1$s</xliff:g> ਸੁਨੇਹਾ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ।"</string>
+ <string name="low_memory" msgid="5300743415198486619">"ਘੱਟ ਮੈਮਰੀ ਦੇ ਕਾਰਨ ਡਾਟਾਬੇਸ ਓਪਰੇਸ਼ਨ ਪੂਰਾ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"ਕੁਝ ਸੁਨੇਹੇ Messaging ਵਿੱਚ ਨਹੀਂ ਭੇਜੇ ਗਏ ਹਨ"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"> <xliff:g id="CONVERSATIONS">%d</xliff:g> ਗੱਲਬਾਤਾਂ ਵਿੱਚ <xliff:g id="MESSAGES_1">%d</xliff:g> ਸੁਨੇਹੇ</item>
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> ਗੱਲਬਾਤਾਂ ਵਿੱਚ <xliff:g id="MESSAGES_1">%d</xliff:g> ਸੁਨੇਹੇ</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"ਸੁਨੇਹਾ ਡਾਉਨਲੋਡ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"ਕੁਝ ਸੁਨੇਹੇ Messaging ਵਿੱਚ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੇ ਗਏ ਹਨ"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"> <xliff:g id="CONVERSATIONS">%d</xliff:g> ਗੱਲਬਾਤਾਂ ਵਿੱਚ <xliff:g id="MESSAGES_1">%d</xliff:g> ਸੁਨੇਹੇ</item>
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> ਗੱਲਬਾਤਾਂ ਵਿੱਚ <xliff:g id="MESSAGES_1">%d</xliff:g> ਸੁਨੇਹੇ</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"ਕਿਰਪਾ ਕਰਕੇ ਐਮਰਜੈਂਸੀ ਸੇਵਾਵਾਂ ਨੂੰ ਇੱਕ ਵੌਇਸ ਕਾਲ ਕਰੋ। ਤੁਹਾਡਾ ਟੈਕਸਟ ਸੁਨੇਹਾ ਇਸ ਵੇਲੇ <xliff:g id="NUMBER">%1$s</xliff:g> ਨੂੰ ਡਿਲੀਵਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕੇਗਾ।"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> ਨਵੇਂ ਸੁੁਨੇਹੇ</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ਨਵੇਂ ਸੁੁਨੇਹੇ</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ਚਾਲੂ ਕਰੋ"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"ਕੈਮਰਾ ਉਪਲਬਧ ਨਹੀਂ"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"ਕੈਮਰਾ ਉਪਲਬਧ ਨਹੀਂ"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ਵੀਡੀਓ ਕੈਪਚਰ ਉਪਲਬਧ ਨਹੀਂ"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"ਮੀਡੀਆ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਦਾ"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ਤਸਵੀਰ ਨਹੀਂ ਲੈ ਸਕਦਾ"</string>
+ <string name="back" msgid="1477626055115561645">"ਪਿੱਛੇ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"ਆਰਕਾਈਵ ਕੀਤਾ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"ਮਿਟਾਓ"</string>
+ <string name="action_archive" msgid="5437034800324083170">"ਆਰਕਾਈਵ ਕਰੋ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ਅਨਆਰਕਾਈਵ ਕਰੋ"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"ਸੂਚਨਾਵਾਂ ਬੰਦ ਕਰੋ"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕਰੋ"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"ਸੰਪਰਕ ਜੋੜੋ"</string>
+ <string name="action_download" msgid="7786338136368564146">"ਡਾਊਨਲੋਡ ਕਰੋ"</string>
+ <string name="action_send" msgid="377635240181672039">"ਭੇਜੋ"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"ਮਿਟਾਓ"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ਕੀ ਇਹ ਸੁਨੇਹਾ ਮਿਟਾਉਣਾ ਹੈ?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ਇਹ ਕਿਰਿਆ ਨਸ਼ਟ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"ਮਿਟਾਓ"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">ਕੀ ਇਹ ਗੱਲਬਾਤਾਂ ਮਿਟਾਉਣੀਆਂ ਹਨ?</item>
+ <item quantity="other">ਕੀ ਇਹ ਗੱਲਬਾਤਾਂ ਮਿਟਾਉਣੀਆਂ ਹਨ?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"ਮਿਟਾਓ"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"ਰੱਦ ਕਰੋ"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"ਵੱਲ"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"ਮਲਟੀਪਲ ਚਿੱਤਰ ਚੁਣੋ"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"ਚੋਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ਔਡੀਓ ਰਿਕਾਰਡ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ਔਡੀਓ ਪਲੇ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ਔਡੀਓ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ਛੋਹਵੋ &amp; ਹੋਲਡ ਕਰੋ"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ਤਸਵੀਰ"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ਔਡੀਓ ਕਲਿਪ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ਵੀਡੀਓ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"ਸੰਪਰਕ ਕਾਰਡ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ਡਾਊਨਲੋਡ ਕਰੋ"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS ਰਾਹੀਂ ਜਵਾਬ"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS ਰਾਹੀਂ ਜਵਾਬ"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ਜਵਾਬ"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ਭਾਗੀਦਾਰ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ਭਾਗੀਦਾਰ</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ਮੈਂ"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"ਬਲੌਕ ਕੀਤਾ &amp; ਆਰਕਾਈਵ ਕੀਤਾ ਸੰਪਰਕ"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"ਸੰਪਰਕ ਅਨਬਲੌਕ ਕੀਤਾ &amp; ਅਨਆਰਕਾਈਵ ਕੀਤਾ"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ਆਰਕਾਈਵ ਕੀਤੀਆਂ"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ਅਨਆਰਕਾਈਵ ਕੀਤੀਆਂ"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"ਸੂਚਨਾਵਾਂ ਬੰਦ ਕੀਤੀਆਂ"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"ਸੂਚਨਾਵਾਂ ਚਾਲੂ ਕੀਤੀਆਂ"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"ਸਾਰੇ ਸੈਟ ਕੀਤੇ। ਭੇਜੋ ਨੂੰ ਦੁਬਾਰਾ ਛੋਹਵੋ।"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Messaging ਡਿਫੌਲਟ SMS ਐਪਸ ਦੇ ਤੌਰ ਤੇ ਸਫਲਤਾਪੂਰਵਕ ਸੈਟ ਕੀਤਾ ਗਿਆ।"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">ਅਟੈਚਮੈਂਟਾਂ ਬਰਖਾਸਤ ਕਰੋ</item>
+ <item quantity="other">ਅਟੈਚਮੈਂਟਾਂ ਬਰਖਾਸਤ ਕਰੋ</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ਔਡੀਓ ਅਟੈਚਮੈਂਟ"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ਔਡੀਓ ਅਟੈਚਮੈਂਟ ਪਲੇ ਕਰੋ"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"ਰੋਕੋ"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> ਦਾ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>।"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> ਦਾ ਸੁਨੇਹਾ ਅਸਫਲ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> ਦਾ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> ਨੂੰ ਨਾ ਭੇਜਿਆ ਗਿਆ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>। Time: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਰਿਹਾ ਹੈ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣਾ ਅਸਫਲ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> ਦਾ ਸੁਨੇਹਾ ਅਸਫਲ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>। <xliff:g id="GROUPINFO">%s</xliff:g>।"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> ਦਾ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>. ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>। <xliff:g id="GROUPINFO">%s</xliff:g>।"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> ਨੂੰ ਨਾ ਭੇਜਿਆ ਗਿਆ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ ਭੇਜ ਰਿਹਾ ਹੈ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ ਭੇਜਣਾ ਅਸਫਲ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> ਨੂੰ ਸੁਨੇਹਾ: <xliff:g id="MESSAGE">%s</xliff:g>। ਸਮਾਂ: <xliff:g id="TIME">%s</xliff:g>।"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ਸੁਨੇਹਾ ਅਸਫਲ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਛੋਹਵੋ।"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> ਨਾਲ ਗੱਲਬਾਤ ਕਰੋ"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"ਵਿਸ਼ਾ ਹਟਾਓ"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ਵੀਡੀਓ ਕੈਪਚਰ ਕਰੋ"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ਇੱਕ ਸਟਿਲ ਚਿੱਤਰ ਕੈਪਚਰ ਕਰੋ"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ਤਸਵੀਰ ਲਓ"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰਨਾ ਚਾਲੂ ਕਰੋ"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਕੈਮਰਾ ਤੇ ਸਵਿਚ ਕਰੋ"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"ਫ੍ਰੰਟ ਅਤੇ ਬੈਕ ਕੈਮਰਾ ਵਿਚਕਾਰ ਸਵਿਚ ਕਰੋ"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"ਰਿਕਾਰਡਿੰਗ ਰੋਕੋ ਅਤੇ ਵੀਡੀਓ ਅਟੈਚ ਕਰੋ"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰਨਾ ਰੋਕੋ"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Messaging ਫੋਟੋਆਂ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ਫੋਟੋਆਂ ਨੂੰ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ਫੋਟੋਆਂ ਨੂੰ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ਵੀਡਿਓ ਨੂੰ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ਵੀਡਿਓ ਨੂੰ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ਐਲਬਮ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਡਾਊਨਲੋਡਸ\" ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ ਗਈਆਂ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਡਾਊਨਲੋਡਸ\" ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ ਗਈਆਂ</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"> <xliff:g id="QUANTITY_1">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ ਸੁੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਿਆ</item>
+ <item quantity="other"> <xliff:g id="QUANTITY_1">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਨੂੰ ਸੁੁਰੱਖਿਅਤ ਨਹੀਂ ਕਰ ਸਕਿਆ</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"ਸੁਰੱਖਿਅਤ ਕੀਤੀ MMS ਅਟੈਚਮੈਂਟ"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ਸੈਟਿੰਗਾਂ"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"ਆਰਕਾਈਵ ਕੀਤਾ"</string>
+ <string name="action_close" msgid="1840519376200478419">"ਬੰਦ ਕਰੋ"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"ਉੱਨਤ"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ਡੀਬਗ ਕਰੋ"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"ਸੂਚਨਾਵਾਂ"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ਅਵਾਜ਼"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"ਸਾਈਲੈਂਟ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"ਵਾਈਬ੍ਰੇਟ"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ਬਲੌਕ ਕੀਤਾ"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS ਡਿਲੀਵਰੀ ਰਿਪੋਰਟਾਂ"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ਤੁਸੀਂ ਜੋ ਵੀ SMS ਭੇਜੋ ਉਸ ਹਰੇਕ ਲਈ ਇੱਕ ਡਿਲੀਵਰੀ ਰਿਪੋਰਟ ਦੀ ਬੇਨਤੀ ਕਰੋ"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"ਆਟੋ-ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"ਆਟੋਮੈਟਿਕਲੀ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ਰੋਮਿੰਗ ਆਟੋ-ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ਰੋਮਿੰਗ ਵੇਲੇ MMS ਆਟੋਮੈਟਿਕਲੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"ਸਮੂਹ ਮੈਸੇਂਜ਼ਿੰਗ"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ਜਦੋਂ ਮਲਟੀਪਲ ਪ੍ਰਾਪਤਕਰਤਾ ਹੋਣ ਤਾਂ ਇੱਕ ਸਿੰਗਲ ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ MMS ਵਰਤੋ"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"ਡਿਫੌਲਟ SMS ਐਪ"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"ਡਿਫੌਲਟ SMS ਐਪ"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"ਤੁਹਾਡਾ ਫੋਨ ਨੰਬਰ"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"ਅਗਿਆਤ"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"ਆਊਟਗੋਇੰਗ ਸੁਨੇਹਾ ਅਵਾਜ਼ਾਂ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS ਡੰਪ ਕਰੋ"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ਅੰਦਰੂਨੀ ਸਟੋਰੇਜ ਫਾਈਲ ਵਿੱਚ ਪ੍ਰਾਪਤ ਕੀਤਾ SMS ਅਧੂਰਾ ਡਾਟਾ ਡੰਪ ਕਰੋ"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS ਡੰਪ ਕਰੋ"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ਅੰਦਰੂਨੀ ਸਟੋਰੇਜ ਫਾਈਲ ਵਿੱਚ ਪ੍ਰਾਪਤ ਕੀਤਾ MMS ਅਧੂਰਾ ਡਾਟਾ ਡੰਪ ਕਰੋ"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"ਵਾਇਰਲੈਸ ਚਿਤਾਵਨੀਆਂ"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"ਸੁਨੇਹਾ ਚੋਣਾਂ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"ਟੈਕਸਟ ਕਾਪੀ ਕਰੋ"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"ਵੇਰਵੇ ਦੇਖੋ"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"ਮਿਟਾਓ"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ਅੱਗੇ ਭੇਜੋ"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"ਸੁਨੇਹਾ ਵੇਰਵੇ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ਟਾਈਪ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"ਟੈਕਸਟ ਸੁਨੇਹਾ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"ਮਲਟੀਮੀਡੀਆ ਸੁਨੇਹਾ"</string>
+ <string name="from_label" msgid="1947831848146564875">"ਇਸ ਤੋਂ: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"ਵੱਲ: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ਭੇਜਿਆ: "</string>
+ <string name="received_label" msgid="4442494712757995203">"ਪ੍ਰਾਪਤ ਕੀਤਾ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"ਵਿਸ਼ਾ: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"ਆਕਾਰ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ਤਰਜੀਹ: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"ਉੱਚ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"ਸਧਾਰਨ"</string>
+ <string name="priority_low" msgid="7398724779026801851">"ਘੱਟ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"ਭੇਜਣ ਵਾਲੇ ਦਾ ਪਤਾ ਲੁਕਾਇਆ ਗਿਆ"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"ਅਟੈਚਮੈਂਟਾਂ ਲੋਡ ਕਰਨ ਵੇਲੇ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜ ਸਕਦਾ।"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ਅਟੈਚਮੈਂਟ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ਨੈੱਟਵਰਕ ਤਿਆਰ ਨਹੀਂ ਹੈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ਟੈਕਸਟ ਮਿਟਾਓ"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"ਟੈਕਸਟ ਅਤੇ ਨੰਬਰ ਦਰਜ ਕਰਨ ਵਿਚਕਾਰ ਸਵਿਚ ਕਰੋ"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"ਹੋਰ ਭਾਗੀਦਾਰ ਜੋੜੋ"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"ਭਾਗੀਦਾਰਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"ਨਵੀਂ ਗੱਲਬਾਤ ਚਾਲੂ ਕਰੋ"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ਇਹ ਆਈਟਮ ਚੁਣੋ"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ਵੀਡੀਓ ਪਲੇ ਕਰੋ"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ਲੋਕ &amp; ਚੋਣਾਂ"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ਡੀਬਗ ਕਰੋ"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ਲੋਕ &amp; ਚੋਣਾਂ"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"ਸਧਾਰਨ"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ਇਸ ਗੱਲਬਾਤ ਵਿੱਚ ਲੋਕ"</string>
+ <string name="action_call" msgid="6596167921517350362">"ਇੱਕ ਕਾਲ ਕਰੋ"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"ਸੁਨੇਹਾ ਭੇਜੋ"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"ਸੁਨੇਹਾ ਭੇਜੋ&lt;br/&gt;&lt;ਛੋਟਾ&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/ਤੋਂ ਛੋਟਾ&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ਫੋਟੋਆਂ ਭੇਜੋ</item>
+ <item quantity="other">ਫੋਟੋਆਂ ਭੇਜੋ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ਔਡੀਓ ਭੇਜੋ</item>
+ <item quantity="other">ਔਡੀਓ ਭੇਜੋ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">ਵੀਡਿਓ ਭੇਜੋ</item>
+ <item quantity="other">ਵੀਡਿਓ ਭੇਜੋ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">ਸੰਪਰਕ ਕਾਰਡ ਭੇਜੋ</item>
+ <item quantity="other">ਸੰਪਰਕ ਕਾਰਡ ਭੇਜੋ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">ਅਟੈਚਮੈਂਟਾਂ ਭੇਜੋ</item>
+ <item quantity="other">ਅਟੈਚਮੈਂਟਾਂ ਭੇਜੋ</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਭੇਜਣ ਲਈ ਤਿਆਰ</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ਅਟੈਚਮੈਂਟਾਂ ਭੇਜਣ ਲਈ ਤਿਆਰ</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ਫੀਡਬੈਕ ਭੇਜੋ"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play ਸਟੋਰ ਵਿੱਚ ਦੇਖੋ"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ਵਰਜਨ ਜਾਣਕਾਰੀ"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"ਵਰਜਨ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ਓਪਨ ਸ੍ਰੋਤ ਲਸੰਸ"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"ਸੂਚਨਾਵਾਂ"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ਅਟੈਚਮੈਂਟ ਸੀਮਾ ਪੂਰੀ ਹੋਈ"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ਅਟੈਚਮੈਂਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ।"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"ਕੀ ਸੰਪਰਕ ਜੋੜਨੇ ਹਨ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"ਸੰਪਰਕ ਜੋੜੋ"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"ਵਿਸ਼ਾ"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"ਵਿਸ਼ਾ: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"ਸੰਪਰਕ ਕਾਰਡ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"ਸੰਪਰਕ ਕਾਰਡ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"ਸੰਪਰਕ ਕਾਰਡ ਦੇਖੋ"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ਸੰਪਰਕ</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ਸੰਪਰਕ</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"ਸੰਪਰਕ ਕਾਰਡ"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"ਜਨਮਦਿਨ"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"ਸੂਚਨਾਵਾਂ"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"ਸੁਨੇਹਾ ਅੱਗੇ ਭੇਜੋ"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ਜਵਾਬ ਦਿਓ"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS ਅਸਮਰਥਿਤ"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"ਭੇਜਣ ਲਈ, Messaging ਨੂੰ ਡਿਫੌਲਟ SMS ਐਪ ਦੇ ਤੌਰ ਤੇ ਸੈਟ ਕਰੋ"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Messaging ਨੂੰ ਡਿਫੌਲਟ SMS ਐਪ ਦੇ ਤੌਰ ਤੇ ਸੈਟ ਕਰੋ"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"ਬਦਲੋ"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਲਈ, Messaging ਨੂੰ ਡਿਫੌਲਟ SMS ਐਪਸ ਦੇ ਤੌਰ ਤੇ ਸੈਟ ਕਰੋ"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS ਸੁਨੇਹੇ ਭੇਜਣ ਲਈ ਕੋਈ ਤਰਜੀਹੀ SIM ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ਡਿਵਾਈਸ ਮਾਲਕ ਵੱਲੋਂ ਇਸ ਐਪ ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੈ।"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ਠੀਕ"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"ਇੱਕ ਗੱਲਬਾਤਾ ਵਿੱਚ ਬਹੁਤ ਜ਼ਿਆਦਾ ਪ੍ਰਾਪਤਕਰਤਾ"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">ਅਪ੍ਰਮਾਣਿਕ ਸੰਪਰਕ</item>
+ <item quantity="other">ਅਪ੍ਰਮਾਣਿਕ ਸੰਪਰਕ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"ਕੈਮਰਾ ਚਿੱਤਰ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"ਤੁਸੀਂ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ਡ੍ਰਾਫਟ"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"ਜਦੋਂ ਤੁਸੀਂ ਇੱਕ ਨਵੀਂ ਗੱਲਬਾਤ ਚਾਲੂ ਕਰਦੇ ਹੋ, ਤਾਂ ਤੁਸੀਂ ਇਸਨੂੰ ਇੱਥੇ ਸੂਚੀਬੱਧ ਦੇਖੋਗੇ"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"ਆਰਕਾਈਵ ਕੀਤੀਆਂ ਗੱਲਬਾਤਾਂ ਇੱਥੇ ਪ੍ਰਗਟ ਹੋਣਗੀਆਂ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"ਗੱਲਬਾਤਾਂ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ਤਸਵੀਰ"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ਔਡੀਓ ਕਲਿਪ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ਵੀਡੀਓ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"ਸੰਪਰਕ ਕਾਰਡ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"ਪਹਿਲਾਂ ਵਰਗਾ ਕਰੋ"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"ਇੱਕ ਨਵਾਂ ਸੁਨੇਹਾ ਚਾਲੂ ਕਰਨ ਲਈ ਇੱਕ ਸੰਪਰਕ ਨਾਮ ਜਾਂ ਫੋਨ ਨੰਬਰ ਦਰਜ ਕਰੋ"</string>
+ <string name="action_block" msgid="9032076625645190136">"ਬਲੌਕ ਕਰੋ"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> ਨੂੰ ਬਲੌਕ ਕਰੋ"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> ਨੂੰ ਅਨਬਲੌਕ ਕਰੋ"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"ਕੀ <xliff:g id="DESTINATION">%s</xliff:g> ਨੂੰ ਬਲੌਕ ਕਰਨਾ ਹੈ?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"ਤੁਸੀਂ ਇਸ ਨੰਬਰ ਤੋਂ ਲਗਾਤਾਰ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰੋਗੇ ਪਰੰਤੂ ਹੁਣ ਤੁਹਾਨੂੰ ਸੂਚਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਏਗਾ। ਇਹ ਗੱਲਬਾਤ ਆਰਕਾਈਵ ਕੀਤੀ ਜਾਏਗੀ।"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"ਬਲੌਕ ਕੀਤੇ ਸੰਪਰਕ"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ਅਨਬਲੌਕ ਕਰੋ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"ਬਲੌਕ ਕੀਤੇ ਸੰਪਰਕ"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ਦਸਤਾਵੇਜ਼ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚੋਂ ਚਿੱਤਰ ਚੁਣੋ"</string>
+ <string name="sending_message" msgid="6363584950085384929">"ਸੁਨੇਹਾ ਭੇਜ ਰਿਹਾ ਹੈ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"ਸੁਨੇਹਾ ਭੇਜਿਆ ਗਿਆ"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"ਸੈਲਿਊਲਰ ਡਾਟਾ ਬੰਦ ਹੈ। ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਕਰੋ।"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"ਏਅਰਪਲੇਨ ਮੋਡ ਵਿੱਚ ਸੁਨੇਹੇ ਨਹੀਂ ਭੇਜ ਸਕਦਾ"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਜਾ ਸਕੇਗਾ"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"ਸੁਨੇਹਾ ਡਾਊਨਲੋਡ ਕੀਤਾ"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"ਸੈਲਿਊਲਰ ਡਾਟਾ ਬੰਦ ਹੈ। ਆਪਣੀਆਂ ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਕਰੋ।"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ਏਅਰਪਲੇਨ ਮੋਡ ਵਿੱਚ ਸੁਨੇਹੇ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"ਸੁਨੇਹਾ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕੇਗਾ"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"ਸਿਫਰ"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ਇੱਕ"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"ਦੋ"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"ਤਿੰਨ"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"ਚਾਰ"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ਪੰਜ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ਛੇ"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ਸੱਤ"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ਅੱਠ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ਨੌ"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> ਨਾਲ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜ ਸਕਦਾ, ਅਸ਼ੁੱਧੀ <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"ਅਗਿਆਤ ਕੈਰੀਅਰ ਨਾਲ ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜ ਸਕਦਾ, ਅਸ਼ੁੱਧੀ <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਨੈਟਵਰਕ ਤੇ ਸੇਵਾ ਸਕਿਰਿਆ ਨਹੀਂ"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਪ੍ਰਮਾਣਿਕ ਡੈਸਟੀਨੇਸ਼ਨ ਪਤਾ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਪ੍ਰਮਾਣਿਤ ਸੁਨੇਹਾ"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਸਮਰਥਿਤ ਸਮੱਗਰੀ"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ: ਅਸਮਰਥਿਤ ਸੁਨੇਹਾ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"ਸੁਨੇਹਾ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ: ਬਹੁਤ ਜ਼ਿਆਦਾ ਵੱਡਾ"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"ਨਵਾਂ ਸੁਨੇਹਾ"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"ਦੇਖੋ"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"ਚਿੱਤਰ"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"ਇੱਕ ਅਨੁੁਕੂਲ ਐਪਲੀਕੇਸ਼ਨ ਨਹੀਂ ਲੱਭ ਸਕਿਆ"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"ਪ੍ਰਾਪਤਕਰਤਾ ਹਟਾਓ"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"ਨਵਾਂ ਸੁਨੇਹਾ"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"ਰੱਦ ਕਰੋ"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ਪਹੁੰਚ ਬਿੰਦੂ ਸੰਪਾਦਿਤ ਕਰੋ"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"ਸੈਟ ਨਹੀਂ ਕੀਤਾ"</string>
+ <string name="apn_name" msgid="1572691851070894985">"ਨਾਮ"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS ਪ੍ਰੌਕਸੀ"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS ਪੋਰਟ"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN ਪ੍ਰਕਾਰ"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN ਮਿਟਾਓ"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"ਨਵਾਂ APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"ਸੁਰੱਖਿਅਤ ਕਰੋ"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ਅਣਡਿੱਠਾ"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"ਨਾਮ ਖੇਤਰ ਨੂੰ ਖਾਲੀ ਨਹੀਂ ਛੱਡਿਆ ਜਾ ਸਕਦਾ।"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ਨੂੰ ਖਾਲੀ ਨਹੀਂ ਛੱਡਿਆ ਜਾ ਸਕਦਾ।"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ਖੇਤਰ 3 ਅੰਕਾਂ ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ।"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ਖੇਤਰ 2 ਜਾਂ 3 ਅੰਕਾਂ ਦਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ।"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"ਡਿਫੌਲਟ APN ਸੈਟਿੰਗਾਂ ਰੀਸਟੋਰ ਕਰ ਰਿਹਾ ਹੈ।"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"ਡਿਫੌਲਟਸ ਤੇ ਰੀਸੈਟ ਕਰੋ"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"ਡਿਫੌਲਟ APN ਸੈਟਿੰਗਾਂ ਰੀਸੈਟ ਕਰਨਾ ਪੂਰਾ ਹੋਇਆ।"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ਬਿਨਾਂ ਨਾਮ"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ਪਹੁੰਚ ਬਿੰਦੂ ਨਾਮ"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"ਨਵਾਂ APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ਪਹੁੰਚ ਬਿੰਦੂ ਨਾਮ ਸੈਟਿੰਗਾਂ ਇਸ ਉਪਭੋਗਤਾ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹਨ"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"ਕੀ ਕਲਿਪਬੋਰਡ ਤੇ ਕਾਪੀ ਕਰਨਾ ਹੈ?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"ਕਾਪੀ ਕਰੋ"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> ਨੂੰ"</string>
+ <string name="general_settings" msgid="5409336577057897710">"ਸਧਾਰਨ"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"ਉੱਨਤ"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"ਸਧਾਰਨ ਸੈਟਿੰਗਾਂ"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"ਉੱਨਤ ਸੈਟਿੰਗਾਂ"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ਸਾਰੇ ਪ੍ਰਾਪਤਕਰਤਾਵਾਂ ਨੂੰ ਵੱਖ-ਵੱਖ SMS ਸੁਨੇਹੇ ਭੇਜੋ। ਕੇਵਲ ਤੁਸੀਂ ਹੀ ਕੋਈ ਜਵਾਬ ਪ੍ਰਾਪਤ ਕਰੋਗੇ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"ਸਾਰੇ ਪ੍ਰਾਪਤਕਰਤਾਵਾਂ ਨੂੰ ਇੱਕ ਸਿੰਗ MMS ਭੇਜੋ।"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"ਅਗਿਆਤ ਨੰਬਰ"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"ਨਵਾਂ ਸੁਨੇਹਾ"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"ਨਵਾਂ ਸੁਨੇਹਾ।"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM ਚੋਣਕਾਰ"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ਚੁਣਿਆ ਗਿਆ, SIM ਚੋਣਕਰਤਾ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"ਵਿਸ਼ਾ ਸੰਪਾਦਿਤ ਕਰੋ"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM ਚੁਣੋ ਜਾਂ ਵਿਸ਼ਾ ਸੰਪਾਦਿਤ ਕਰੋ"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ਔਡੀਓ ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"ਨਵੀਂ ਗੱਲਬਾਤ ਚਾਲੂ ਕਰੋ"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Messaging"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Messaging ਸੂਚੀ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Messaging"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"ਨਵਾਂ ਸੁਨੇਹਾ"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"ਗੱਲਬਾਤ ਸੂਚੀ"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"ਗੱਲਬਾਤਾਂ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"ਸੁਨੇਹੇ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"ਹੋਰ ਗੱਲਬਾਤਾਂ ਦੇਖੋ"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"ਹੋਰ ਸੁਨੇਹੇ ਦੇਖੋ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"ਗੱਲਬਾਤ ਮਿਟਾਈ ਗਈ"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"ਗੱਲਬਾਤ ਮਿਟਾਈ ਗਈ। ਇੱਕ ਵੱਖਰੀ Messaging ਗੱਲਬਾਤ ਦਿਖਾਉਣ ਲਈ ਛੋਹਵੋ"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ਬਲੌਕ ਕੀਤਾ"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"ਅਨਬਲੌਕ ਕੀਤਾ"</string>
+ <string name="db_full" msgid="8459265782521418031">"ਸਟੋਰੇਜ ਸਪੇਸ ਘੱਟ ਹੈ। ਕੁਝ ਡਾਟਾ ਨਸ਼ਟ ਹੋ ਸਕਦਾ ਹੈ।"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ਅਟੈਚਮੈਂਟਾਂ ਚੁਣੋ"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"ਚੋਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ਚੁਣਿਆ ਗਿਆ"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਜਾਂ ਵੱਧ ਅਟੈਚਮੈਂਟਾਂ ਹਟਾਓ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"ਤੁਸੀਂ ਆਪਣਾ ਸੁਨੇਹਾ ਭੇਜਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹੋ, ਪਰੰਤੂ ਇਹ ਸ਼ਾਇਦ ਨਾ ਭੇਜਿਆ ਜਾ ਸਕੇ, ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਇੱਕ ਜਾਂ ਵੱਧ ਅਟੈਚਮੈਂਟਾਂ ਨਹੀਂ ਹਟਾਉਂਦੇ।"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"ਤੁਸੀਂ ਪ੍ਰਤੀ ਸੁਨੇਹਾ ਕੇਵਲ ਇੱਕ ਵੀਡੀਓ ਭੇਜ ਸਕਦੇ ਹੋ। ਕਿਰਪਾ ਕਰਕੇ ਵਾਧੂ ਵੀਡੀਓ ਹਟਾਓ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Messaging ਅਟੈਚਮੈਂਟ ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ।"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ਫੇਰ ਵੀ ਭੇਜੋ"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"ਗੱਲਬਾਤ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ਚੁਣਿਆ ਗਿਆ"</string>
+</resources>
diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml
new file mode 100644
index 0000000..2bd82e4
--- /dev/null
+++ b/res/values-pl/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"brak tematu"</item>
+ <item msgid="272485471009191934">"braktematu"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Tak"</item>
+ <item msgid="6049132459802288033">"Nie"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Ha ha"</item>
+ <item msgid="2611328818571146775">"Dzięki"</item>
+ <item msgid="4881335087096496747">"Zgadzam się"</item>
+ <item msgid="2422296858597420738">"Super"</item>
+ <item msgid="4805581752819452687">"Jestem w drodze"</item>
+ <item msgid="4746700499431366214">"Skontaktuję się z Tobą później"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..0ee2511
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Wiadomości"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Wiadomości"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Wybierz wątek"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Ustawienia"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Wyślij wiadomość"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Dodaj załącznik"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Pomoc"</string>
+ <string name="welcome" msgid="2857560951820802321">"Witamy"</string>
+ <string name="skip" msgid="7238879696319945853">"Pomiń"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Dalej &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Dalej"</string>
+ <string name="exit" msgid="1905187380359981199">"Zakończ"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Ustawienia &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Ustawienia"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Aplikacja SMS/MMS potrzebuje uprawnień do SMS-ów, telefonu i kontaktów."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Uprawnienia możesz zmienić, klikając Ustawienia &gt; Aplikacje &gt; SMS/MMS &gt; Uprawnienia."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Uprawnienia możesz zmienić, klikając Ustawienia, Aplikacje, SMS/MMS, Uprawnienia."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Częste kontakty"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Wszystkie kontakty"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Wyślij pod numer <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Zrób zdjęcia lub nagraj film"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Wybierz obrazy z tego urządzenia"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Nagraj dźwięk"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Wybierz zdjęcie"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Plik multimedialny został wybrany."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Plik multimedialny nie został wybrany."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Wybrano: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"obraz – <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" msgid="8142175029053370662">"obraz"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Nagraj dźwięk"</string>
+ <string name="action_share" msgid="2143483844803153871">"Udostępnij"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"W tej chwili"</string>
+ <string name="posted_now" msgid="867560789350406701">"Teraz"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> godziny</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> godzin</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> godziny</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> godzina</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dni</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> dni</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dnia</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dzień</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> tygodnie</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> tygodni</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tygodnia</item>
+ <item quantity="one">tydzień</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> miesiące</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> miesięcy</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> miesiąca</item>
+ <item quantity="one">miesiąc</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> lata</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> lat</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> roku</item>
+ <item quantity="one">rok</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Wiadomość klasy 0"</string>
+ <string name="save" msgid="5081141452059463572">"Zapisz"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Na urządzeniu jest mało miejsca. Aplikacja SMS/MMS automatycznie usunie starsze SMS-y, by zwolnić miejsce."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Kończy się miejsce"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Aplikacja SMS/MMS może nie wysyłać i nie odbierać wiadomości do momentu zwolnienia miejsca na urządzeniu."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Mało miejsca na SMS-y. Może usuniesz niektóre wiadomości?"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Potwierdź swój numer telefonu"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Dzięki tej jednorazowej czynności aplikacja SMS/MMS będzie poprawnie dostarczać Twoje wiadomości grupowe."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Numer telefonu"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Usuń wszystkie MMS-y"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Usuń wiadomości starsze niż <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Automatycznie usuwaj wiadomości starsze niż <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignoruj"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Usunąć wszystkie MMS-y?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Usunąć wiadomości starsze niż <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Usunąć wiadomości starsze niż <xliff:g id="DURATION">%s</xliff:g> i włączyć automatyczne usuwanie?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> pisze"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Treść Twojej wiadomości"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Wiadomość od użytkownika <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Wysłałeś wiadomość"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Wysyłam…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nie wysłano. Kliknij, by spróbować ponownie."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nie wysłano. Próbuję ponownie…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Wyślij ponownie lub usuń"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Zadzwoń do służb ratunkowych. Tym razem nie udało się dostarczyć SMS-a."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Błąd"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nowa wiadomość MMS do pobrania"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nowa wiadomość MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nie udało się pobrać"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Kliknij, by spróbować ponownie"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Kliknij, by pobrać"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Pobierz lub usuń"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Pobieram…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Wiadomość wygasła lub jest niedostępna"</string>
+ <string name="mms_info" msgid="3402311750134118165">"rozmiar: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, termin ważności: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nie można wysłać. Nieprawidłowy adresat."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Usługa nie została aktywowana w sieci"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Nie udało się wysłać z powodu problemu z siecią"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Wiadomość wygasła lub jest niedostępna"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Brak tematu)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Nieznany nadawca"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Dostarczono"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nie udało się pobrać wiadomości <xliff:g id="SUBJECT">%1$s</xliff:g> od użytkownika <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Nie można dokończyć operacji na bazie danych z powodu zbyt małej ilości pamięci"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Wiadomość nie została wysłana"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Niektóre wiadomości nie zostały wysłane w aplikacji SMS/MMS"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> wiadomości w <xliff:g id="CONVERSATIONS">%d</xliff:g> wątkach</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> wiadomości w <xliff:g id="CONVERSATIONS">%d</xliff:g> wątkach</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> wiadomości w <xliff:g id="CONVERSATIONS">%d</xliff:g> wątku</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> wiadomości w jednym wątku</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Wiadomość nie została pobrana"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Niektóre wiadomości nie zostały pobrane w aplikacji SMS/MMS"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> wiadomości w <xliff:g id="CONVERSATIONS">%d</xliff:g> wątkach</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> wiadomości w <xliff:g id="CONVERSATIONS">%d</xliff:g> wątkach</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> wiadomości w <xliff:g id="CONVERSATIONS">%d</xliff:g> wątku</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> wiadomości w jednym wątku</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Wiadomość do <xliff:g id="NUMBER">%1$s</xliff:g> nie została wysłana"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Zadzwoń do służb ratunkowych. Tym razem nie udało się dostarczyć SMS-a pod numer <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> nowe wiadomości</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> nowych wiadomości</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nowej wiadomości</item>
+ <item quantity="one">Nowa wiadomość</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Rozpocznij"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Aparat jest niedostępny"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Aparat jest niedostępny"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Nagrywanie filmu jest niedostępne"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Nie można zapisać pliku multimedialnego"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nie można zrobić zdjęcia"</string>
+ <string name="back" msgid="1477626055115561645">"Wstecz"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archiwum"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Usuń"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archiwizuj"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Przywróć z archiwum"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Wyłącz powiadomienia"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Włącz powiadomienia"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Dodaj kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Pobierz"</string>
+ <string name="action_send" msgid="377635240181672039">"Wyślij"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Usuń"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Usunąć tę wiadomość?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Nie można tego cofnąć."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Usuń"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="few">Usunąć te wątki?</item>
+ <item quantity="many">Usunąć te wątki?</item>
+ <item quantity="other">Usunąć te wątki?</item>
+ <item quantity="one">Usunąć ten wątek?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Usuń"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Anuluj"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Do"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Wybierz wiele obrazów"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Potwierdź wybór"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"Jeszcze <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Nie można nagrać dźwięku. Spróbuj ponownie."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Nie można odtworzyć dźwięku. Spróbuj ponownie."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Nie można zapisać dźwięku. Spróbuj ponownie."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Kliknij i przytrzymaj"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Zdjęcie"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Klip dźwiękowy"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Film"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Wizytówka"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Pobierz"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Odpowiedz SMS-em"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Wyślij MMS-a"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Odpowiedz"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> uczestników</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> uczestników</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> uczestnika</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> uczestnik</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ja"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt został zablokowany i zarchiwizowany"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt odblokowany i rozpakowany"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Zarchiwizowano: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Przywrócone z archiwum: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Powiadomienia wyłączone"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Powiadomienia włączone"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Wszystko gotowe. Ponownie kliknij Wyślij."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"SMS/MMS to teraz domyślna aplikacja do SMS-ów."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="few">Odrzuć załączniki</item>
+ <item quantity="many">Odrzuć załączniki</item>
+ <item quantity="other">Odrzuć załączniki</item>
+ <item quantity="one">Odrzuć załącznik</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Załącznik audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Odtwórz załącznik audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pauza"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Wiadomość od użytkownika <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Nieudane odbieranie wiadomości od: <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Wiadomość od: <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Niewysłana wiadomość do: <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Wysyłanie wiadomości do: <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Nieudane wysyłanie wiadomości do: <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Wiadomość do: <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Nieudane odbieranie wiadomości od: <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Wiadomość od: <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Niewysłana wiadomość do: <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Wysyłanie wiadomości do: <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Nieudane wysyłanie wiadomości do: <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Wiadomość do: <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Godzina: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Niedostarczona wiadomość. Kliknij, aby ponowić próbę."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Rozmowa z: <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Usuń temat"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Nagraj film"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Zrób zdjęcie"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Zrób zdjęcie"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Zacznij nagrywać film"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Włącz aparat pełnoekranowy"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Przełącz między kamerą z przodu i z tyłu"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Zatrzymaj nagrywanie i dołącz film"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Zatrzymaj nagrywanie filmu"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Zdjęcia z aplikacji SMS/MMS"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> zdjęcia zapisane w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> zdjęć zapisanych w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> zdjęcia zapisane w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> zdjęcie zapisane w albumie „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> filmy zapisane w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> filmów zapisanych w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> filmu zapisane w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> film zapisany w albumie „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> załączniki zapisane w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> załączników zapisanych w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> załącznika zapisane w albumie „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> załącznik zapisany w albumie „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> załączniki zapisane w folderze „Pobrane pliki”</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> załączników zapisanych w folderze „Pobrane pliki”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> załącznika zapisane w folderze „Pobrane pliki”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> załącznik zapisany w folderze „Pobrane pliki”</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> załączniki zapisane</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> załączników zapisanych</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> załącznika zapisane</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> załącznik zapisany</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="few">Nie udało się zapisać <xliff:g id="QUANTITY_1">%d</xliff:g> załączników</item>
+ <item quantity="many">Nie udało się zapisać <xliff:g id="QUANTITY_1">%d</xliff:g> załączników</item>
+ <item quantity="other">Nie udało się zapisać <xliff:g id="QUANTITY_1">%d</xliff:g> załącznika</item>
+ <item quantity="one">Nie udało się zapisać <xliff:g id="QUANTITY_0">%d</xliff:g> załącznika</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Zapisany załącznik MMS-a"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Ustawienia"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Zarchiwizowane"</string>
+ <string name="action_close" msgid="1840519376200478419">"Zamknij"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Zaawansowane"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Debugowanie"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Powiadomienia"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Dźwięk"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Cichy"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Wibracje"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Zablokowany"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Raporty doręczeń SMS-ów"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Otrzymuj raport o dostarczeniu każdego wysłanego SMS-a"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Autopobieranie"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automatycznie pobieraj MMS-y"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Autopobieranie w roamingu"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automatycznie pobieraj MMS-y w roamingu"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Wiadomości grupowe"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Używaj MMS-ów, gdy chcę wysłać jedną wiadomość do wielu odbiorców"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Domyślna aplikacja do SMS-ów"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Domyślna aplikacja do SMS-ów"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Twój numer telefonu"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Nieznany"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Dźwięki przy wysyłaniu wiadomości"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Zrzuć SMS-y"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Zrzuć odebrane nieprzetworzone dane SMS-ów do pliku w pamięci zewnętrznej"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Zrzuć MMS-y"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Zrzuć odebrane nieprzetworzone dane MMS-ów do pliku w pamięci zewnętrznej"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alerty o zagrożeniach"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opcje wiadomości"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Skopiuj tekst"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Wyświetl szczegóły"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Usuń"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Przekaż dalej"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Szczegóły wiadomości"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Typ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Wiadomość multimedialna"</string>
+ <string name="from_label" msgid="1947831848146564875">"Od: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Do: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Wysłano: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Odebrane: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Temat: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Rozmiar: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorytet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Wysoki"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normalny"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Niski"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Ukryty adres nadawcy"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Nie można wysłać wiadomości w czasie ładowania załączników."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nie można załadować załącznika. Spróbuj ponownie."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Sieć nie jest gotowa. Spróbuj ponownie."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Usuń tekst"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Przełącz się między wpisywaniem tekstu i cyfr"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Dodaj uczestników"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Potwierdź uczestników"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Rozpocznij nowy wątek"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Wybierz ten element"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Odtwórz film"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Osoby i opcje"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Debuguj"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Osoby i opcje"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Ogólne"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Uczestnicy tej rozmowy"</string>
+ <string name="action_call" msgid="6596167921517350362">"Zadzwoń"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Wyślij wiadomość"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Wyślij wiadomość&lt;br/&gt;&lt;small&gt;z <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="few">Wyślij zdjęcia</item>
+ <item quantity="many">Wyślij zdjęcia</item>
+ <item quantity="other">Wyślij zdjęcia</item>
+ <item quantity="one">Wyślij zdjęcie</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="few">Wyślij pliki audio</item>
+ <item quantity="many">Wyślij pliki audio</item>
+ <item quantity="other">Wyślij pliki audio</item>
+ <item quantity="one">Wyślij plik audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="few">Wyślij filmy</item>
+ <item quantity="many">Wyślij filmy</item>
+ <item quantity="other">Wyślij filmy</item>
+ <item quantity="one">Wyślij film</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="few">Wyślij wizytówki</item>
+ <item quantity="many">Wyślij wizytówki</item>
+ <item quantity="other">Wyślij wizytówki</item>
+ <item quantity="one">Wyślij wizytówkę</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="few">Wyślij załączniki</item>
+ <item quantity="many">Wyślij załączniki</item>
+ <item quantity="other">Wyślij załączniki</item>
+ <item quantity="one">Wyślij załącznik</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> załączniki gotowe do wysłania</item>
+ <item quantity="many"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> załączników gotowych do wysłania</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> załącznika gotowe do wysłania</item>
+ <item quantity="one">Jeden załącznik gotowy do wysłania</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Prześlij opinię"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Zobacz w Sklepie Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informacje o wersji"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Wersja %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licencje open source"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Powiadomienia"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Osiągnięto limit załączników"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Nie udało się załadować załącznika."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Dodać do kontaktów?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Dodaj kontakt"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Temat"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Temat: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Ładowanie wizytówki"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Nie można załadować wizytówki"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Wyświetl wizytówkę"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontakty</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> kontaktów</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktu</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Wizytówki"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Urodziny"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notatki"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Przekaż wiadomość dalej"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Odpowiedz"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS-y wyłączone"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Aby wysyłać wiadomości, ustaw SMS/MMS jako domyślną aplikację do SMS-ów"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Ustaw SMS/MMS jako domyślną aplikację do SMS-ów"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Zmień"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Aby odbierać wiadomości, ustaw SMS/MMS jako domyślną aplikację do SMS-ów"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nie wybrano preferowanej karty SIM do wysyłania SMS-ów"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Właściciel urządzenia nie zezwolił na działanie tej aplikacji."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Zbyt wielu uczestników rozmowy"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="few">Nieprawidłowe kontakty</item>
+ <item quantity="many">Nieprawidłowe kontakty</item>
+ <item quantity="other">Nieprawidłowe kontakty</item>
+ <item quantity="one">Nieprawidłowy kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Nie udało się załadować zdjęcia z aparatu"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ty: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Wersja robocza"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Po rozpoczęciu nowej rozmowy zobaczysz ją tutaj"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Tutaj będą widoczne zarchiwizowane wątki"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Ładuję wątki…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Zdjęcie"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Klip dźwiękowy"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Film"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Wizytówka"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Cofnij"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Ponów"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Wpisz nazwę kontaktu lub numer telefonu, by rozpocząć nową wiadomość"</string>
+ <string name="action_block" msgid="9032076625645190136">"Zablokuj"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Zablokuj: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Odblokuj: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Zablokować <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Będziesz nadal otrzymywać wiadomości z tego numeru, ale nie będzie już powiadomień o nich. Ten wątek zostanie zarchiwizowany."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Zablokowane kontakty"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ODBLOKUJ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Zablokowane kontakty"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Wybierz obraz z biblioteki dokumentów"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Wysyłam wiadomość"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Wiadomość wysłana"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Komórkowa transmisja danych jest wyłączona. Sprawdź ustawienia."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"W trybie samolotowym nie można wysyłać wiadomości"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Nie można wysłać wiadomości"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Wiadomość pobrana"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Komórkowa transmisja danych jest wyłączona. Sprawdź ustawienia."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Wiadomości nie można pobierać w trybie samolotowym"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Nie udało się pobrać wiadomości"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Jeden"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dwa"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Trzy"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Cztery"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Pięć"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sześć"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Siedem"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Osiem"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Dziewięć"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Nie można wysłać wiadomości w sieci <xliff:g id="CARRIERNAME">%1$s</xliff:g> (błąd <xliff:g id="ERRORCODE">%2$d</xliff:g>)"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Nie można wysłać wiadomości w sieci nieznanego operatora (błąd <xliff:g id="ERRORCODE">%1$d</xliff:g>)"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"PD: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Nie wysłano wiadomości: usługa nie została aktywowana w sieci"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Wiadomość nie została wysłana: nieprawidłowy adres docelowy"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Wiadomość nie została wysłana: nieprawidłowa wiadomość"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Wiadomość nie została wysłana: nieobsługiwane treści"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Wiadomość nie została wysłana: nieobsługiwana wiadomość"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Wiadomość nie została wysłana: jest za duża"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nowa wiadomość"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Wyświetl"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Obraz"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nie udało się znaleźć stosownej aplikacji"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Usuń odbiorcę"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nowa wiadomość"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Anuluj"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Edytuj punkt dostępu"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nie ustawiono"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nazwa"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy dla MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Typ APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Usuń APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nowy APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Zapisz"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Odrzuć"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Pole Nazwa nie może być puste."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Nazwa APN nie może być pusta."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Pole MCC musi zawierać 3 cyfry."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Pole MNC musi zawierać 2 lub 3 cyfry."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Przywracam domyślne ustawienia APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Przywróć domyślne"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Przywrócono domyślne ustawienia APN."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Bez nazwy"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nazwy punktów dostępu"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-y"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nowy APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Ustawienia nazwy punktu dostępu są niedostępne dla tego użytkownika"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Skopiować do schowka?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiuj"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"do <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Ogólne"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Zaawansowane"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Ustawienia ogólne"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Ustawienia zaawansowane"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Karta SIM „<xliff:g id="SIM_NAME">%s</xliff:g>”"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Wyślij osobne SMS-y do wszystkich odbiorców. Odpowiedzi trafią tylko do Ciebie"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Wysyłaj jednego MMS-a do wszystkich odbiorców"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nieznany numer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nowa wiadomość"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nowa wiadomość."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Przełącznik SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Przełącznik SIM, wybrana karta: <xliff:g id="SIM_0">%1$s</xliff:g>"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Edytuj temat"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Wybierz SIM lub edytuj temat"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Dotknij i przytrzymaj, aby nagrać dźwięk"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Zacznij nową rozmowę"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Wiadomości"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista wiadomości"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Wiadomości"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nowa wiadomość"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista wątków"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Ładuję wątki"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Wczytuję wiadomości"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Wyświetl więcej wątków"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Wyświetl więcej wiadomości"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Wątek został usunięty"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Rozmowa usunięta. Kliknij, by zobaczyć inną rozmowę w aplikacji SMS/MMS"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Zablokowano"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Odblokowano"</string>
+ <string name="db_full" msgid="8459265782521418031">"Mała ilość miejsca. Część danych może zostać utracona."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Wybierz załączniki"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Potwierdź wybór"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Wybrano <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Usuń co najmniej jeden załącznik i spróbuj ponownie."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Możesz spróbować wysłać wiadomość, ale może ona nie zostać dostarczona, dopóki nie usuniesz przynajmniej jednego załącznika."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"W jednej wiadomości możesz wysłać tylko jeden film. Usuń dodatkowe filmy i spróbuj ponownie."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Nie udało się załadować załącznika w aplikacji SMS/MMS."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Wyślij mimo wszystko"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Nie udało się rozpocząć rozmowy"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Wybrano <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-pt-rPT/arrays.xml b/res/values-pt-rPT/arrays.xml
new file mode 100644
index 0000000..3f99811
--- /dev/null
+++ b/res/values-pt-rPT/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sem assunto"</item>
+ <item msgid="272485471009191934">"sem assunto"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Sim"</item>
+ <item msgid="6049132459802288033">"Não"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Eh eh"</item>
+ <item msgid="2611328818571146775">"Obrigado"</item>
+ <item msgid="4881335087096496747">"Aceito"</item>
+ <item msgid="2422296858597420738">"Boa"</item>
+ <item msgid="4805581752819452687">"Estou a caminho"</item>
+ <item msgid="4746700499431366214">"OK, ligo mais tarde"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..e546031
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mensagens"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mensagens"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Selecionar conversa"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Definições"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Enviar mensagem"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Adicionar um anexo"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ajuda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Bem-vindo"</string>
+ <string name="skip" msgid="7238879696319945853">"Ignorar"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Seguinte &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Seguinte"</string>
+ <string name="exit" msgid="1905187380359981199">"Sair"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Definições &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Definições"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"A aplicação Mensagens necessita de autorização para SMS, Telefone e Contactos."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Pode alterar as autorizações em Definições &gt; Aplicações &gt; Mensagens &gt; Autorizações."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Pode alterar as autorizações em Definições, Aplicações, Mensagens, Autorizações."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frequentes"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Todos os contactos"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Enviar para <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capturar imagens ou vídeo"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Escolher imagens deste dispositivo"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Gravar áudio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Escolher fotografia"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"O ficheiro multimédia está selecionado."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"O ficheiro multimédia está desselecionado."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> selecionado(s)"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imagem de <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" msgid="8142175029053370662">"imagem"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Gravar áudio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Partilhar"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Agora mesmo"</string>
+ <string name="posted_now" msgid="867560789350406701">"Agora"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dias</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dia</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semanas</item>
+ <item quantity="one">uma semana</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> meses</item>
+ <item quantity="one">um mês</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> anos</item>
+ <item quantity="one">um ano</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mensagem de classe 0"</string>
+ <string name="save" msgid="5081141452059463572">"Guardar"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"O dispositivo tem pouco espaço disponível. A aplicação Mensagens elimina automaticamente mensagens mais antigas para libertar espaço."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Está quase sem espaço de armazenamento"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"A aplicação Mensagens pode não enviar ou receber mensagens até que exista mais espaço disponível no dispositivo."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Armazenamento SMS reduzido. Pode ter de eliminar mensagens."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirmar número de telefone"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Este passo único assegura que a aplicação Mensagens fornece as suas mensagens de grupo corretamente."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Número de telefone"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Eliminar todas as mensagens com multimédia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Eliminar mensagens com mais de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Eliminar automaticamente mensagens com mais de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorar"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Eliminar todas as mensagens com multimédia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Eliminar mensagens com mais de <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Eliminar mensagens com mais de <xliff:g id="DURATION">%s</xliff:g> e ativar eliminação automática?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> disse"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Você disse"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Enviou uma mensagem"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"A enviar..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Não enviado. Toque para tentar novamente."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Não enviado. A tentar novamente..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Reenviar ou eliminar"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Faça uma chamada de voz para os serviços de emergência. Neste momento, não foi possível entregar a sua mensagem de texto."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Falha"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nova mensagem MMS para transferir"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nova mensagem MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Não foi possível transferir"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Toque para tentar novamente"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Toque para transferir"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Transferir ou eliminar"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"A transferir..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Mensagem expirada ou não disponível"</string>
+ <string name="mms_info" msgid="3402311750134118165">"tamanho: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, data de validade: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Não é possível enviar. O destinatário não é válido."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Serviço não ativado na rede"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Não foi possível enviar devido a um problema de rede"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Mensagem expirada ou não disponível"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Sem assunto)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Remetente desconhecido"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Entregue"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Não foi possível transferir a mensagem <xliff:g id="SUBJECT">%1$s</xliff:g> de <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Não foi possível concluir a operação da base de dados devido a pouca memória"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mensagem não enviada"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Algumas mensagens não enviadas na aplicação Mensagens"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensagens em <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensagens numa conversa</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mensagem não transferida"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Algumas mensagens não transferidas na aplicação Mensagens"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensagens em <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mensagens numa conversa</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mensagem para <xliff:g id="NUMBER">%1$s</xliff:g> não enviada"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Faça uma chamada de voz para os serviços de emergência. Neste momento, não foi possível entregar a sua mensagem de texto para o número <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> novas mensagens</item>
+ <item quantity="one">Nova mensagem</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Iniciar"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Câmara não disponível"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Câmara não disponível"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Captura de vídeo não disponível"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Não é possível guardar multimédia"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Não é possível tirar a fotografia"</string>
+ <string name="back" msgid="1477626055115561645">"Anterior"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arquivadas"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Eliminar"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arquivar"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Retirar do arquivo"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desativar as notificações"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Ativar notificações"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Adicionar contacto"</string>
+ <string name="action_download" msgid="7786338136368564146">"Transferir"</string>
+ <string name="action_send" msgid="377635240181672039">"Enviar"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Eliminar"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Pretende eliminar esta mensagem?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Esta ação não pode ser anulada."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Eliminar"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Pretende eliminar estas conversas?</item>
+ <item quantity="one">Pretende eliminar esta conversa?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Eliminar"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancelar"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Para"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Selecionar várias imagens"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmar seleção"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Não é possível gravar áudio. Tente novamente."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Não é possível reproduzir áudio. Tente novamente."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Não foi possível guardar áudio. Tente novamente."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Toque sem largar"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imagem"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clipe de áudio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vídeo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Cartão de contacto"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Transferir"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Responder por SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Responder por MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Responder"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participantes</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participante</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Eu"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contacto bloqueado e arquivado"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contacto desbloqueado e não arquivado"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> arquivada(s)"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> não arquivada(s)"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificações desativadas"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificações ativadas"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Tudo definido. Toque novamente em Enviar."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"A aplicação Mensagens foi definida com êxito como a aplicação de SMS predefinida."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Rejeitar anexos</item>
+ <item quantity="one">Rejeitar anexo</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Anexo de áudio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reproduzir anexo de áudio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Interromper"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mensagem falhada de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mensagem não enviada para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"A enviar mensagem para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mensagem falhada para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mensagem para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mensagem falhada de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mensagem não enviada para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"A enviar mensagem para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mensagem falhada para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mensagem para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mensagem falhada. Toque para tentar novamente."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversa com <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Eliminar assunto"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capturar vídeo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturar uma imagem fixa"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Tirar fotografia"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Começar a gravar vídeo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Mudar para a câmara de ecrã completo"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Alternar entre as câmaras frontal e traseira"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Parar gravação e anexar vídeo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Parar a gravação de vídeo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos das Mensagens"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotos guardadas no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto guardada no álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vídeos guardados no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> vídeo guardado no álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> anexos guardados no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> anexo guardado no álbum \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> anexos guardados em \"Transferências\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> anexo guardado em \"Transferências\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> anexos guardados</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> anexo guardado</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Não foi possível guardar <xliff:g id="QUANTITY_1">%d</xliff:g> anexos</item>
+ <item quantity="one">Não foi possível guardar <xliff:g id="QUANTITY_0">%d</xliff:g> anexo</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Anexo de MMS guardado"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Definições"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arquivadas"</string>
+ <string name="action_close" msgid="1840519376200478419">"Fechar"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avançadas"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Depurar"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificações"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Som"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silencioso"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrar"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqueado"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Relatórios de entrega de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Solicitar um relatório de entrega para cada SMS enviada"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recuperação automática"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recuperar automaticamente mensagens MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Recuperar automat. em roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Obter automaticamente MMS em roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mensagens de grupo"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utilizar MMS para enviar uma única mensagem quando houver vários destinatários"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplicação de SMS predefinida"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplicação de SMS predefinida"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"O seu número de telefone"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Desconhecido"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sons de mensagem enviada"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Criar cópia de segurança de SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"A cópia de segurança recebeu dados SMS não processados no ficheiro de armazenamento externo"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Criar cópia de segurança de MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"A cópia de segurança recebeu dados MMS não processados no ficheiro de armazenamento externo"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertas sem fios"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opções da mensagem"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copiar texto"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ver detalhes"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Eliminar"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Encaminhar"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalhes da mensagem"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mensagem de texto"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mensagem multimédia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Para: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Enviadas: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Recebidas: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Assunto: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Tamanho: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioridade: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Baixa"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Endereço do remetente oculto"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Não é possível enviar a mensagem enquanto os anexos estiverem a ser carregados."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Não é possível carregar o anexo. Tente novamente."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"A rede não está pronta. Tente novamente."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Eliminar texto"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Alternar entre a introdução de texto e números"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Adicionar mais participantes"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmar participantes"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Iniciar nova conversa"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Selecionar este item"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reproduzir vídeo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Pessoas e opções"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Depurar"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Pessoas e opções"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Geral"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Pessoas nesta conversa"</string>
+ <string name="action_call" msgid="6596167921517350362">"Efetuar uma chamada"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Enviar mensagem"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Enviar mensagem&lt;br/&gt;&lt;small&gt;de <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Enviar fotos</item>
+ <item quantity="one">Enviar foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Enviar ficheiros de áudio</item>
+ <item quantity="one">Enviar ficheiro de áudio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Enviar vídeos</item>
+ <item quantity="one">Enviar vídeo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Enviar cartões de contacto</item>
+ <item quantity="one">Enviar cartão de contacto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Enviar anexos</item>
+ <item quantity="one">Enviar anexo</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> anexos prontos a enviar</item>
+ <item quantity="one">Um anexo pronto a enviar</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Enviar comentários"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ver na Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informações da versão"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versão %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licenças de código aberto"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificações"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Foi atingido o limite de anexos"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Falha ao carregar o anexo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Adicionar a Contactos?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Adicionar contacto"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Assunto"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Assunto: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"A carregar o cartão de contacto"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Não foi possível carregar o cartão de contacto"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ver cartão de contacto"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contactos</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> contacto</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Cartões de contacto"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Aniversário"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notas"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Encaminhar mensagem"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Responder"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS desativado"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Para enviar, defina a aplicação Mensagens como a aplicação de SMS predefinida"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Defina a aplicação Mensagens como a aplicação de SMS predefinida"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Alterar"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Para receber mensagens, defina a aplicação Mensagens como a aplicação de SMS predefinida"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nenhum SIM preferido selecionado para o envio de mensagens SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Esta aplicação não é permitida pelo proprietário do dispositivo."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Demasiados participantes numa conversa"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Contactos inválidos</item>
+ <item quantity="one">Contacto inválido</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Não foi possível carregar a imagem da câmara"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Utilizador: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Rascunho"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Ao iniciar uma nova conversa, esta será listada aqui"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"As conversas arquivadas aparecem aqui"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"A carregar conversações..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imagem"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clipe de áudio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vídeo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Cartão de contacto"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Anular"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Tentar novamente"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Introduza um nome ou número de telefone do contacto para iniciar uma nova mensagem"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquear"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquear <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desbloquear <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Pretende bloquear <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"O utilizador continuará a receber mensagens deste número, mas deixará de ser notificado. Esta conversa será arquivada."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contactos bloqueados"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOQUEAR"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contactos bloqueados"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Escolher imagem da biblioteca de documentos"</string>
+ <string name="sending_message" msgid="6363584950085384929">"A enviar mensagem"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mensagem enviada"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Os dados celulares estão desligados. Verifique as suas definições."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Não é possível enviar mensagens no modo de avião"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Não foi possível enviar a mensagem"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mensagem transferida"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Os dados celulares estão desligados. Verifique as suas definições."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Não é possível transferir mensagens no modo de avião"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Não foi possível transferir a mensagem"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Um"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dois"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Três"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Quatro"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinco"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seis"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sete"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Oito"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nove"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Não é possível enviar a mensagem com <xliff:g id="CARRIERNAME">%1$s</xliff:g>, erro <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Não é possível enviar a mensagem com operador desconhecido, erro <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Enc: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mensagem não enviada: serviço não ativado na rede"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mensagem não enviada: endereço de destino inválido"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mensagem não enviada: mensagem inválida"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mensagem não enviada: conteúdo não suportado"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mensagem não enviada: a mensagem não é compatível"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mensagem não enviada: demasiado grande"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nova mensagem"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ver"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imagem"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Não foi possível encontrar uma aplicação adequada"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Remover o destinatário"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nova mensagem"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancelar"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Editar ponto de acesso"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Não definido"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nome"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy de MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Porta MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipo de APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Eliminar APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Novo APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Guardar"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Rejeitar"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"O campo Nome não pode estar vazio."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"O APN não pode estar vazio."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"O campo MCC tem de ter 3 dígitos."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"O campo MNC tem de ter 2 ou 3 dígitos."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"A restaurar as predefinições do APN"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Repor as predefinições"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Reposição das predefinições do APN concluída."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sem nome"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nomes dos Pontos de Acesso"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Novo APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"As definições de Nome do Ponto de Acesso não estão disponíveis para este utilizador"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Pretende copiar para a área de transferência?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copiar"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"em <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Geral"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avançadas"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Definições gerais"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Definições avançadas"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Enviar mensagens SMS individuais para todos os destinatários. Apenas o utilizador recebe as respostas"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Enviar uma única MMS para todos os destinatários"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Número desconhecido"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nova mensagem"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nova mensagem."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Seletor de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> selecionado, seletor de SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editar assunto"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Selecionar SIM ou editar assunto"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Tocar sem soltar para gravar áudio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Iniciar nova conversa"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mensagens"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista de mensagens"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mensagens"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nova mensagem"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista de conversas"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"A carregar conversas"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"A carregar mensagens"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ver mais conversas"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Ver mais mensagens"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversa eliminada"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversa eliminada. Toque para mostrar uma conversa das Mensagens diferente"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloqueado"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Desbloqueado"</string>
+ <string name="db_full" msgid="8459265782521418031">"O espaço de armazenamento é reduzido. Alguns dados podem ser perdidos."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Selecionar anexos"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmar seleção"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> selecionado(s)"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Remova um ou mais anexos e tente novamente."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Pode tentar enviar a sua mensagem, mas esta pode não ser entregue se não remover um ou mais anexos."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Apenas pode enviar um vídeo por mensagem. Remova os vídeos adicionais e tente novamente."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Falha da aplicação Mensagens ao carregar o anexo."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Enviar mesmo assim"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Não foi possível iniciar a conversa"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> selecionado"</string>
+</resources>
diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml
new file mode 100644
index 0000000..0ab0334
--- /dev/null
+++ b/res/values-pt/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"sem assunto"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Sim"</item>
+ <item msgid="6049132459802288033">"Não"</item>
+ <item msgid="3084376867445867895">"Ok"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Obrigado"</item>
+ <item msgid="4881335087096496747">"Concordo"</item>
+ <item msgid="2422296858597420738">"Legal"</item>
+ <item msgid="4805581752819452687">"Estou a caminho"</item>
+ <item msgid="4746700499431366214">"OK, falo com você mais tarde"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..c9fdfad
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mensagens"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mensagens"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Selecionar uma conversa"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Configurações"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Enviar mensagem"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Adicionar um anexo"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ajuda"</string>
+ <string name="welcome" msgid="2857560951820802321">"Bem-vindo"</string>
+ <string name="skip" msgid="7238879696319945853">"Pular"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Próxima &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Próximo"</string>
+ <string name="exit" msgid="1905187380359981199">"Sair"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Configurações &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Configurações"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"O app Mensagens precisa de permissão para SMS, telefone e contatos."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"É possível alterar as permissões em \"Config. &gt; Apps &gt; Mensagens &gt; Permissões\"."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"É possível alterar as permissões em \"Config. &gt; Apps &gt; Mensagens &gt; Permissões\"."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frequentes"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Todos os contatos"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Enviar para <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Capturar fotos ou vídeos"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Escolher imagens do dispositivo"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Gravar áudio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Escolher foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"A mídia foi selecionada."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"A mídia foi desmarcada."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> selecionado"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imagem <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" msgid="8142175029053370662">"imagem"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Gravar áudio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Compartilhar"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Há pouco"</string>
+ <string name="posted_now" msgid="867560789350406701">"Agora"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> horas</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dias</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dias</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> semanas</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> semanas</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> meses</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> meses</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> anos</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> anos</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mensagem classe 0"</string>
+ <string name="save" msgid="5081141452059463572">"Salvar"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"O dispositivo está com pouco espaço. As mensagens mais antigas serão excluídas automaticamente para liberar espaço."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Pouco espaço de armazenamento"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"O app Mensagens talvez não envie ou receba mensagens até que mais espaço esteja disponível no seu dispositivo."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Pouco espaço para SMS. Pode ser necessário excluir mensagens."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirme seu número de telefone"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Esta etapa única garantirá que o app Mensagens entregue suas mensagens em grupo corretamente."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Número de telefone"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Excluir todas as mensagens com mídia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Excluir mensagens com mais de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Excluir automaticamente mensagens com mais de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorar"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Excluir todas as mensagens com mídia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Excluir mensagens com mais de <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Excluir mensagens com mais de <xliff:g id="DURATION">%s</xliff:g> e ativar a exclusão automática?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> disse"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Você disse"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Você enviou uma mensagem"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Enviando…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Não enviado. Toque para tentar novamente."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Não enviado. Tentando novamente…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Reenviar ou excluir"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Faça uma chamada de voz para os serviços de emergência. Não foi possível entregar sua mensagem de texto no momento."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Falhou"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nova mensagem MMS para fazer o download"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nova mensagem MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Falha no download"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tocar para tentar novamente"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Tocar para fazer o download"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Fazer o download ou excluir"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Fazendo o download…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"A mensagem expirou ou não está disponível"</string>
+ <string name="mms_info" msgid="3402311750134118165">"tamanho: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, validade: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Falha ao enviar. Destinatário inválido."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Serviço não ativado na rede"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Falha ao enviar devido a um problema de rede"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"A mensagem expirou ou não está disponível"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Sem assunto)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Remetente desconhecido"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Entregue"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Não foi possível fazer o download da mensagem <xliff:g id="SUBJECT">%1$s</xliff:g> de <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Não foi possível concluir a operação do banco de dados devido a memória insuficiente"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mensagem não enviada"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Algumas mensagens não foram enviadas no app Mensagens"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> mensagens em <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensagens em <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Download da mensagem não realizado"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Não foi feito o download de algumas mensagens no app Mensagens"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> mensagens em <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mensagens em <xliff:g id="CONVERSATIONS">%d</xliff:g> conversas</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mensagem para <xliff:g id="NUMBER">%1$s</xliff:g> não enviada"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Faça uma chamada de voz para os serviços de emergência. Não foi possível entregar sua mensagem de texto para <xliff:g id="NUMBER">%1$s</xliff:g> no momento."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> novas mensagens</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> novas mensagens</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Iniciar"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Câmera não disponível"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Câmera não disponível"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Captura de vídeo não disponível"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Falha ao salvar a mídia"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Não é possível tirar uma foto"</string>
+ <string name="back" msgid="1477626055115561645">"Voltar"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arquivadas"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Excluir"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arquivar"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Desarquivar"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Desativar notificações"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Ativar notificações"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Adicionar contato"</string>
+ <string name="action_download" msgid="7786338136368564146">"Fazer o download"</string>
+ <string name="action_send" msgid="377635240181672039">"Enviar"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Excluir"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Excluir esta mensagem?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Esta ação não pode ser desfeita."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Excluir"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Excluir estas conversas?</item>
+ <item quantity="other">Excluir estas conversas?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Excluir"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Cancelar"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Para"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Selecionar várias imagens"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmar seleção"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Falha ao gravar o áudio. Tente novamente."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Falha ao reproduzir o áudio. Tente novamente."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Falha ao salvar o áudio. Tente novamente."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Toque e mantenha pressionado"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imagem"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clipe de áudio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Vídeo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Cartão de visita"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Download"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Responder via SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Responder via MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Responder"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> participantes</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> participantes</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Eu"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Contato bloqueado e arquivado"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Contato desbloqueado e desarquivado"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> arquivada(s)"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> desarquivada(s)"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificações desativadas"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificações ativadas"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Tudo pronto. Toque em \"Enviar\" novamente."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"O app Mensagens foi definido como o app padrão de SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Descartar anexos</item>
+ <item quantity="other">Descartar anexos</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Anexo de áudio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Reproduzir anexo de áudio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausa"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Falha na mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mensagem não enviada para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Enviando mensagem para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Falha na mensagem para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mensagem para <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Falha na mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mensagem de <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mensagem não enviada para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Enviando mensagem para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Falha na mensagem para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mensagem para <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Hora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Falha na mensagem. Toque para tentar novamente."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversa com <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Excluir assunto"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Capturar vídeo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Capturar uma imagem estática"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Tirar foto"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Iniciar gravação de vídeo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Alternar para câmera em tela cheia"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Alternar entre câmera frontal e traseira"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Parar a gravação e anexar vídeo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Parar gravação de vídeo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotos do app Mensagens"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> fotos salvas no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotos salvas no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> vídeos salvos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> vídeos salvos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> anexos salvos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> anexos salvos no álbum \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> anexos salvos em \"Downloads\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> anexos salvos em \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> anexos salvos</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> anexos salvos</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Não foi possível salvar <xliff:g id="QUANTITY_1">%d</xliff:g> anexos</item>
+ <item quantity="other">Não foi possível salvar <xliff:g id="QUANTITY_1">%d</xliff:g> anexos</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Anexo MMS salvo"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Configurações"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arquivadas"</string>
+ <string name="action_close" msgid="1840519376200478419">"Fechar"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avançadas"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Depurar"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificações"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Som"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silencioso"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibração"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloqueado"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Relat. de entrega de SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Solicitar um relatório de entrega para cada SMS enviado"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Recuperação automática"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Recuperar mensagens MMS automaticamente"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Recup. autom. em roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Recuperar MMS automaticamente durante roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mensagens em grupo"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Usar MMS para enviar uma única mensagem para vários destinatários"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"App de SMS padrão"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"App de SMS padrão"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Seu número de telefone"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Desconhecido"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sons de mensagem de saída"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Descartar SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Descartar dados brutos de SMS recebidos no arquivo de armazenamento externo"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Descartar MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Descartar dados brutos de MMS recebidos no arquivo de armazenamento externo"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alertas sem fio"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opções de mensagem"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copiar texto"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ver detalhes"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Excluir"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Avançar"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalhes da mensagem"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tipo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mensagem de texto"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mensagem multimídia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Para: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Enviada: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Recebida: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Assunto: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Tamanho: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioridade: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Alta"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Baixa"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Endereço do remetente oculto"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Não é possível enviar mensagens ao carregar anexos."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Falha ao carregar o anexo. Tente novamente."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"A rede não está pronta. Tente novamente."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Excluir texto"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Alternar entre entrada de texto ou números"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Adicionar mais participantes"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmar participantes"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Iniciar nova conversa"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Selecione este item"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Reproduzir vídeo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Pessoas e opções"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Depurar"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Pessoas e opções"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Gerais"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Pessoas nesta conversa"</string>
+ <string name="action_call" msgid="6596167921517350362">"Fazer uma chamada"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Enviar mensagem"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Enviar mensagem&lt;br/&gt;&lt;small&gt;de <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Enviar fotos</item>
+ <item quantity="other">Enviar fotos</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Enviar áudios</item>
+ <item quantity="other">Enviar áudios</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Enviar vídeos</item>
+ <item quantity="other">Enviar vídeos</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Enviar cards de contato</item>
+ <item quantity="other">Enviar cards de contato</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Enviar anexos</item>
+ <item quantity="other">Enviar anexos</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> anexos prontos para envio</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> anexos prontos para envio</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Enviar feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ver na Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informações da versão"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versão %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licenças de código aberto"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificações"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Limite de anexos alcançado"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Falha ao carregar anexo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Adicionar a Contatos?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Adicionar contato"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Assunto"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Assunto: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Carregando cartão de visita"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Falha ao carregar o cartão de visita"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ver cartão de visita"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> contatos</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> contatos</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Cartões de visita"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Aniversário"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notas"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Encaminhar mensagem"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Responder"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS desativado"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Para enviar, defina o Mensagens como app padrão de SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Definir o Mensagens como o app padrão de SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Alterar"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Para receber mensagens, defina o Mensagens como app padrão de SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nenhum SIM preferido para o envio de mensagens SMS selecionado"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Este app não é permitido pelo proprietário do dispositivo."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Ok"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Muitos participantes em uma conversa"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Contatos inválidos</item>
+ <item quantity="other">Contatos inválidos</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Falha ao carregar a imagem da câmera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Você: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Rascunho"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Quando uma nova conversa for iniciada, será listada aqui"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"As conversas arquivadas aparecem aqui"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Carregando conversas…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imagem"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clipe de áudio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Vídeo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Cartão de visita"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Desfazer"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Repetir"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Digite o nome ou telefone de um contato para iniciar uma nova mensagem"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloquear"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloquear <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Desbloquear <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Bloquear <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Você continuará recebendo mensagens deste número, mas não será notificado. Esta conversa será ativada."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Contatos bloqueados"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DESBLOQUEAR"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Contatos bloqueados"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Escolher imagem da biblioteca de documentos"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Enviando mensagem"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mensagem enviada"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Dados da rede celular desativados. Verifique suas configurações."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Não é possível enviar mensagens no modo avião"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Não foi possível enviar a mensagem"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mensagem baixada"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Dados da rede celular desativados. Verifique suas configurações."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Não é possível fazer o download de mensagens no modo Avião"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Não foi possível fazer o download da mensagem"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Um"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dois"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Três"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Quatro"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinco"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Seis"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sete"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Oito"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nove"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Não é possível enviar a mensagem com <xliff:g id="CARRIERNAME">%1$s</xliff:g>. Erro: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Não é possível enviar a mensagem com operadora desconhecida. Erro: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Enc: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mensagem não enviada: serviço não ativado na rede"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mensagem não enviada: endereço de destino inválido"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mensagem não enviada: mensagem inválida"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mensagem não enviada: conteúdo não compatível"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mensagem não enviada: mensagem não compatível"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mensagem não enviada: muito grande"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nova mensagem"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Visualizar"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imagem"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Não foi possível encontrar um app adequado"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Remover destinatário"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nova mensagem"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Cancelar"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Editar ponto de acesso"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Não definido"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nome"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Porta MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tipo de APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Excluir APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Novo APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Salvar"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Descartar"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"O campo Nome não pode ficar vazio."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"O APN não pode estar vazio."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"O campo MCC deve ter 3 dígitos."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"O campo MNC deve ter 2 ou 3 dígitos."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Restaurando as configurações APN padrão."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Redefinir para o padrão"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Redefinição das configurações padrão do APN concluída."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Sem título"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Nomes dos pontos de acesso"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Novo APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"As configurações do Nome do ponto de acesso não estão disponíveis para este usuário"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copiar para área de transferência?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copiar"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"para <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Gerais"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avançadas"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Configurações gerais"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Configurações avançadas"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Enviar mensagens SMS individuais para todos os destinatários. Só você receberá respostas"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Enviar uma só mensagem MMS para todos os destinatários"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Número desconhecido"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nova mensagem"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nova mensagem."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Seletor de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> selecionado, seletor de SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editar assunto"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Selecione o SIM ou edite o assunto"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Toque e segure para gravar áudio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Iniciar nova conversa"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mensagens"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista de mensagens"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mensagens"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nova mensagem"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista de conversas"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Carregando conversas"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Carregando mensagens"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ver mais conversas"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Ver mais mensagens"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversa excluída"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversa excluída. Toque para mostrar outra conversa do app Mensagens"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Contato bloqueado"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Contato desbloqueado"</string>
+ <string name="db_full" msgid="8459265782521418031">"O espaço de armazenamento está baixo. Alguns dados podem ser perdidos."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Selecionar anexos"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmar seleção"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> selecionados"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Remova um ou mais anexos e tente novamente."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Tente enviar sua mensagem. Se ela não for entregue, remova um ou mais anexos."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Você só pode enviar um vídeo por mensagem. Remova os vídeos adicionais e tente novamente."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Ocorreu uma falha no app Mensagens ao carregar o anexo."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Enviar mesmo assim"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Não foi possível iniciar a conversa"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> selecionado"</string>
+</resources>
diff --git a/res/values-ro/arrays.xml b/res/values-ro/arrays.xml
new file mode 100644
index 0000000..dc5e3fe
--- /dev/null
+++ b/res/values-ro/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"fără subiect"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Da"</item>
+ <item msgid="6049132459802288033">"Nu"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Mulțumesc"</item>
+ <item msgid="4881335087096496747">"Sunt de acord"</item>
+ <item msgid="2422296858597420738">"Frumos"</item>
+ <item msgid="4805581752819452687">"Sunt pe drum"</item>
+ <item msgid="4746700499431366214">"OK, revin mai târziu"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..25b2d8c
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,545 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mesaje"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mesaje"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Selectați conversația"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Setări"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Trimiteți mesajul"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Adăugați un atașament"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ajutor"</string>
+ <string name="welcome" msgid="2857560951820802321">"Bun venit"</string>
+ <string name="skip" msgid="7238879696319945853">"Omiteți"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Înainte &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Înainte"</string>
+ <string name="exit" msgid="1905187380359981199">"Ieșiți"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Setări &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Setări"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mesageria necesită permisiuni la SMS, Telefon și Agendă."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Puteți să schimbați permisiunile din Setări &gt; Aplicații &gt; Mesagerie &gt; Permisiuni."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Puteți să schimbați permisiunile din Setări, Aplicații, Mesagerie, Permisiuni."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Folosite frecvent"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Toată agenda"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Trimiteți la <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Faceți fotografii sau înregistrați videoclipuri"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Alegeți imagini de pe acest dispozitiv"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Înregistrați conținut audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Alegeți o fotografie"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Conținutul media este selectat."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Conținutul media este deselectat."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Ați selectat <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imagine din <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" msgid="8142175029053370662">"imagine"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Înregistrați conținut audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Trimiteți"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Adineauri"</string>
+ <string name="posted_now" msgid="867560789350406701">"Acum"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> minute</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> de minute</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> minut</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> ore</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> de ore</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> oră</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> zile</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> de zile</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> zi</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> săptămâni</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> de săptămâni</item>
+ <item quantity="one">o săptămână</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> luni</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> de luni</item>
+ <item quantity="one">o lună</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> ani</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> de ani</item>
+ <item quantity="one">un an</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mesaje clasa 0"</string>
+ <string name="save" msgid="5081141452059463572">"Salvați"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Spațiul de stocare pe dispozitiv este redus. Mesageria va șterge automat mesaje mai vechi pentru a elibera spațiul."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Spațiu de stocare limitat"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Este posibil ca aplicația Mesagerie să nu trimită și să nu primească mesaje până când nu este disponibil mai mult spațiu pe dispozitiv."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Spațiu de stocare scăzut pentru SMS-uri. Poate fi necesar să ștergeți unele mesaje."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Confirmați numărul de telefon"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Prin acest pas efectuat o singură dată vă asigurați că Mesageria va livra în mod corespunzător mesajele de grup."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Număr de telefon"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Ștergeți toate mesajele multimedia"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Ștergeți mesajele mai vechi de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Ștergeți automat mesajele mai vechi de <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorați"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Ștergeți toate mesajele multimedia?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Ștergeți mesajele mai vechi de <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Ștergeți mesajele mai vechi de <xliff:g id="DURATION">%s</xliff:g> și activați ștergerea automată?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> a spus"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Ați spus"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mesaj de la <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Ați trimis un mesaj"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Se trimite..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nu s-a trimis. Atingeți pentru a încerca din nou."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nu s-a trimis. Se încearcă din nou..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Trimiteți din nou sau ștergeți"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Inițiați un apel vocal către serviciile de urgență. Momentan, mesajul text nu a putut fi livrat."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Eroare"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Mesaj MMS nou de descărcat"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Mesaj MMS nou"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Descărcarea nu a reușit"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Atingeți pentru a reîncerca"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Atingeți pentru a descărca"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Descărcați sau ștergeți"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Se descarcă..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Mesajul a expirat sau nu este disponibil"</string>
+ <string name="mms_info" msgid="3402311750134118165">"dimensiune: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expirare: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nu se poate trimite. Destinatar nevalid."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Serviciul nu este activat în rețea"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Mesajul nu s-a putut trimite din cauza unei probleme de rețea"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Mesajul a expirat sau nu este disponibil."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Fără subiect)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Expeditor necunoscut"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Livrat"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nu s-a putut descărca mesajul <xliff:g id="SUBJECT">%1$s</xliff:g> de la <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Nu s-a putut finaliza operațiunea în baza de date din cauza memoriei reduse"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mesajul nu a fost trimis."</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Unele mesaje nu au fost trimise în Mesagerie"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> (de) mesaje în <xliff:g id="CONVERSATIONS">%d</xliff:g> conversații</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> (de) mesaje în <xliff:g id="CONVERSATIONS">%d</xliff:g> de conversații</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> (de) mesaje într-o conversație</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mesajul nu a fost descărcat"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Unele mesaje nu au fost descărcate în Mesagerie"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> (de) mesaje în <xliff:g id="CONVERSATIONS">%d</xliff:g> conversații</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> (de) mesaje în <xliff:g id="CONVERSATIONS">%d</xliff:g> de conversații</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> (de) mesaje într-o conversație</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mesajul către <xliff:g id="NUMBER">%1$s</xliff:g> nu a fost trimis"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Inițiați un apel vocal către serviciile de urgență. Momentan, mesajul text către <xliff:g id="NUMBER">%1$s</xliff:g> nu a putut fi livrat."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> mesaje noi</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> de mesaje noi</item>
+ <item quantity="one">Mesaj nou</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Începeți"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Camera foto nu este disponibilă"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Camera foto nu este disponibilă"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Filmarea video nu este disponibilă"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Nu se poate salva fișierul multimedia"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nu se poate fotografia"</string>
+ <string name="back" msgid="1477626055115561645">"Înapoi"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arhivate"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Ștergeți"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arhivați"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Dezarhivați"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Dezactivați notificările"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Activați notificările"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Adăugați o persoană"</string>
+ <string name="action_download" msgid="7786338136368564146">"Descărcați"</string>
+ <string name="action_send" msgid="377635240181672039">"Trimiteți"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Ștergeți"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Ștergeți acest mesaj?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Această acțiune nu poate fi anulată."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Ștergeți"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="few">Ștergeți aceste conversații?</item>
+ <item quantity="other">Ștergeți aceste conversații?</item>
+ <item quantity="one">Ștergeți această conversație?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Ștergeți"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Anulați"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Către"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Selectați mai multe imagini"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Confirmați selecția"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Mesajul audio nu poate fi înregistrat. Încercați din nou."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Mesajul audio nu poate fi redat. Încercați din nou."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Mesajul audio nu a putut fi salvat. Încercați din nou."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Apăsați lung"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Imagine"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clip audio"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Videoclip"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Carte de vizită"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Descărcați"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Răspundeți prin SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Răsp. prin MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Răspundeți"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> participanți</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> de participanți</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> participant</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Eu"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Persoana de contact este blocată și arhivată"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Persoană de contact deblocată și dezarhivată"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> arhivate"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> dezarhivate"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Notificări dezactivate"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Notificări activate"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Gata. Atingeți din nou Trimiteți."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Mesageria a fost setată ca aplicație prestabilită pentru SMS-uri."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="few">Eliminați atașamentele</item>
+ <item quantity="other">Eliminați atașamentele</item>
+ <item quantity="one">Eliminați atașamentul</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Atașament audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Redați atașamentul audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pauză"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mesaj de la <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mesaj neprimit de la <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mesaj de la <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mesaj netrimis către <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Se trimite mesajul către <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mesaj netrimis către <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mesaj către <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mesaj neprimit de la <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mesaj de la <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mesaj netrimis către <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Se trimite mesajul către <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mesaj netrimis către <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mesaj către <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mesaj nelivrat. Atingeți pentru a reîncerca."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Conversație cu <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Ștergeți subiectul"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Înregistrați conținut video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Fotografiați o imagine statică"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Fotografiaţi"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Începeți să înregistrați conținut video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Comutați la camera pe ecran complet"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Comutați între camera foto frontală și camera foto posterioară"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Opriți înregistrarea și atașați videoclipul"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Opriți înregistrarea video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotografii din Mesagerie"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografii salvate în albumul „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> de fotografii salvate în albumul „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> fotografie salvată în albumul „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> videoclipuri salvate în albumul „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> de videoclipuri salvate în albumul „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> videoclip salvat în albumul „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> atașamente salvate în albumul „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> de atașamente salvate în albumul „<xliff:g id="ALBUMNAME_3">%s</xliff:g>”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> atașament salvat în albumul „<xliff:g id="ALBUMNAME_1">%s</xliff:g>”</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> atașamente salvate în „Descărcări”</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> de atașamente salvate în „Descărcări”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> atașament salvat în „Descărcări”</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> atașamente au fost salvate</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> de atașamente au fost salvate</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> atașament a fost salvat</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="few">Nu s-au putut salva <xliff:g id="QUANTITY_1">%d</xliff:g> atașamente</item>
+ <item quantity="other">Nu s-au putut salva <xliff:g id="QUANTITY_1">%d</xliff:g> de atașamente</item>
+ <item quantity="one">Nu s-a putut salva <xliff:g id="QUANTITY_0">%d</xliff:g> atașament</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Atașamentul MMS a fost salvat"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Setări"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arhivate"</string>
+ <string name="action_close" msgid="1840519376200478419">"Închideți"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avansate"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Remedierea erorilor"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Notificări"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Sunet"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Silențios"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrații"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blocat"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Rapoarte de trimitere a SMS-urilor"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Solicitați un raport de expediere pentru fiecare mesaj SMS trimis"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Preluare automată"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Preia automat mesajele MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Preluare automată în roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Preia automat mesajele MMS în roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mesagerie de grup"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Utilizați serviciul MMS pentru a trimite un singur mesaj, atunci când există mai mulți destinatari"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplicație SMS prestabilită"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplicație SMS prestabilită"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Numărul dvs. de telefon"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Necunoscut"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sunete pentru mesaje trimise"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Copiați mesajele SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Copiați datele brute din mesajele SMS primite în fișierul de stocare externă"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Copiați mesajele MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Copiați datele brute din mesajele MMS primite în fișierul de stocare externă"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Alerte wireless"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opţiuni mesaj"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Copiați textul"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Vedeți detaliile"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Ștergeți"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Redirecționați"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detalii mesaj"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tip: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mesaj text"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mesaj multimedia"</string>
+ <string name="from_label" msgid="1947831848146564875">"De la: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Către: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Trimis: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Primit: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subiect: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Dimensiune: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritate: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Ridicată"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normală"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Scăzută"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Adresa expeditorului este ascunsă"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Nu se poate trimite mesajul în timp ce se încarcă atașamente."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nu se poate încărca atașamentul. Încercați din nou."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Rețeaua nu este gata. Încercați din nou."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Ștergeți textul"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Comutați între introducere de text și numere"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Adăugați mai mulți participanți"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Confirmați participanții"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Începeți o conversație nouă"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Selectați acest articol"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Redați videoclipul"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Persoane și opțiuni"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Remediați erorile"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Persoane și opțiuni"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Generale"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Persoane participante la conversație"</string>
+ <string name="action_call" msgid="6596167921517350362">"Efectuați un apel"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Trimiteți mesajul"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Trimiteți mesajul&lt;br/&gt;&lt;small&gt;de pe cardul <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="few">Trimiteți fotografiile</item>
+ <item quantity="other">Trimiteți fotografiile</item>
+ <item quantity="one">Trimiteți fotografia</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="few">Trimiteți fișierele audio</item>
+ <item quantity="other">Trimiteți fișierele audio</item>
+ <item quantity="one">Trimiteți fișierul audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="few">Trimiteți videoclipurile</item>
+ <item quantity="other">Trimiteți videoclipurile</item>
+ <item quantity="one">Trimiteți videoclipul</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="few">Trimiteți cărțile de vizită</item>
+ <item quantity="other">Trimiteți cărțile de vizită</item>
+ <item quantity="one">Trimiteți cartea de vizită</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="few">Trimiteți atașamentele</item>
+ <item quantity="other">Trimiteți atașamentele</item>
+ <item quantity="one">Trimiteți atașamentul</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> atașamente gata de trimis</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> de atașamente gata de trimis</item>
+ <item quantity="one">Un atașament gata de trimis</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Trimiteți feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Vedeți în Magazinul Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informații despre versiune"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versiunea %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licențe open source"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Notificări"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"S-a atins limita pentru atașamente"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Atașamentul nu a putut fi încărcat."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Adăugați în Agendă?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Adăugați o persoană"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subiect"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subiect: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Se încarcă cartea de vizită"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Cartea de vizită nu a putut fi încărcată"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Vedeți cartea de vizită"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> persoane de contact</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> de persoane de contact</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> persoană de contact</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Cărți de vizită"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Ziua de naștere"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Note"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Redirecționați mesajul"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Răspundeți"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS-urile sunt dezactivate"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Pentru a trimite mesaje, setați Mesageria ca aplicație prestabilită pentru SMS-uri"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Setați Mesageria ca aplicație prestabilită pentru SMS-uri"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Modificați"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Pentru a primi mesaje, setați Mesageria ca aplicație prestabilită pentru SMS-uri"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Pentru trimiterea mesajelor SMS nu este selectat niciun card SIM preferat"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Această aplicație nu este acceptată de proprietarul dispozitivului."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Prea mulți participanți la o conversație"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="few">Persoane de contact nevalide</item>
+ <item quantity="other">Persoane de contact nevalide</item>
+ <item quantity="one">Persoană de contact nevalidă</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Nu s-a putut încărca imaginea de la camera foto"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Dvs.: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Mesaj nefinalizat"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Când începeți o nouă conversație, o veți vedea aici"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Conversațiile arhivate apar aici"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Conversațiile sunt în curs de încărcare..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Imagine"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clip audio"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Videoclip"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Carte de vizită"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Anulați"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Reîncercați"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Introduceți numele unei persoane de contact sau un număr de telefon pentru a începe un nou mesaj"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blocați"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blocați <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Deblocați-l(o) pe <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blocați <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Veți primi, în continuare, mesaje de la acest număr, dar nu veți mai primi notificări. Această conversație va fi arhivată."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Persoane din Agendă blocate"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"DEBLOCAȚI"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Persoane din Agendă blocate"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Alegeți imaginea din biblioteca de documente"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Se trimite mesajul"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mesaj trimis"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Datele mobile sunt dezactivate. Verificați setările."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Nu se pot trimite mesaje în modul Avion"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Mesajul nu a putut fi trimis"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mesaj descărcat"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Datele mobile sunt dezactivate. Verificați setările."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Nu se pot descărca mesaje în modul Avion"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Mesajul nu a putut fi descărcat"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Unu"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Doi"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Trei"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Patru"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Cinci"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Șase"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Șapte"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Opt"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nouă"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Nu se poate trimite mesaj prin <xliff:g id="CARRIERNAME">%1$s</xliff:g>, eroare <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Nu se poate trimite mesaj printr-un operator necunoscut, eroare <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Redir.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mesajul nu a fost trimis: serviciul nu este activat în rețea"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mesajul nu a fost trimis: adresă de destinație nevalidă"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mesajul nu a fost trimis: mesaj nevalid"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mesajul nu a fost trimis: conținut neacceptat"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mesajul nu a fost trimis: mesaj neacceptat"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mesajul nu a fost trimis: mesaj prea mare"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mesaj nou"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Afișați"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imagine"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nu am găsit o aplicație adecvată"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Eliminați un destinatar"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mesaj nou"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Anulați"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Modificaţi punctul de acces"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nesetat"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nume"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Tip APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Ștergeți APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN nou"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Salvați"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Renunțați"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Câmpul Nume trebuie completat."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN-ul trebuie completat."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Câmpul MCC trebuie să conțină 3 cifre."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Câmpul MNC trebuie să conțină 2 sau 3 cifre."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Se restabilesc setările APN prestabilite."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Resetați la valorile prestabilite"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Resetarea setărilor APN prestabilite a fost finalizată."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Fără titlu"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Numele punctelor de acces"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-uri"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN nou"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Setările pentru „Nume puncte de acces” nu sunt disponibile pentru acest utilizator"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Copiați în clipboard?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Copiați"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"pe cardul <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Generale"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avansate"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Setări generale"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Setări avansate"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Cartela SIM „<xliff:g id="SIM_NAME">%s</xliff:g>”"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Trimiteți mesaje SMS individuale către toți destinatarii. Numai dvs. veți primi răspunsurile."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Trimiteți un singur mesaj MMS către toți destinatarii"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Număr necunoscut"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mesaj nou"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mesaj nou."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Selector de SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> a fost selectat, selector de SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Editați subiectul"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Selectați cardul SIM sau editați subiectul"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Atingeți lung pentru a înregistra conținutul audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Începeți o conversație nouă"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mesagerie"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Listă Mesagerie"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mesagerie"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mesaj nou"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista de conversații"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Se încarcă conversațiile"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Se încarcă mesajele"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Vedeți mai multe conversații"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Vedeți mai multe mesaje"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Conversație ștearsă"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Conversația a fost ștearsă. Atingeți pentru a afișa altă conversație din Mesagerie"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blocat"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Deblocat"</string>
+ <string name="db_full" msgid="8459265782521418031">"Spațiul de stocare este scăzut. Se pot pierde unele date."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Selectați atașamentele"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Confirmați selecția"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Ați selectat <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Eliminați unul sau mai multe atașamente și încercați din nou."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Puteți încerca să trimiteți mesajul, însă dacă nu eliminați unul sau mai multe atașamente, este posibil ca acesta să nu fie livrat."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Puteți trimite doar un videoclip per mesaj. Eliminați videoclipurile suplimentare și încercați din nou."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Mesageria nu a putut încărca atașamentul."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Trimiteți oricum"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Conversația nu a putut fi începută"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> selectat"</string>
+</resources>
diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml
new file mode 100644
index 0000000..8af4db2
--- /dev/null
+++ b/res/values-ru/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"без темы"</item>
+ <item msgid="272485471009191934">"без темы"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Да"</item>
+ <item msgid="6049132459802288033">"Нет"</item>
+ <item msgid="3084376867445867895">"ОК"</item>
+ <item msgid="3155097332660174689">"Ха-ха"</item>
+ <item msgid="2611328818571146775">"Спасибо!"</item>
+ <item msgid="4881335087096496747">"Хорошо"</item>
+ <item msgid="2422296858597420738">"Отлично"</item>
+ <item msgid="4805581752819452687">"Скоро буду"</item>
+ <item msgid="4746700499431366214">"ОК, отвечу позже"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..0683b2b
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"SMS/MMS"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"SMS/MMS"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Выберите чат"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Настройки"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Отправить сообщение"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Прикрепить файл"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Справка"</string>
+ <string name="welcome" msgid="2857560951820802321">"Добро пожаловать!"</string>
+ <string name="skip" msgid="7238879696319945853">"Пропустить"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Далее &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Далее"</string>
+ <string name="exit" msgid="1905187380359981199">"Закрыть"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Настройки &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Настройки"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Приложению SMS/MMS требуются разрешения категорий SMS, \"Телефон\" и \"Контакты\"."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Чтобы изменить разрешения, нажмите \"Настройки &gt; Приложения &gt; SMS/MMS &gt; Разрешения\"."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Чтобы изменить разрешения, нажмите \"Настройки &gt; Приложения &gt; SMS/MMS &gt; Разрешения\"."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Частые"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Все контакты"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Отправить на номер <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Снять фото или видео"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Выбрать изображения на устройстве"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Записать аудио"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Выбрать фотографию"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Медиафайл выбран"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Выбор отменен"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Выбрано: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"изображение <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" msgid="8142175029053370662">"изображение"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Запись аудио"</string>
+ <string name="action_share" msgid="2143483844803153871">"Поделиться"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Только что"</string>
+ <string name="posted_now" msgid="867560789350406701">"Только что"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> минута</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> минуты</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> минут</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> минуты</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> час</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> часа</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> часов</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> часа</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> день</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> дня</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> дней</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> дня</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> неделя</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> недели</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> недель</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> недели</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> месяц</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> месяца</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> месяцев</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> месяца</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> год</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> года</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> лет</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> года</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Сообщение класса 0"</string>
+ <string name="save" msgid="5081141452059463572">"Сохранить"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Свободное место в памяти устройства заканчивается. Чтобы освободить его, приложение удалит старые сообщения."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Недостаточно памяти"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Недостаточно свободного места, чтобы отправлять и получать сообщения в SMS/MMS."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Для хранения SMS осталось мало места. Удалите старые сообщения."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Подтверждение номера телефона"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Номер требуется для отправки групповых сообщений в SMS/MMS. Его нужно ввести только один раз."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Номер телефона"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Удалить сообщения с медиафайлами"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Удалить сообщения старше <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Автоматически удалять сообщения старше <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Игнорировать"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Удалить сообщения с медиафайлами?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Удалить сообщения старше <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Удалить сообщения старше <xliff:g id="DURATION">%s</xliff:g> и включить автоматическое удаление?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> пишет:"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Вы сказали:"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Сообщение от пользователя <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Вы отправили сообщение"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Отправка…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Сообщение не отправлено. Нажмите, чтобы повторить попытку."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Сообщение не отправлено. Повторная попытка…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Переслать или удалить"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Не удалось доставить сообщение. Чтобы связаться с экстренными службами, позвоните по этому номеру."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Не удалось отправить"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Скачайте новое MMS-сообщение"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Новое сообщение MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Не удалось скачать"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Нажмите, чтобы повторить попытку"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Нажмите, чтобы скачать"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Скачать или удалить"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Скачивание…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Срок действия сообщения истек, или оно недоступно"</string>
+ <string name="mms_info" msgid="3402311750134118165">"размер: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, срок действия: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Не удалось отправить сообщение: укажите действительного получателя."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Услуга не активирована в сети"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Не удалось отправить сообщение из-за ошибки сети"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Срок действия сообщения истек, или оно недоступно"</string>
+ <string name="no_subject" msgid="5587715902648568767">"Без темы"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Неизвестный отправитель"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Доставлено"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Не удалось загрузить сообщение \"<xliff:g id="SUBJECT">%1$s</xliff:g>\" от пользователя <xliff:g id="FROM">%2$s</xliff:g>"</string>
+ <string name="low_memory" msgid="5300743415198486619">"Не удалось выполнить операцию с базой данных: недостаточно памяти"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Сообщение не отправлено"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Некоторые сообщения в SMS/MMS не отправлены"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="few">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="many">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="other">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Не удалось загрузить сообщение"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Некоторые сообщения в SMS/MMS не скачаны"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="few">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="many">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="other">Сообщений: <xliff:g id="MESSAGES_1">%d</xliff:g>, чатов: <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"SMS на номер <xliff:g id="NUMBER">%1$s</xliff:g> не отправлено"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Не удалось доставить сообщение на номер <xliff:g id="NUMBER">%1$s</xliff:g>. Позвоните в экстренную службу по телефону."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> новое сообщение</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> новых сообщения</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> новых сообщений</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> нового сообщения</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Начать беседу"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Камера недоступна"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Камера недоступна"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Видеозапись недоступна"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Не удалось сохранить"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Не удалось сфотографировать"</string>
+ <string name="back" msgid="1477626055115561645">"Назад"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архив"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Удалить"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архив"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Вернуть из архива"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Отключить оповещения"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Включить оповещения"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Добавить контакт"</string>
+ <string name="action_download" msgid="7786338136368564146">"Скачать"</string>
+ <string name="action_send" msgid="377635240181672039">"Отправить"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Удалить"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Удалить сообщение?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Это действие нельзя будет отменить."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Удалить"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Удалить эти чаты?</item>
+ <item quantity="few">Удалить эти чаты?</item>
+ <item quantity="many">Удалить эти чаты?</item>
+ <item quantity="other">Удалить эти чаты?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Удалить"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Отмена"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Кому"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Выбрать несколько изображений"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Подтверждение выбора"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Не удалось записать. Повторите попытку."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Не удалось воспроизвести. Повторите попытку."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Не удалось сохранить. Повторите попытку."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Нажмите и удерживайте"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Изображение"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудиоклип"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Видео"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Карточка контакта"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Скачать"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Отправить SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Ответить MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Ответить"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> участник</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> участника</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> участников</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> участника</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Вы"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Контакт заблокирован, переписка помещена в архив"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Контакт разблокирован, чат возвращен из архива"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Добавлено в архив: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Возвращено из архива: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Уведомления отключены"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Уведомления включены"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Все готово. Нажмите \"Отправить\" ещё раз."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Вы назначили SMS/MMS приложением для SMS по умолчанию"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Убрать прикрепленные файлы</item>
+ <item quantity="few">Убрать прикрепленные файлы</item>
+ <item quantity="many">Убрать прикрепленные файлы</item>
+ <item quantity="other">Убрать прикрепленные файлы</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Аудиофайл"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Воспроизвести прикрепленный аудиофайл"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Пауза"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Сообщение от пользователя <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Не удалось отправить сообщение от пользователя <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Сообщение от пользователя <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Неотправленное сообщение для пользователя <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Отправляется сообщение для пользователя <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Не удалось отправить сообщение для пользователя <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Сообщение для пользователя <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Не удалось отправить сообщение от пользователя <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Сообщение от пользователя <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Неотправленное сообщение для группы <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Отправляется сообщение для группы <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Не удалось отправить сообщение для группы <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Сообщение для группы <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Время: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Не удалось отправить сообщение. Нажмите, чтобы повторить попытку."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Чат с пользователями <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Удалить тему."</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Снять видео"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Сделать стоп-кадр"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Сделать снимок"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Начать запись видео"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Перейти в полноэкранный режим"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Переключение между основной и фронтальной камерами"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Остановить запись и прикрепить видео"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Остановить запись видео"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Фото из SMS/MMS"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> фото сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> фото сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> фото сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> фото сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> видео сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> видео сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> видео сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> видео сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> прикрепленный файл сохранен в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> прикрепленных файла сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> прикрепленных файлов сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> прикрепленных файла сохранено в альбоме \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленный файл сохранен в Загрузках</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файла сохранено в Загрузках</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файлов сохранено в Загрузках</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файла сохранено в Загрузках</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленный файл сохранен</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файла сохранено</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файлов сохранено</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленного файла сохранено</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Не удалось сохранить <xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленный файл</item>
+ <item quantity="few">Не удалось сохранить <xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файла</item>
+ <item quantity="many">Не удалось сохранить <xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленных файлов</item>
+ <item quantity="other">Не удалось сохранить <xliff:g id="QUANTITY_1">%d</xliff:g> прикрепленного файла</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Сохраненный файл из MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Настройки"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архив"</string>
+ <string name="action_close" msgid="1840519376200478419">"Закрыть"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Дополнительные настройки"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Выполнить отладку"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Уведомления"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Звуковой сигнал"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Без звука"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Вибросигнал"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Заблокировано"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Отчеты о доставке SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Запрашивать отчет о доставке для всех отправляемых SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Автополучение"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Автоматически загружать MMS-сообщения"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Автополучение в роуминге"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Автоматически загружать MMS-сообщения в роуминге"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Групповые сообщения"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Использовать MMS для отправки одного сообщения нескольким получателям"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Приложение для обмена SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Приложение для обмена SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Ваш номер телефона"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Неизвестный"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Сигнал исходящего сообщения"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Передача SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Передать необработанные данные полученных SMS во внешнее хранилище"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Передача MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Передать необработанные данные полученных MMS во внешнее хранилище"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Оповещение населения"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Параметры сообщения"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Копировать текст"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Сведения"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Удалить"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Переслать"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Сведения о сообщении"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Тип: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"MMS"</string>
+ <string name="from_label" msgid="1947831848146564875">"От: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Кому: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Отправлено: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Получено: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Тема: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Размер: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Приоритет: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-карта: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Высокий"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Обычный"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Низкий"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM-карта <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Адрес отправителя скрыт"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Не удается отправить сообщение. Дождитесь завершения загрузки прикрепленных файлов."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Не удалось загрузить прикрепленный файл. Повторите попытку."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Сеть не готова. Повторите попытку."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Удалить текст"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Переключение между вводом букв и цифр"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Добавить участников"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Подтвердить участников"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Начать чат"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Выбрать этот вариант"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Воспроизвести видео"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Участники и параметры"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Выполнить отладку"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Участники и параметры"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Общие"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Участники чата"</string>
+ <string name="action_call" msgid="6596167921517350362">"Позвонить"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Отправить сообщение"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Отправить сообщение&lt;br/&gt;&lt;small&gt;с <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Отправить фото</item>
+ <item quantity="few">Отправить фото</item>
+ <item quantity="many">Отправить фото</item>
+ <item quantity="other">Отправить фото</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Отправить аудио</item>
+ <item quantity="few">Отправить аудио</item>
+ <item quantity="many">Отправить аудио</item>
+ <item quantity="other">Отправить аудио</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Отправить видео</item>
+ <item quantity="few">Отправить видео</item>
+ <item quantity="many">Отправить видео</item>
+ <item quantity="other">Отправить видео</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Отправить карточки контактов</item>
+ <item quantity="few">Отправить карточки контактов</item>
+ <item quantity="many">Отправить карточки контактов</item>
+ <item quantity="other">Отправить карточки контактов</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Отправить прикрепленные файлы</item>
+ <item quantity="few">Отправить прикрепленные файлы</item>
+ <item quantity="many">Отправить прикрепленные файлы</item>
+ <item quantity="other">Отправить прикрепленные файлы</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прикрепленный файл готов к отправке</item>
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прикрепленных файла готовы к отправке</item>
+ <item quantity="many"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прикрепленных файлов готовы к отправке</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прикрепленного файла готовы к отправке</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Отправить отзыв"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Искать в Play Маркете"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Версия"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Версия %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Лицензии открытого ПО"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Уведомления"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Прикреплено максимальное количество файлов"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Не удалось загрузить прикрепленный файл"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Добавить в контакты?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Добавить"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Тема"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Тема: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Загрузка карточки контакта…"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Не удалось загрузить карточку контакта"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Посмотреть карточку контакта"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> контакт</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> контакта</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> контактов</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> контакта</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Карточки контактов"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"День рождения"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Примечания"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Пересылка сообщения"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Ответ"</string>
+ <string name="plus_one" msgid="9010288417554932581">"и ещё 1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"и ещё %d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS-служба отключена"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Чтобы отправлять сообщения, назначьте SMS/MMS приложением для SMS по умолчанию"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Назначьте SMS/MMS приложением для SMS по умолчанию"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Изменить"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Чтобы получать сообщения, сделайте SMS/MMS приложением для SMS по умолчанию"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Не выбрана SIM-карта для отправки SMS-сообщений"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Владелец устройства запретил использовать это приложение."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ОК"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Слишком много участников"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Контакты не найдены</item>
+ <item quantity="few">Контакты не найдены</item>
+ <item quantity="many">Контакты не найдены</item>
+ <item quantity="other">Контакты не найдены</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Не удалось загрузить изображение с камеры"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Вы: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Черновик"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Когда вы начнете переписку, она появится здесь"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Здесь появятся архивированные чаты"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Загрузка цепочек писем…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Изображение"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудиоклип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Видео"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Карточка контакта"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Отменить"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Повторить"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Введите имя пользователя или номер телефона"</string>
+ <string name="action_block" msgid="9032076625645190136">"Заблокировать"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Заблокировать пользователя <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Разблокировать пользователя <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Заблокировать <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Вы по-прежнему будете получать сообщения с этого номера, но не увидите оповещений о них. Этот чат будет архивирован."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Заблокированные контакты"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"РАЗБЛОКИРОВАТЬ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Заблокированные контакты"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Выбрать изображение в библиотеке документов"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Отправка сообщения"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Сообщение отправлено"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Передача данных по мобильной сети отключена в настройках устройства"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"В режиме полета нельзя отправлять сообщения"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Не удалось отправить сообщение"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Сообщение скачано"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Передача данных по мобильной сети отключена в настройках устройства"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Невозможно скачать сообщения в режиме полета"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Невозможно скачать сообщение"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Ноль"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Один"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Два"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Три"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Четыре"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Пять"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Шесть"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Семь"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Восемь"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Девять"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Не удалось отправить сообщение с помощью оператора \"<xliff:g id="CARRIERNAME">%1$s</xliff:g>\" (ошибка <xliff:g id="ERRORCODE">%2$d</xliff:g>)"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Не удалось отправить сообщение с помощью неизвестного оператора (ошибка <xliff:g id="ERRORCODE">%1$d</xliff:g>)"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Сообщение не отправлено: услуга не активирована в сети"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Сообщение не отправлено: недопустимый адрес получателя"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Ошибка: недопустимое сообщение"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Сообщение не отправлено, поскольку содержит неподдерживаемый контент"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Ошибка: неподдерживаемое сообщение"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Не удалось отправить сообщение из-за его размера"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Новое сообщение"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Посмотреть"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Изображение"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Подходящее приложение не найдено"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Удалить получателя."</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Новое сообщение"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Отмена"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Настройки точки доступа"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Не настроено"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Название"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Точка доступа"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Прокси-сервер MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Порт MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Тип точки доступа"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Удалить точку доступа"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Создать точку доступа"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Сохранить"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Отменить"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Укажите название."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Укажите точку доступа."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Поле MCC должно содержать 3 цифры."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Поле MNC должно содержать 2 или 3 цифры."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Восстановление настроек по умолчанию…"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Восстановить настройки по умолчанию"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Настройки по умолчанию восстановлены"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Без названия"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Точки доступа"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Точки доступа"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Создать точку доступа"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Этот пользователь не может изменять настройки точки доступа"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Скопировать в буфер обмена?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Да"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"На <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Общие"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Дополнительные настройки"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Общие настройки"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Расширенные настройки"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM-карта \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Отправлять всем получателям отдельные SMS-сообщения. Ответы на них будут отправлены только вам."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Отправлять всем получателям одно MMS-сообщение"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Неизвестный номер"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Новое сообщение"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Новое сообщение"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Выбрать SIM-карту"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Выбрана SIM-карта <xliff:g id="SIM_0">%1$s</xliff:g>"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Изменить тему"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Выбрать SIM-карту или изменить тему"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Нажмите и удерживайте, чтобы записать аудио"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Начать чат"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS/MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Чаты из SMS/MMS"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS/MMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Новое сообщение"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Список чатов"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Загрузка чатов"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Загрузка сообщений…"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Другие чаты"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Другие сообщения"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Чат удален"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Чат удален. Нажмите здесь, чтобы перейти к другому."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Заблокирован"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Разблокирован"</string>
+ <string name="db_full" msgid="8459265782521418031">"Заканчивается место в памяти устройства. Некоторые данные могут быть потеряны."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Выбор прикрепленных файлов"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Подтверждение выбора"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Выбрано: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Удалите один или несколько прикрепленных файлов и повторите попытку."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Если вы не удалите один или несколько прикрепленных файлов, сообщение может не дойти до адресата."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Вы можете отправлять по одному видео за раз. Удалите лишние ролики и повторите попытку."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Не удалось загрузить прикрепленные файлы."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Отправить"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Не удается начать разговор"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"SIM-карта выбрана: <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-si-rLK/arrays.xml b/res/values-si-rLK/arrays.xml
new file mode 100644
index 0000000..89f6c6c
--- /dev/null
+++ b/res/values-si-rLK/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"කාරණාවක් නොමැත"</item>
+ <item msgid="272485471009191934">"කාරණාවක් නොමැත"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ඔව්"</item>
+ <item msgid="6049132459802288033">"නැත"</item>
+ <item msgid="3084376867445867895">"හරි"</item>
+ <item msgid="3155097332660174689">"හා හා"</item>
+ <item msgid="2611328818571146775">"ස්තූතියි"</item>
+ <item msgid="4881335087096496747">"මම එකඟයි"</item>
+ <item msgid="2422296858597420738">"නයිස්"</item>
+ <item msgid="4805581752819452687">"මම එන ගමන්"</item>
+ <item msgid="4746700499431366214">"හරි, මම ඔබ වෙත පසුව පැමිණෙන්නම්"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..11a5943
--- /dev/null
+++ b/res/values-si-rLK/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"පණිවිඩ යැවීම"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"පණිවිඩ යැවීම"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"සංවාදය තෝරන්න"</string>
+ <string name="action_settings" msgid="1329008122345201684">"සැකසීම්"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"පණිවිඩය යවන්න"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ඇමිණුමක් එක් කරන්න"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"උදවු"</string>
+ <string name="welcome" msgid="2857560951820802321">"සාදරයෙන් පිළිගනිමු"</string>
+ <string name="skip" msgid="7238879696319945853">"මඟ හරින්න"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"ඊළඟ &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"මීළඟ"</string>
+ <string name="exit" msgid="1905187380359981199">"පිටවන්න"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"සැකසීම් &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"සැකසීම්"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"පණිවිඩ යැවීමට SMS, Phone සහ සම්බන්ධතා වෙත අවසර අවශ්‍යයි."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"ඔබට සැකසීම් &gt; යෙදුම් &gt; පණිවිඩ යැවීම &gt; අවසරවල අවසර වෙනස් කළ හැකිය."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"ඔබට සැකසීම්, යෙදුම්, පණිවිඩ යැවීම, අවසරවල අවසර වෙනස් කළ හැකිය."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"නිතරම"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"සියලුම සම්බන්ධතා"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> වෙත යවන්න"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"පින්තූර හෝ වීඩියෝ ලබාගන්න"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"මෙම උපාංගයෙන් පින්තූර තෝරන්න"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ශ්‍රව්‍ය තැටිගත කරන්න"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ඡායාරූපය තෝරන්න"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"මාධ්‍යය තෝරාගෙන තිබේ."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"මාධ්‍යය තෝරාගෙන නොතිබේ."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ක් තෝරාගන්නා ලදි"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"රූපය <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" msgid="8142175029053370662">"රූපය"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ශ්‍රව්‍ය තැටිගත කරන්න"</string>
+ <string name="action_share" msgid="2143483844803153871">"බෙදාගන්න"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"මේ දැන්"</string>
+ <string name="posted_now" msgid="867560789350406701">"දැන්"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one">මිනිත්තු <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="other">මිනිත්තු <xliff:g id="COUNT_1">%d</xliff:g></item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one">පැය <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="other">පැය <xliff:g id="COUNT_1">%d</xliff:g></item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one">දින <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="other">දින <xliff:g id="COUNT_1">%d</xliff:g></item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one">සති <xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="other">සති <xliff:g id="COUNT">%d</xliff:g></item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one">මාස <xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="other">මාස <xliff:g id="COUNT">%d</xliff:g></item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one">අවුරුදු <xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="other">අවුරුදු <xliff:g id="COUNT">%d</xliff:g></item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0 පන්තියේ පණිවිඩය"</string>
+ <string name="save" msgid="5081141452059463572">"සුරකින්න"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"උපාංගයේ ඉඩ අඩුය. පණිවිඩකරණය ඉඩ හිස් කර ගැනීමට පැරණි පණිවිඩ ස්වයංක්‍රියව මකනු ඇත."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"ආචයනය ඉඩ ප්‍රමාණය අඩු වී ඇත"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"පණිවිඩ යැවීම ඔබේ උපාංගය මත වැඩි ඉඩක් ලබාගත හැකිවන තෙක් පණිවිඩ නොයැවිය හැකිය නැතහොත් ලබා නොගත හැකිය."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"අඩු SMS ආචයනය. ඇතැම් විට ඔබට පණිවිඩ මැකීමට සිදුවේ."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"ඔබගේ දුරකථන අංකය තහවුරු කරන්න"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"පණිවිඩ යැවීම ඔබේ සමූහ පණිවිඩ නිසි ලෙස බෙදා හරිනු ඇති බවට මෙම එක්-වරක් පියවර තහවුරු කරනු අැත."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"දුරකථන අංකය"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"මාධ්‍ය සමඟ ඇති සියලුම පණිවිඩ මකන්න"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> වඩා පැරණි පණිවිඩ මකන්න"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> වඩා පැරණි පණිවිඩ ස්වයංක්‍රීයව මකන්න"</string>
+ <string name="ignore" msgid="7063392681130898793">"නොසලකා හරින්න"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"මාධ්‍ය සමඟ ඇති සියලුම පණිවිඩ මකන්නද?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> වඩා පැරණි පණිවිඩ මකන්නද?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> වඩා පැරණි පණිවිඩ ස්වයංක්‍රීයව මැකීම සක්‍රිය කරන්නද?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> පැවසුවා"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"ඔබ පැවසුවා"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> වෙතින් පණිවිඩය:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"ඔබ පණිවිඩයක් යැවුවා"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"යවමින්..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"යැව්වේ නැත. නැවත උත්සාහ කිරීමට ස්පර්ශ කරන්න."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"යැව්වේ නැත. නැවත උත්සාහ කරමින්…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"නැවත යවන්න නැතහොත් මකන්න"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"කරුණාකර හදිසි සේවාවන් සඳහා හඬ ඇමතුමක් ගන්න. මේ මොහොතේ ඔබගේ පෙළ පණිවිඩය යැවීමට නොහැක."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"අසාර්ථක වුණි"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"බාගැනීමට අලුත් MMS පණිවුඩයකි"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"අලුත් MMS පණිවුඩය"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"බාගැනීමට නොහැකි විය"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"නැවත උත්සාහයට ස්පර්ශ කරන්න"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"බාගැනීමට ස්පර්ශ කරන්න"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"බාගතකරන්න නැතහොත් මකන්න"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"බාගනිමින්…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"පණිවිඩය කල් ඉකුත්වී ඇත හෝ දැනට නොමැත."</string>
+ <string name="mms_info" msgid="3402311750134118165">"ප්‍රමාණය: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, කල් ඉකුත් වීම: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"යැවිය නොහැක. ලබන්නා වලංගු නැත."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"ජාලය තුළ සේවාව සක්‍රිය කර නොමැත"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ජාලයේ ගැටළුවක් හේතුවෙන් යැවීම කළ නොහැක"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"පණිවිඩය කල් ඉකුත්වී ඇත හෝ දැනට නොමැත."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(කාරණාවක් නොමැත)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"නොහඳුනන යවන්නෙක්"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"භාර දුණි"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="SUBJECT">%1$s</xliff:g> පණිවිඩය <xliff:g id="FROM">%2$s</xliff:g> වෙතින් බාගත නොහැකි විය."</string>
+ <string name="low_memory" msgid="5300743415198486619">"අඩු මතකය නිසා දත්ත සමුදාය මත මෙහෙයුම සම්පූර්ණ කළ නොහැකි විය"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"පණිවිඩය යැවුවේ නැත"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"සමහර පණිවිඩ පණිවිඩ යැවීමේදී නොයවන ලදී"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one">සංවාද <xliff:g id="MESSAGES_1">%d</xliff:g> ක් තුළ පණිවිඩ <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="other">සංවාද <xliff:g id="MESSAGES_1">%d</xliff:g> ක් තුළ පණිවිඩ <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"පණිවිඩය බානොගන්නා ලදී"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"සමහර පණිවිඩ පණිවිඩ යැවීමේදී බා නොගන්නා ලදී"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one">සංවාද <xliff:g id="MESSAGES_1">%d</xliff:g> ක් තුළ පණිවිඩ <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="other">සංවාද <xliff:g id="MESSAGES_1">%d</xliff:g> ක් තුළ පණිවිඩ <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> වෙත පණිවිඩය ගියේ නැත"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"කරුණාකර හදිසි සේවාවන් සඳහා හඬ ඇමතුමක් ගන්න. මේ මොහොතේ <xliff:g id="NUMBER">%1$s</xliff:g> වෙත ඔබගේ පෙළ පණිවිඩය යැවීමට නොහැක."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one">නව පණිවිඩ <xliff:g id="MESSAGES">%d</xliff:g></item>
+ <item quantity="other">නව පණිවිඩ <xliff:g id="MESSAGES">%d</xliff:g></item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ආරම්භ කරන්න"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"කැමරාව නොපවතී"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"කැමරාව නොපවතී"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"වීඩියෝව ගැනීම නොතිබේ"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"මාධ්‍ය සුරැකීම කළ නොහැක"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"පින්තූරය ලබාගත නොහැක"</string>
+ <string name="back" msgid="1477626055115561645">"ආපසු"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"සංරක්ෂිත"</string>
+ <string name="action_delete" msgid="4076795795307486019">"මකන්න"</string>
+ <string name="action_archive" msgid="5437034800324083170">"සංරක්ෂිත කරන්න"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"සංරක්ෂණය නොකළ"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"දැනුම්දීම් අක්‍රිය කරන්න"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"දැනුම්දීම් සක්‍රිය කරන්න"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"සම්බන්ධතාවය එක් කරන්න"</string>
+ <string name="action_download" msgid="7786338136368564146">"බාගන්න"</string>
+ <string name="action_send" msgid="377635240181672039">"යවන්න"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"මකන්න"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"මෙම පණිවිඩය මකන්න?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"මෙම ක්‍රියාව අවසන් කිරීම කළ නොහැක."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"මකන්න"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">මෙම සංවාද මකන්නද?</item>
+ <item quantity="other">මෙම සංවාද මකන්නද?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"මකන්න"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"අවලංගු කරන්න"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"වෙත"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"බහු පින්තූර තෝරන්න"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"තේරීම සහතික කරන්න"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ශ්‍රව්‍ය පටිගත කළ නොහැක. නැවත උත්සාහ කරන්න."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ශ්‍රව්‍ය ධාවනය කළ නොහැක. නැවත උත්සාහ කරන්න."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ශ්‍රව්‍ය සුරැකීම කළ නොහැක. නැවත උත්සාහ කරන්න."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ස්පර්ශ කරන්න සහ අල්ලා සිටින්න"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"පින්තූරය"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ශ්‍රව්‍ය පසුර"</string>
+ <string name="notification_video" msgid="4331423498662606204">"වීඩියෝව"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"සම්බන්ධතා කාඩ් පත"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"බාගන්න"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS ඔස්සේ ප්‍රතිචාර දක්වන්න"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS හරහා පිළිතුරු දෙන්න"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"පිළිතුරු දෙන්න"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one">සහභාගිවන්නන් <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="other">සහභාගිවන්නන් <xliff:g id="COUNT_1">%d</xliff:g></item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"මම"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"සම්බන්ධතා අවහිර කරන ලද සහ සංරක්ෂණය කරන ලද"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"සම්බන්ධතාව අවහිර කිරීම ඉවත් කර සංරක්ෂණය නොකරන ලදී"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ක් සංරක්ෂණය කරන ලදි"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ක් සංරක්ෂණය නොකරන ලදි"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"දැනුම්දීම් ක්‍රියාවිරහිත කරන ලදී"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"දැනුම්දීම් ක්‍රියාත්මක කරන ලදී"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"සියල්ල සකසා ඇත. නැවත යැවීමට ස්පර්ශ කරන්න."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"පණිවිඩ යැවීම සාර්ථකව පෙරනිමි SMS යෙදුම ලෙස සකසන ලදී."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">ඇමුණුම් ඉවත ලන්න</item>
+ <item quantity="other">ඇමුණුම් ඉවත ලන්න</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ශ්‍රව්‍ය ඇමුණුම"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ශ්‍රව්‍ය ඇමුණුම ධාවනය කරන්න"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"විරාමය"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> වෙතින් පණිවිඩය: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> වෙතින් අසාර්ථක පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> වෙතින් පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g> වෙත නොයැවූ පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g> වෙත පණිවිඩයක් යවමින්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g> වෙත අසාර්ථක පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g> වෙත පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> වෙතින් අසාර්ථක පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> වෙතින් පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> වෙත නොයැවූ පණිවිඩය: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> වෙත පණිවිඩයක් යවමින්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> වෙත අසාර්ථක පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> වෙත පණිවිඩයක්: <xliff:g id="MESSAGE">%s</xliff:g>. වේලාව: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"පණිවිඩය අසාර්ථක විය. නැවත උත්සාහ කිරීමට ස්පර්ශ කරන්න."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> සමඟ සංවාදය"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"කාරණය මකන්න"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"වීඩියෝව ග්‍රහණය කරගන්න"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"නිසල පින්තූරයක් ලබාගන්න"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"පින්තූරයක් ගන්න"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"වීඩියෝව පටිගත කිරීම ආරම්භ කරන්න"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"පූර්ණ තිර කැමරාව වෙත මාරු වෙන්න"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"ඉදිරිපස සහ පසුපස කාමර අතර මාරු නොවන්න"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"පටිගත කිරීම නවත්වා වීඩියෝව අමුණන්න"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"වීඩියෝ පටිගත කිරීම නැවත් වන්න"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"ඡායාරූප පණිවිඩ යැවීම"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one">ඡායාරූප <xliff:g id="QUANTITY_2">%d</xliff:g> ක් \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ඇල්බමය වෙත සුරකින ලදී</item>
+ <item quantity="other">ඡායාරූප <xliff:g id="QUANTITY_2">%d</xliff:g> ක් \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ඇල්බමය වෙත සුරකින ලදී</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one">වීඩියෝ <xliff:g id="QUANTITY_2">%d</xliff:g> ක් \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ඇල්බමය වෙත සුරකින ලදී</item>
+ <item quantity="other">වීඩියෝ <xliff:g id="QUANTITY_2">%d</xliff:g> ක් \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ඇල්බමය වෙත සුරකින ලදී</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one">ඇමුණුම් <xliff:g id="QUANTITY_2">%d</xliff:g> ක් \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ඇල්බමය වෙත සුරකින ලදී</item>
+ <item quantity="other">ඇමුණුම් <xliff:g id="QUANTITY_2">%d</xliff:g> ක් \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ඇල්බමය වෙත සුරකින ලදී</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one">ඇමුණුම් <xliff:g id="QUANTITY_1">%d</xliff:g> ක් \"බාගැනීම්\" වෙත සුරකින ලදී</item>
+ <item quantity="other">ඇමුණුම් <xliff:g id="QUANTITY_1">%d</xliff:g> ක් \"බාගැනීම්\" වෙත සුරකින ලදී</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one">ඇමුණුම් <xliff:g id="QUANTITY_1">%d</xliff:g> ක් සුරකින ලදි</item>
+ <item quantity="other">ඇමුණුම් <xliff:g id="QUANTITY_1">%d</xliff:g> ක් සුරකින ලදි</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">ඇමුණුම් <xliff:g id="QUANTITY_1">%d</xliff:g> ක් සුරැකීමට නොහැකි විය</item>
+ <item quantity="other">ඇමුණුම් <xliff:g id="QUANTITY_1">%d</xliff:g> ක් සුරැකීමට නොහැකි විය</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS ඇමුණුම සුරකින ලදි"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"සැකසීම්"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"සංරක්ෂිත"</string>
+ <string name="action_close" msgid="1840519376200478419">"වසන්න"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"උසස්"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"නිදොස්කරණය"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"දැනුම්දීම්"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ශබ්දය"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"නිහඬ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"කම්පනය වන්න"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"අවහිර කරන ලදි"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS යැවීමේ වාර්තාව"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ඔබ යවන ලද සෑම පණිවිඩයකටම යැවීමේ වාර්තාවක් ඉල්ලන්න"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"ස්වයං-ලබාගැනීම"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS පණිවිඩ ස්වයංක්‍රීයව ලබාගන්න"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"රෝමින් ස්වයං-ලබාගැනීම"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"රෝමිං විට MMS ස්වයංක්‍රියව යළි ලබා ගන්න"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"කණ්ඩායම් පණිවිඩ යැවීම්"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ලබන්නන් කිහිපදෙනෙක් සිටින විට එක් පණිවිඩයක් යැවීමට MMS භාවිතා කරන්න"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"සුපුරුදු SMS යෙදුම"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"සුපුරුදු SMS යෙදුම"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"ඔබගේ දුරකථන අංකය"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"නොදනී"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"යන පණිවිඩ හඬ"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS ගොඩ ගසන්න"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ලැබුණු SMS අමු දත්ත බාහිර ආචයන ගොනුවකට ගොඩ ගසන්න"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS ගොඩ ගසන්න"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ලැබුණු MMS අමු දත්ත බාහිර ආචයන ගොනුවකට ගොඩ ගසන්න"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"නොරැහැන් ඇඟවීම්"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"පණිවිඩ විකල්ප"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"පෙළ පිටපත් කරන්න"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"විස්තර බලන්න"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"මකන්න"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ඉදිරියට යවන්න"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"පණිවිඩ විස්තර"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"වර්ගය: "</string>
+ <string name="text_message" msgid="7415419755252205721">"පෙළ පණිවුඩය"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"බහු මාධ්‍ය පණිවිඩය"</string>
+ <string name="from_label" msgid="1947831848146564875">"වෙතින්: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"වෙත: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"යවන ලද: "</string>
+ <string name="received_label" msgid="4442494712757995203">"ලැබිණි: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"කාරණය: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"ප්‍රමාණය: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ප්‍රමුඛතාව: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"ඉහළ"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"සාමාන්‍ය"</string>
+ <string name="priority_low" msgid="7398724779026801851">"පහළ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"සැඟවුණු යවන්නාගේ ලිපිනය"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"ඇමුණුම් පුරණය කිරීමේදී පණිවිඩය යැවිය නොහැක."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ඇමුණුම පුර්ණය කළ නොහැක. නැවත උත්සාහ කරන්න."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"ජාලය සූදානම් නැත. නැවත උත්සාහ කරන්න."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"පෙළ මකන්න"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"පෙළ සහ අංක ඇතුළු කිරීම අතර මාරුවන්න"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"සහභාගිවන්නන් තව එකතු කරන්න"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"සහභාගිවන්නන් සහතික කරන්න"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"අළුත් සංවාදයක් ආරම්භ කරන්න"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"මෙම අයිතමය තෝරන්න"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"වීඩියෝව ධාවනය කරන්න"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"පුද්ගලයින් සහ විකල්ප"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"නිදොස්කරණය"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"පුද්ගලයින් සහ විකල්ප"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"පොදු"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"මෙම සංවාදයේ සිටින පුද්ගලයින්"</string>
+ <string name="action_call" msgid="6596167921517350362">"ඇමතුමක් ලබාගන්න"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"පණිවිඩය යවන්න"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g> සිට&lt;/small&gt;&lt;br/&gt; පණිවිඩ යවන්න"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">ඡායාරූප යවන්න</item>
+ <item quantity="other">ඡායාරූප යවන්න</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">ශ්‍රව්‍යය යවන්න</item>
+ <item quantity="other">ශ්‍රව්‍යය යවන්න</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">වීඩියෝ යවන්න</item>
+ <item quantity="other">වීඩියෝ යවන්න</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">සම්බන්ධතා කාඩ්පත් යවන්න</item>
+ <item quantity="other">සම්බන්ධතා කාඩ්පත් යවන්න</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">ඇමුණුම් යවන්න</item>
+ <item quantity="other">ඇමුණුම් යවන්න</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one">ඇමුණුම් <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ක් යැවීමට සුදානම්</item>
+ <item quantity="other">ඇමුණුම් <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ක් යැවීමට සුදානම්</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ප්‍රතිපෝෂණය යවන්න"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play වෙළඳසැල තුළ බලන්න"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"අනුවාදයේ තොරතුරු"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"අනුවාදය %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"විවෘත මූලාශ්‍ර බලපත්‍ර"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"දැනුම්දීම්"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ඇමුණුම් සීමාව ළඟා විය"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ඇමුණුම ප්‍රවේශනය කිරීම අසාර්ථක විය."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"සම්බන්ධතාවෙත එකතු කරනද?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"සම්බන්ධතාවය එකතු කරන්න"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"මාතෘකාව"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"මාතෘකාව "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"සම්බන්ධතා කාඩ් පත් පුර්ණ කරමින්"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"සම්බන්ධතා කාඩ්පත පුර්ණය කළ නොහැක"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"සම්බන්ධ්තා කාඩ්පත පෙන්වන්න"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one">සම්බන්ධතා <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="other">සම්බන්ධතා <xliff:g id="COUNT_1">%d</xliff:g></item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"සම්බන්ධතා කාඩ්පත්"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"උපන්දිනය"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"සටහන්"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"ඉදිරියට යන පණිවිඩය"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"පිළිතුරු දෙන්න"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS අබල කරන ලදි"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"යැවීමට, පණිවිඩ යැවීම පෙරනිමි SMS යෙදුම ලෙස සකසන්න"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"පණිවිඩ යැවීම පෙරනිමි SMS යෙදුම ලෙස සකසන්න"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"වෙනස් කරන්න"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"පණිවිඩ ලබා ගැනීම සඳහා, පණිවිඩ යැවීම පෙරනිමි SMS යෙදුම ලෙස සකසන්න"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS පණිවිඩ යැවීමට සඳහා මනාප SIM එකක් තෝරාගෙන නොමැත"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"උපාංගයේ හිමිකරු මෙම යෙදුමට ඉඩ නොදෙන ලදි."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"හරි"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"සංවාදයේ සහභාගීවන්නන් ගොඩක් සිටි"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">වලංගු නොවන සම්බන්ධතා</item>
+ <item quantity="other">වලංගු නොවන සම්බන්ධතා</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"කැමරා පින්තූරය පුර්ණය කළ නොහැක"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"ඔබ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"කටු සටහන"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"අලුත් සංවාදක් ඔබ ආරම්භ කළ පසු, එය මෙතැන ලැයිස්තු කර ඇති බව ඔබ දකීවි"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"සංරක්ෂණය කළ සංවාද මෙහි දිස් වේ"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"සංවාද පුර්ණය වෙමින්..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"පින්තූරය"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ශ්‍රව්‍ය පසුර"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"වීඩියෝව"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"සම්බන්ධතා කාඩ් පත"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"අස් කරන්න"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"නැවත උත්සාහ කරන්න"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"අලුත් පණිවිඩයක් ආරම්භ කිරීමට සම්බන්ධතාවේ නම හෝ දුරකථන අංකය ඇතුළු කරන්න"</string>
+ <string name="action_block" msgid="9032076625645190136">"අවහිර කරන්න"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> අවහිර කරන ලද"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> අවහිර නොකරන්න"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> අවහිර කරලද?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"මෙම අංකයෙන් දිගටම පණිවිඩ ලැබීමට ඔබ ඉදිරියට යන ලදි නමුත් තවදුරත් දැනුම් දෙන්න එපා. මෙම සංවාදය සංරක්ෂණය කරවී."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"සම්බන්ධතා අවහිර කරන ලදි"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"අවහිර නොකරන්න"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"සම්බන්ධතා අවහිර කරන ලදි"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ලේඛන පුස්ථකාලයෙන් පින්තූර තෝරන්න"</string>
+ <string name="sending_message" msgid="6363584950085384929">"පණිවිඩය යවමින්"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"පණිවිඩය යවන ලදී"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"සෙල්‍යුලර් දත්ත අක්‍රිය කර තිබේ. ඔබගේ සැකසීම් පරික්ෂා කරන්න."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"අහස්යානා ආකාරය තුළ පණිවිඩ යැවිය නොහැක"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"පණිවිඩය යැවිය නොහැකි විය"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"පණිවිඩය බාගන්නා ලදි"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"සෙල්‍යුලර් දත්ත අක්‍රිය කර තිබේ. ඔබගේ සැකසීම් පරික්ෂා කරන්න."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ගුවන්යානා ප්‍රකාරයේදී පණිවිඩ බාගැනීමට නොහැකිය"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"පණිවිඩය බාගැනීමට නොහැකි විය"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"බිංදුව"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"එක"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"දෙක"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"තුන"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"හතර"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"පහ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"හය"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"හත"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"අට"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"නවය"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> සමඟ පණිවිඩය යැවිය නොහැක, දෝෂය <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"නොදන්නා වාහකයක් සමඟ පණිවිඩය යැවිය නොහැක, දෝෂය <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"ඉදිරි: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"පණිවිඩ නොයවන ලදි: ජාලය තුළ සක්‍රිය නොකළ සේවාවකි"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"පණිවිඩ නොයවන ලදි: ගමනාන්ත ලිපිනය වලංගු නැත"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"පණිවිඩ නොයවන ලදි: පණිවිඩය වලංගු නැත"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"පණිවිඩ නොයවන ලදි: අන්තර්ගතය සහාය නොදක්වයි"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"පණිවිඩ නොයවන ලදි: පණිවිඩ සහාය නොදක්වයි"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"පණිවිඩය නොයවන ලදි: ඉතාම විශාලයි"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"අලුත් පණිවිඩය"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"බලන්න"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"පින්තුරය"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"අදාළ යෙදුම සොයාගත නොහැක"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"ලබන්නා ඉවත් කරන්න"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"අලුත් පණිවිඩය"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"අවලංගු කරන්න"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ප්‍රවේශ ස්ථානය සංස්කරණය"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"සකසා නැත"</string>
+ <string name="apn_name" msgid="1572691851070894985">"නම‍"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS ප්‍රොක්සිය"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS තොට"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN වර්ගය"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN මකන්න"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"නව APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"සුරකින්න"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ඉවතලන්න"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"නම ක්ෂේත්‍රය හිස් විය නොහැක."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN හිස් විය නොහැක."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ක්ෂේත්‍රය සංඛ්‍යා 3 ක් විය යුතුය."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ක්ෂේත්‍රය ඉලක්කම් 2 ක් හෝ 3 ක් විය යුතුය."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"සුපුරුදු APN සැකසුම් යළි පිහිටුවමින්."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"සුපුරුදු වෙත යළි සකසන්න"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"සුපුරුදු APN සැකසීම නැවත සැකසීම සම්පූර්ණ කරන ලදි."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"නම් යොදා නැත"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ප්‍රවේශ ලක්ෂ්‍ය නම්"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"නව APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"මෙම පරිශීලකයා සඳහා ප්‍රවේශ ලක්ෂ්‍යයේ නමේ සැකසීම් නොතිබේ"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"පසුරු පුවරුව වෙත පිටපත් කරන්නද?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"පිටපත්කරණය"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> වෙත"</string>
+ <string name="general_settings" msgid="5409336577057897710">"පොදු"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"උසස්"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"පොදු සැකසීම්"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"උසස් සැකසුම්"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"සියළුම ලබන්නන් වෙත තනි SMS පණිවිඩ යවන්න. ඕනෑම පිළිතුරක් පමණක් ඔබට ලැබේ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"සියළුම ලබන්නන් වෙත තනි MMS යවන්න"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"නොදන්නා අංකය"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"අලුත් පණිවිඩය"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"අලුත් පණිවිඩය."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM තෝරාගැනීම"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> තෝරාගත්තේය, SIM තෝරාගැනීම"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"මාතෘකාව සකසන්න"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM තෝරන්න හෝ මාතෘකාව සකසන්න"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ශ්‍රව්‍ය පටිගත කිරීමට තට්ටු කර අල්ලා සිටින්න"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"අළුත් සංවාදයක් ආරම්භ කරන්න"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"පණිවිඩ යැවීම"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"පණිවිඩ යැවීමේ ලැයිස්තුව"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"පණිවිඩ යැවීම"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"අලුත් පණිවිඩය"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"සංවාද ලැයිස්තුව"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"සංවාද පුරණය වෙමින්"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"පණිවුඩ පූරණය කරමින්"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"තවත් සංවාද පෙන්වන්න"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"View more messages"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"සංවාදය මකාදැමුණි"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"සංවාදය මකන ලදී. වෙනස් පණිවිඩ යැවීමේ සංවාදයක් පෙන්වීමට ස්පර්ශ කරන්න"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"අවහිර කරන ලදි"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"අවහුර නොකළ"</string>
+ <string name="db_full" msgid="8459265782521418031">"ගබඩා ඉඩ අවමයි. ඇතැම් දත්ත අහිමි වනු ඇත."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"ඇමුණුම් තෝරන්න"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"තේරීම සහතික කරන්න"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ක් තෝරන ලදි"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"කරුණාකර ඇමුණුම් එකක් හෝ වැඩි සංඛ්‍යාවක් ඉවත් කර නැවත උත්සාහ කරන්න."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"ඔබට ඔබේ පණිවිඩය යැවීමට උත්සාහ කළ හැකි වුවත්, ඔබ ඇමුණුම් එකක් හෝ වැඩි සංඛ්‍යාවක් ඉවත් කරන්නේ නම් හැර එය බෙදාගැනීම නොහැකි විය හැකිය."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"ඔබට පණිවිඩයකට එක් වීඩියෝවක් පමණක් යැවිය හැකිය. කරුණාකර අමතර වීඩියෝ ඉවත් කර නැවත උත්සාහ කරන්න."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"පණිවිඩ යැවීම ඇමුණුම පූරණය කිරීමට අසමත් විය."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"කෙසේ වුවත් යවන්න"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"සංවාදය ඇරඹීමට නොහැකි විය"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> තෝරාගත්තේය"</string>
+</resources>
diff --git a/res/values-sk/arrays.xml b/res/values-sk/arrays.xml
new file mode 100644
index 0000000..0e48259
--- /dev/null
+++ b/res/values-sk/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"bez predmetu"</item>
+ <item msgid="272485471009191934">"žiadny predmet"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Áno"</item>
+ <item msgid="6049132459802288033">"Nie"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Haha"</item>
+ <item msgid="2611328818571146775">"Ďakujeme"</item>
+ <item msgid="4881335087096496747">"Súhlasím"</item>
+ <item msgid="2422296858597420738">"Pekné"</item>
+ <item msgid="4805581752819452687">"Som na ceste"</item>
+ <item msgid="4746700499431366214">"OK, odpíšem neskôr"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..32ec96b
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"SMS a MMS"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"SMS a MMS"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Vybrať konverzáciu"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Nastavenia"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Odoslať správu"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Pridať prílohu"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Pomocník"</string>
+ <string name="welcome" msgid="2857560951820802321">"Vitajte"</string>
+ <string name="skip" msgid="7238879696319945853">"Preskočiť"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Ďalej &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Ďalej"</string>
+ <string name="exit" msgid="1905187380359981199">"Ukončiť"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Nastavenia &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Nastavenia"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Aplikácia SMS a MMS vyžaduje povolenie na prístup k správam SMS, telefónu a kontaktom."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Povolenia môžete zmeniť v časti Nastavenia &gt; Aplikácie &gt; SMS a MMS &gt; Povolenia."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Povolenia môžete zmeniť v časti Nastavenia, Aplikácie, SMS a MMS, Povolenia."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Často kontaktované osoby"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Všetky kontakty"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Odoslať na číslo <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Snímať obrázky alebo videá"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Vybrať obrázky z tohto zariadenia"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Zaznamenať zvuk"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Vybrať fotku"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Je vybrané médium."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Výber média bol zrušený."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Vybrané: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"obrázok – <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" msgid="8142175029053370662">"obrázok"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Zaznamenanie zvuku"</string>
+ <string name="action_share" msgid="2143483844803153871">"Zdieľať"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Práve teraz"</string>
+ <string name="posted_now" msgid="867560789350406701">"Teraz"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> hodiny</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> hodiny</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> hodín</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> hodina</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dni</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> dňa</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dní</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> deň</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> týždne</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> týždňa</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> týždňov</item>
+ <item quantity="one">týždeň</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> mesiace</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> mesiaca</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mesiacov</item>
+ <item quantity="one">mesiac</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> roky</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> roka</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> rokov</item>
+ <item quantity="one">rok</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Správa triedy 0"</string>
+ <string name="save" msgid="5081141452059463572">"Uložiť"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"V zariadení dochádza voľné miesto. Služba SMS a MMS bude automaticky odstraňovať staršie správy, aby uvoľnila miesto."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Nedostatok miesta na úložisku"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Aplikácia SMS a MMS nebude môcť posielať ani prijímať správy, pokiaľ v zariadení nebude k dispozícii viac miesta."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"V úložisku pre správy SMS nie je dostatok voľného miesta. Je možné, že budete musieť správy odstrániť."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Potvrďte svoje telefónne číslo"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Tento jednorazový krok zaistí, že aplikácia SMS a MMS bude správne doručovať vaše skupinové správy."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefónne číslo"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Odstrániť všetky správy s médiami"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Odstrániť správy staršie ako <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Automaticky odstrániť správy staršie ako <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorovať"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Odstrániť všetky správy s médiami?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Odstrániť správy staršie ako <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Chcete odstrániť správy staršie ako <xliff:g id="DURATION">%s</xliff:g> a zapnúť automatické odstraňovanie?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> povedal(a)"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Povedali ste"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Správa od používateľa <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Odoslali ste správu"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Odosiela sa…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Správa nebola odoslaná. Klepnutím to skúste znova."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Správa nebola odoslaná. Skúšame to znova…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Znova odoslať alebo odstrániť"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Zavolajte na pohotovostné služby. Vašu textovú správu sa v tejto chvíli nepodarilo doručiť."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Nepodarilo sa"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nová správa MMS na stiahnutie"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nová správa MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nepodarilo sa stiahnuť"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Klepnutím to skúste znova"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Stiahnite klepnutím"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Stiahnuť alebo odstrániť"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Sťahuje sa..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Platnosť správy vypršala alebo správa nie je k dispozícii"</string>
+ <string name="mms_info" msgid="3402311750134118165">"veľkosť: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, vypršanie platnosti: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Správu nie je možné odoslať. Nie je uvedený platný príjemca."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Služba nie je v sieti aktivovaná"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Správu sa nepodarilo odoslať, pretože sa vyskytol problém so sieťou"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Platnosť správy vypršala alebo správa nie je k dispozícii"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Bez predmetu)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Neznámy odosielateľ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Doručené"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nepodarilo sa stiahnuť správu <xliff:g id="SUBJECT">%1$s</xliff:g> od odosielateľa <xliff:g id="FROM">%2$s</xliff:g>"</string>
+ <string name="low_memory" msgid="5300743415198486619">"Operáciu v databáze sa nepodarilo dokončiť z dôvodu nedostatku pamäte"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Správu sa nepodarilo odoslať"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Niektoré správy neboli odoslané v aplikácii SMS a MMS"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> Počet správ v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzáciách</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> Počet správ v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzácie</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> Počet správ v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzáciách</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> Počet správ v jednej konverzácii</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Správu sa nepodarilo stiahnuť"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Niektoré správy neboli stiahnuté v aplikácii SMS a MMS"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> Počet správ v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzáciách</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> Počet správ v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzácie</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> Počet správ v <xliff:g id="CONVERSATIONS">%d</xliff:g> konverzáciách</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> Počet správ v jednej konverzácii</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Správa na číslo <xliff:g id="NUMBER">%1$s</xliff:g> nebola odoslaná"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Zavolajte na pohotovostné služby. Vašu textovú správu na číslo <xliff:g id="NUMBER">%1$s</xliff:g> sa v tejto chvíli nepodarilo doručiť."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> nové správy</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> novej správy</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nových správ</item>
+ <item quantity="one">Nová správa</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Spustiť"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Fotoaparát nie je k dispozícii"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Fotoaparát nie je k dispozícii"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Snímanie videa nie je k dispozícii"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Média nie je možné uložiť"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Snímku nie je možné odfotiť"</string>
+ <string name="back" msgid="1477626055115561645">"Späť"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Archivované"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Odstrániť"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Archivovať"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Vybrať z archívu"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Vypnúť upozornenia"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Zapnúť upozornenia"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Pridať kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Stiahnuť"</string>
+ <string name="action_send" msgid="377635240181672039">"Odoslať"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Odstrániť"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Chcete odstrániť túto správu?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Táto akcia sa nedá vrátiť späť."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Odstrániť"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="few">Odstrániť tieto konverzácie?</item>
+ <item quantity="many">Odstrániť tieto konverzácie?</item>
+ <item quantity="other">Odstrániť tieto konverzácie?</item>
+ <item quantity="one">Odstrániť túto konverzáciu?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Odstrániť"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Zrušiť"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Komu"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Vybrať viaceré obrázky"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Potvrdiť výber"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Zvuk sa nedá zaznamenať. Skúste to znova."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Zvuk sa nedá prehrať. Skúste to znova."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Zvuk sa nepodarilo uložiť. Skúste to znova."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Klepnutie a podržanie"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Fotografia"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Zvukový klip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Vizitka"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Stiahnuť"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Odpovedať pomocou správy SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Odpovedať správou MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Odpovedať"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> účastníci</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> účastníka</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> účastníkov</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> účastník</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ja"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt bol zablokovaný a archivovaný"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt bol odblokovaný a jeho archivácia bola zrušená"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Archivované: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Zrušená archivácia: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Upozornenia sú vypnuté"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Upozornenia sú zapnuté"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Všetko je pripravené. Klepnite znova na tlačidlo Odoslať."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Aplikácia SMS a MMS bola úspešne nastavená ako predvolená aplikácia pre SMS"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="few">Zahodiť prílohy</item>
+ <item quantity="many">Zahodiť prílohy</item>
+ <item quantity="other">Zahodiť prílohy</item>
+ <item quantity="one">Zahodiť prílohu</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Zvuková príloha"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Prehrať zvukovú prílohu"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pozastaviť"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Správa od používateľa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Správa od používateľa <xliff:g id="SENDER">%s</xliff:g> bola neúspešná: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Správa od používateľa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Správa nebola odoslaná používateľovi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Odosiela sa správa používateľovi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Správa používateľovi <xliff:g id="CONTACT">%s</xliff:g> bola neúspešná: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Správa používateľovi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Neúspešná správa od používateľa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Správa od používateľa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Správa nebola odoslaná skupine <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Odosiela sa správa skupine <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Správa skupine <xliff:g id="GROUP">%s</xliff:g> bola neúspešná: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Správa skupine <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Neúspešná správa. Skúste to znova klepnutím."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Rozhovor s používateľmi <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Odstrániť predmet"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Natočiť video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Urobiť statickú snímku"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Vyfotiť"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Spustiť natáčanie videa"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Prepnúť na fotoaparát na celej obrazovke"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Prepnúť medzi predným a zadným fotoaparátom"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Zastaviť zaznamenávanie a pripojiť video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Zastaviť zaznamenávanie videa"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotky v aplikácii SMS a MMS"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> fotky boli uložené do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> fotky bolo uloženej do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotiek bolo uložených do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> fotka bola uložená do albumu <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> videá boli uložené do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> videa bolo uloženého do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videí bolo uložených do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video bolo uložené do albumu <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> prílohy boli uložené do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> prílohy bolo uloženej do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> príloh bolo uložených do albumu <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> príloha bola uložená do albumu <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> prílohy boli uložené do priečinka so stiahnutými súbormi</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> prílohy bolo uloženej do priečinka so stiahnutými súbormi</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> príloh bolo uložených do priečinka so stiahnutými súbormi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> príloha bola uložená do priečinka so stiahnutými súbormi</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="few">Boli uložené <xliff:g id="QUANTITY_1">%d</xliff:g> prílohy</item>
+ <item quantity="many">Bolo uloženej <xliff:g id="QUANTITY_1">%d</xliff:g> prílohy</item>
+ <item quantity="other">Bolo uložených <xliff:g id="QUANTITY_1">%d</xliff:g> príloh</item>
+ <item quantity="one">Bola uložená <xliff:g id="QUANTITY_0">%d</xliff:g> príloha</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="few">Nepodarilo sa uložiť <xliff:g id="QUANTITY_1">%d</xliff:g> prílohy</item>
+ <item quantity="many">Nepodarilo sa uložiť <xliff:g id="QUANTITY_1">%d</xliff:g> prílohy</item>
+ <item quantity="other">Nepodarilo sa uložiť <xliff:g id="QUANTITY_1">%d</xliff:g> príloh</item>
+ <item quantity="one">Nepodarilo sa uložiť <xliff:g id="QUANTITY_0">%d</xliff:g> prílohu</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Uložené prílohy správ MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Nastavenia"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Archivované"</string>
+ <string name="action_close" msgid="1840519376200478419">"Zavrieť"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Rozšírené"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Ladiť"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Upozornenia"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Zvuk"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Tichý režim"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibrovanie"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokované"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Prehľady doručovania SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Vyžadovať potvrdenie o doručení každej odoslanej SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatické načítanie"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Automaticky načítať správy MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Automatické načítanie pri roamingu"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Automaticky načítať správy MMS počas roamingu"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Skupinové správy"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Použiť MMS na odoslanie jednej správy, ak má niekoľko príjemcov"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Predvolená aplikácia SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Predvolená aplikácia SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Vaše telefónne číslo"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Neznáme"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Zvuky odosielanej správy"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Zálohovať správy SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Zálohovať nespracované údaje prijatých správ SMS do súboru v externom úložisku"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Zálohovať správy MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Zálohovať nespracované údaje prijatých správ MMS do súboru v externom úložisku"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Bezdrôtové núdzové upozornenia"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Možnosti správy"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopírovať text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Zobraziť podrobnosti"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Odstrániť"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Poslať ďalej"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Podrobnosti správy"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Typ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Textová správa"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimediálna správa"</string>
+ <string name="from_label" msgid="1947831848146564875">"Od: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Komu: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Odoslané: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Prijaté: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Predmet: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Veľkosť: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priorita: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM karta: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Vysoká"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normálna"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Nízka"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"Slot SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Skrytá adresa odosielateľa"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Počas načítavania príloh nie je možné odoslať správu."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nie je možné načítať prílohu. Skúste to znova."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Sieť nie je pripravená. Skúste to znova."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Odstrániť text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Prepnúť medzi zadávaním textu a číslic"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Pridať ďalších účastníkov"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Potvrdiť účastníkov"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Začať novú konverzáciu"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Vybrať túto položku"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Prehrať video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Ľudia a možnosti"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Ladiť"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Ľudia a možnosti"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Všeobecné"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Ľudia v tejto konverzácii"</string>
+ <string name="action_call" msgid="6596167921517350362">"Zavolať"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Odoslať správu"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Odošlite správu&lt;br/&gt;&lt;small&gt;zo SIM karty <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="few">Odoslať fotky</item>
+ <item quantity="many">Odoslať fotky</item>
+ <item quantity="other">Odoslať fotky</item>
+ <item quantity="one">Odoslať fotku</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="few">Odoslať zvukové súbory</item>
+ <item quantity="many">Odoslať zvukové súbory</item>
+ <item quantity="other">Odoslať zvukové súbory</item>
+ <item quantity="one">Odoslať zvukový súbor</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="few">Odoslať videá</item>
+ <item quantity="many">Odoslať videá</item>
+ <item quantity="other">Odoslať videá</item>
+ <item quantity="one">Odoslať video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="few">Odoslať vizitky</item>
+ <item quantity="many">Odoslať vizitky</item>
+ <item quantity="other">Odoslať vizitky</item>
+ <item quantity="one">Odoslať vizitku</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="few">Odoslať prílohy</item>
+ <item quantity="many">Odoslať prílohy</item>
+ <item quantity="other">Odoslať prílohy</item>
+ <item quantity="one">Odoslať prílohu</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> prílohy sú pripravené na odoslanie</item>
+ <item quantity="many"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> prílohy je pripravenej na odoslanie</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> príloh je pripravených na odoslanie</item>
+ <item quantity="one">Jedna príloha je pripravená na odoslanie</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Odoslať spätnú väzbu"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Zobraziť v Obchode Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informácie o verzii"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Verzia %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licencie open source"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Upozornenia"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Dosiahli ste limit pre počet príloh"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Prílohu sa nepodarilo načítať."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Pridať medzi kontakty?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Pridať kontakt"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Predmet"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Predmet: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Načítava sa vizitka"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Nepodarilo sa načítať vizitku"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Zobraziť vizitku"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> kontakty</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> kontaktu</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontaktov</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Vizitky"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Narodeniny"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Poznámky"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Poslanie správy ďalej"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Odpovedanie"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Správy SMS sú zakázané"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Ak chcete odosielať správy, nastavte SMS a MMS ako predvolenú aplikáciu pre SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Nastavte SMS a MMS ako predvolenú aplikáciu pre SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Zmeniť"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Ak chcete prijímať správy, nastavte SMS a MMS ako predvolenú aplikáciu pre SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nie je vybraná preferovaná SIM karta na odosielanie správ SMS."</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Túto aplikáciu vlastník zariadenia nepovolil."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"V konverzácii je príliš mnoho účastníkov"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="few">Neplatné kontakty</item>
+ <item quantity="many">Neplatné kontakty</item>
+ <item quantity="other">Neplatné kontakty</item>
+ <item quantity="one">Neplatný kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Obrázok z fotoaparátu sa nepodarilo načítať"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Vy: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Koncept"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Tu uvidíte svoje konverzácie"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Tu sa zobrazujú archivované konverzácie"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Načítavajú sa konverzácie..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Obrázok"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Zvukový klip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Vizitka"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Späť"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Skúsiť znova"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Zadajte meno kontaktu alebo telefónne číslo a začnite písať novú správu"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokovať"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokovať kontakt <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Odblokovať kontakt <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Blokovať <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Z tohto čísla budete aj naďalej dostávať správy. Nebudete už však dostávať upozornenia. Táto konverzácia bude archivovaná."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokované kontakty"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ODBLOKOVAŤ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokované kontakty"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Vyberte obrázok z knižnice dokumentov"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Správa sa odosiela"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Správa bola odoslaná"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Prenos mobilných údajov je vypnutý. Skontrolujte svoje nastavenia."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"V režime V lietadle nie je možné odosielať správy"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Odoslanie správy zlyhalo"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Správa bola stiahnutá"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Prenos mobilných dát je vypnutý. Skontrolujte svoje nastavenia."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"V režime v lietadle nie je možné sťahovať správy"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Správu nie je možné stiahnuť"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nula"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Jedna"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dva"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tri"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Štyri"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Päť"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Šesť"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sedem"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Osem"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Deväť"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Správu prostredníctvom operátora <xliff:g id="CARRIERNAME">%1$s</xliff:g> nie je možné odoslať. Chyba: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Správu prostredníctvom neznámeho operátora nie je možné odoslať. Chyba: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Poslať ďalej: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Správa nebola odoslaná: služba nie je aktivovaná v sieti"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Správa nebola odoslaná: neplatná cieľová adresa"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Správa nebola odoslaná: neplatná správa"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Správa nebola odoslaná: nepodporovaný obsah"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Správa nebola odoslaná: nepodporovaná správa"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Správa nebola odoslaná: veľkosť prekročila limit"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nová správa"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Zobraziť"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Obrázok"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nenašla sa vhodná aplikácia"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Odstrániť príjemcu"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nová správa"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Zrušiť"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Úprava prístupového bodu"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nenastavené"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Názov"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Názov prístupového bodu"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy server systému MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port systému MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Typ prístupového bodu"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Odstrániť názov prístupového bodu (APN)"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nový prístupový bod"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Uložiť"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Zrušiť"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Pole Meno nemôže byť prázdne."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Názov prístupového bodu (APN) nemôže byť prázdny."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Pole MCC musí obsahovať 3 číslice."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Pole MNC musí obsahovať 2 alebo 3 číslice."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Obnovujú sa predvolené nastavenia názvu prístupového bodu (APN)."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Obnoviť predvolené"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Predvolené nastavenia prístupových bodov boli obnovené"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Bez názvu"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Názvy prístupových bodov"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Prístupové body"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nový prístupový bod"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Nastavenia názvu prístupového bodu (APN) nie sú dostupné pre tohto používateľa"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopírovať do schránky?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopírovať"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"na SIM kartu <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Všeobecné"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Rozšírené"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Všeobecné nastavenia"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Rozšírené nastavenia"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM karta <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Odoslať osobitné správy SMS všetkým príjemcom. Všetky odpovede dostanete iba vy."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Odoslať jednu správu MMS všetkým príjemcom"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Neznáme číslo"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nová správa"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nová správa"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Výber SIM karty"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Výber SIM karty (vybraná SIM karta <xliff:g id="SIM_0">%1$s</xliff:g>)"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Upraviť predmet"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Vybrať SIM kartu alebo upraviť predmet"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Zvuk zaznamenáte klepnutím a podržaním"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Začať novú konverzáciu"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS a MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Zoznam v aplikácii SMS a MMS"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS a MMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nová správa"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Zoznam konverzácií"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Načítavajú sa konverzácie..."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Načítavajú sa správy"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Zobraziť ďalšie konverzácie"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Zobraziť viac správ"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Konverzácia bola odstránená"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Konverzácia bola odstránená. Klepnutím si zobrazte inú konverzáciu aplikácie SMS a MMS."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Zablokované"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Odblokované"</string>
+ <string name="db_full" msgid="8459265782521418031">"V úložisku je málo miesta. Niektoré údaje môžu byť stratené."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Vybrať prílohy"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Potvrďte výber"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Vybrané: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Odstráňte jednu alebo viac príloh a skúste to znova."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Svoju správu sa môžete pokúsiť odoslať, ale pravdepodobne bude doručená až vtedy, keď odstránite jednu alebo viac príloh."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Môžete odoslať iba jedno video na správu. Odstráňte ďalšie videá a skúste to znova."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Aplikácii SMS a MMS sa nepodarilo načítať prílohu."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Aj tak odoslať"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Rozhovor nie je možné spustiť"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Vybrané: <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-sl/arrays.xml b/res/values-sl/arrays.xml
new file mode 100644
index 0000000..0cd69b7
--- /dev/null
+++ b/res/values-sl/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"brez zadeve"</item>
+ <item msgid="272485471009191934">"nizadeve"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Da"</item>
+ <item msgid="6049132459802288033">"Ne"</item>
+ <item msgid="3084376867445867895">"V redu"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Hvala"</item>
+ <item msgid="4881335087096496747">"Strinjam se"</item>
+ <item msgid="2422296858597420738">"Lepo"</item>
+ <item msgid="4805581752819452687">"Prihajam"</item>
+ <item msgid="4746700499431366214">"V redu, oglasim se pozneje"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..2352632
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Sporočila"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Sporočila"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Izbira pogovora"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Nastavitve"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Pošlji sporočilo"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Dodajte prilogo"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Pomoč"</string>
+ <string name="welcome" msgid="2857560951820802321">"Pozdravljeni"</string>
+ <string name="skip" msgid="7238879696319945853">"Preskoči"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Naprej &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Naprej"</string>
+ <string name="exit" msgid="1905187380359981199">"Izhod"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Nastavitve &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Nastavitve"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Sporočila zahtevajo dovoljenje za dostop do sporočil SMS, telefona in stikov."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Dovoljenja lahko spreminjate v »Nastavitve &gt; Aplikacije &gt; Sporočila &gt; Dovoljenja«."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Dovoljenja lahko spreminjate v »Nastavitve, Aplikacije, Sporočila, Dovoljenja«."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Pogosti"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Vsi stiki"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Pošlji na številko <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Snemanje slik ali videoposnetkov"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Izbira slik iz te naprave"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Snemanje zvoka"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Izbira fotografije"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Predstavnost je izbrana."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Izbor predstavnosti je preklican."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Št. izbranih: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"slika <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" msgid="8142175029053370662">"slika"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Snemanje zvoka"</string>
+ <string name="action_share" msgid="2143483844803153871">"Skupna raba"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Pravkar"</string>
+ <string name="posted_now" msgid="867560789350406701">"Zdaj"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ura</item>
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> uri</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> ure</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ur</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> dan</item>
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> dneva</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> dni</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dni</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> teden</item>
+ <item quantity="two"><xliff:g id="COUNT">%d</xliff:g> tedna</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> tedne</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tednov</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> mesec</item>
+ <item quantity="two"><xliff:g id="COUNT">%d</xliff:g> meseca</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> mesece</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> mesecev</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> leto</item>
+ <item quantity="two"><xliff:g id="COUNT">%d</xliff:g> leti</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> leta</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> let</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Sporočilo razreda 0"</string>
+ <string name="save" msgid="5081141452059463572">"Shrani"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"V napravi primanjkuje prostora za shranjevanje. Sporočila bodo zaradi sprostitve prostora samodejno izbrisala starejša sporočila."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Zmanjkalo bo prostora za shranjevanje"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Sporočila morda ne bodo pošiljala ali sprejemala sporočil, dokler v napravi ne bo na voljo več prostora."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Prostora za SMS-je je malo. Morda boste morali izbrisati sporočila."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Potrditev telefonske številke"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"S tem enkratnim korakom boste zagotovili, da bodo Sporočila pravilno dostavljala skupinska sporočila."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonska številka"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Izbriši vsa sporočila s predstavnostjo"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Izbriši sporočila, starejša od <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Samodejno brisanje sporočil, starejših od <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Prezri"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Želite izbrisati vsa sporočila s predstavnostjo?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Želite izbrisati sporočila, starejša od <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Želite izbrisati sporočila, starejša od <xliff:g id="DURATION">%s</xliff:g>, in vklopiti samodejno brisanje?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"Oseba <xliff:g id="SENDER">%s</xliff:g> sporoča"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Sporočili ste"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Sporočilo osebe <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Poslali ste sporočilo"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Pošiljanje …"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Ni bilo poslano. Dotaknite se za vnovični poskus."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Ni bilo poslano. Vnovični poskus …"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Znova pošlji ali izbriši"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Pokličite nujno pomoč. SMS-ja ni bilo mogoče poslati."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Ni uspelo"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Novo sporočilo MMS za prenos"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Novo sporočilo MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Prenos ni uspel"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Dotaknite se za vnovični poskus"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Dotaknite se za prenos"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Prenesi ali izbriši"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Prenašanje …"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Sporočilo je poteklo ali ni na voljo"</string>
+ <string name="mms_info" msgid="3402311750134118165">"velikost: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, poteče: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Pošiljanje ni mogoče. Prejemnik ni veljaven."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Storitev ni aktivirana v omrežju"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Pošiljanje zaradi težave v omrežju ni uspelo"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Sporočilo je poteklo ali ni na voljo"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Ni zadeve)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Neznan pošiljatelj"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Dostavljeno"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Sporočila <xliff:g id="SUBJECT">%1$s</xliff:g> osebe <xliff:g id="FROM">%2$s</xliff:g> ni bilo mogoče prenesti."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Postopka v zbirki podatkov ni bilo mogoče dokončati, ker primanjkuje pomnilnika"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Sporočilo ni bilo poslano"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Nekatera sporočila niso bila poslana v Sporočilih"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočilo v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovoru</item>
+ <item quantity="two"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočili v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovorih</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočila v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovorih</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočil v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovorih</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Sporočilo ni bilo preneseno"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Nekatera sporočila niso bila prenesena v Sporočilih"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočilo v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovoru</item>
+ <item quantity="two"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočili v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovorih</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočila v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovorih</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> sporočil v <xliff:g id="CONVERSATIONS">%d</xliff:g> pogovorih</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Sporočilo na številko <xliff:g id="NUMBER">%1$s</xliff:g> ni bilo poslano"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Pokličite nujno pomoč. SMS-ja na <xliff:g id="NUMBER">%1$s</xliff:g> ni bilo mogoče poslati."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> novo sporočilo</item>
+ <item quantity="two"><xliff:g id="MESSAGES">%d</xliff:g> novi sporočili</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> nova sporočila</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> novih sporočil</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Začni"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Fotoaparat ni na voljo"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Fotoaparat ni na voljo"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Snemanje videoposnetkov ni na voljo"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Predstavnosti ni mogoče shraniti"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Ni mogoče posneti fotografije"</string>
+ <string name="back" msgid="1477626055115561645">"Nazaj"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arhivirano"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Izbriši"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arhiviraj"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Odstrani iz arhiva"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Izklop obvestil"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Vklop obvestil"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Dodaj stik"</string>
+ <string name="action_download" msgid="7786338136368564146">"Prenos"</string>
+ <string name="action_send" msgid="377635240181672039">"Pošlji"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Izbriši"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Želite izbrisati sporočilo?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Tega dejanja ni mogoče razveljaviti."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Izbriši"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Želite izbrisati te pogovore?</item>
+ <item quantity="two">Želite izbrisati te pogovore?</item>
+ <item quantity="few">Želite izbrisati te pogovore?</item>
+ <item quantity="other">Želite izbrisati te pogovore?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Izbriši"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Prekliči"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Za"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Izbira več slik"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Potrditev izbora"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Zvoka ni mogoče posneti. Poskusite znova."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Zvoka ni mogoče predvajati. Poskusite znova."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Zvoka ni bilo mogoče shraniti. Poskusite znova."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Dotaknite se in pridržite"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Slika"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Zvočni posnetek"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Videoposnetek"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Osebna vizitka"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Prenos"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Odgovor prek sporočila SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Odg. z MMS-jem"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Odgovori"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> udeleženec</item>
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> udeleženca</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> udeleženci</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> udeležencev</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Jaz"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Blokirani in arhivirani stiki"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Stik odblokiran in odstranjen iz arhiva"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Št. arhiviranih: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Št. povrnjenih iz arhiva: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Obvestila izklopljena"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Obvestila vklopljena"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Vse je nastavljeno. Znova se dotaknite »Pošlji«."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Sporočila so uspešno nastavljena kot privzeta aplikacija za sporočila SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Zavrzi priloge</item>
+ <item quantity="two">Zavrzi priloge</item>
+ <item quantity="few">Zavrzi priloge</item>
+ <item quantity="other">Zavrzi priloge</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Zvočna priloga"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Predvajanje zvočne priloge"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Začasna zaustavitev"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Sporočilo pošiljatelja <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Neuspelo sporočilo osebe <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Sporočilo osebe <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Neposlano sporočilo osebi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Pošiljanje sporočila osebi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Neuspelo sporočilo osebi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Sporočilo osebi <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Neuspelo sporočilo osebe <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Sporočilo osebe <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Neposlano sporočilo skupini <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Pošiljanje sporočila skupini <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Neuspelo sporočilo skupini <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Sporočilo skupini <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Čas: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Neuspelo sporočilo. Dotaknite se za vnovičen poskus."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Pogovor z udeleženci <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Izbris zadeve"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Posnemi video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Posnemi fotografijo"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Fotografiraj"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Začni snemanje videa"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Preklopi v celozaslonski način fotoaparata"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Preklop med sprednjim in zadnjim fotoaparatom"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Ustavi snemanje in pripni videoposnetek"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Ustavi snemanje videoposnetka"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Fotografije v Sporočilih"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografija shranjena v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="two"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografiji shranjeni v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografije shranjene v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografij shranjenih v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> videoposnetek shranjen v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="two"><xliff:g id="QUANTITY_2">%d</xliff:g> videoposnetka shranjena v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> videoposnetki shranjeni v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videoposnetkov shranjenih v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> priloga shranjena v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="two"><xliff:g id="QUANTITY_2">%d</xliff:g> prilogi shranjeni v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> priloge shranjene v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> prilog shranjenih v album »<xliff:g id="ALBUMNAME_3">%s</xliff:g>«</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> priloga shranjena v »Prenosi«</item>
+ <item quantity="two"><xliff:g id="QUANTITY_1">%d</xliff:g> prilogi shranjeni v »Prenosi«</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> priloge shranjene v »Prenosi«</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> prilog shranjenih v »Prenosi«</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> priloga je bila shranjena</item>
+ <item quantity="two"><xliff:g id="QUANTITY_1">%d</xliff:g> prilogi sta bili shranjeni</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> priloge so bile shranjene</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> prilog je bilo shranjenih</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> priloge ni bilo mogoče shraniti</item>
+ <item quantity="two"><xliff:g id="QUANTITY_1">%d</xliff:g> prilog ni bilo mogoče shraniti</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> prilog ni bilo mogoče shraniti</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> prilog ni bilo mogoče shraniti</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Shranjena priloga sporočila MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Nastavitve"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arhivirano"</string>
+ <string name="action_close" msgid="1840519376200478419">"Zapri"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Dodatno"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Odpravljanje napak"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Obvestila"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Zvok"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Tiho"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibriranje"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blokirano"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Poročila o dostavi sporočil SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Zahtevanje poročila o dostavi za vsak poslan SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Samodejni prenos"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Samodejni prenos MMS-jev"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Samodejni prenos med gostovanjem"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Samodejni prenos sporočil MMS med gostovanjem"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Skupinsko pošiljanje sporočil"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Če želite eno sporočilo poslati več prejemnikom, uporabite MMS"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Privzeta aplikacija za sporočila SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Privzeta aplikacija za sporočila SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Vaša telefonska številka"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Neznano"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Zvoki pri odhodnih sporočilih"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Izvoz SMS-jev"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Izvoz neobdelanih podatkov iz prejetih SMS-jev v datoteko v zunanji napravi za shranjevanje"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Izvoz MMS-jev"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Izvoz neobdelanih podatkov iz prejetih MMS-jev v datoteko v zunanji napravi za shranjevanje"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Brezžična opozorila"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Možnosti sporočila"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopiraj besedilo"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ogled podrobnosti"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Brisanje"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Posreduj"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Podrobnosti sporočila"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Vrsta: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"MMS"</string>
+ <string name="from_label" msgid="1947831848146564875">"Od: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Za: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Poslano: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Prejeto: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Zadeva: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Velikost: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prednost: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Visoka"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Običajna"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Nizka"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Skrit naslov pošiljatelja"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Med nalaganjem priponk ni mogoče poslati sporočila."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Priloge ni mogoče naložiti. Poskusite znova."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Omrežje ni pripravljeno. Poskusite znova."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Izbriši besedilo"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Preklop med vnašanjem besedila in številk"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Dodaj več udeležencev"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Potrditev udeležencev"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Začenjanje novega pogovora"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Izbira tega elementa"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Predvajanje videoposnetka"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Osebe in možnosti"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Odpravljanje napak"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Osebe in možnosti"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Splošno"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Osebe v tem pogovoru"</string>
+ <string name="action_call" msgid="6596167921517350362">"Klicanje"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Pošlji sporočilo"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Pošiljanje sporočila&lt;br/&gt;&lt;small&gt;s kartico <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Pošljite fotografije</item>
+ <item quantity="two">Pošljite fotografije</item>
+ <item quantity="few">Pošljite fotografije</item>
+ <item quantity="other">Pošljite fotografije</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Pošljite zvočne posnetke</item>
+ <item quantity="two">Pošljite zvočne posnetke</item>
+ <item quantity="few">Pošljite zvočne posnetke</item>
+ <item quantity="other">Pošljite zvočne posnetke</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Pošljite videoposnetke</item>
+ <item quantity="two">Pošljite videoposnetke</item>
+ <item quantity="few">Pošljite videoposnetke</item>
+ <item quantity="other">Pošljite videoposnetke</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Pošljite osebne vizitke</item>
+ <item quantity="two">Pošljite osebne vizitke</item>
+ <item quantity="few">Pošljite osebne vizitke</item>
+ <item quantity="other">Pošljite osebne vizitke</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Pošljite priloge</item>
+ <item quantity="two">Pošljite priloge</item>
+ <item quantity="few">Pošljite priloge</item>
+ <item quantity="other">Pošljite priloge</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> priloga je pripravljena za pošiljanje</item>
+ <item quantity="two"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> prilogi sta pripravljeni za pošiljanje</item>
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> priloge so pripravljene za pošiljanje</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> prilog je pripravljenih za pošiljanje</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Pošljite povratne informacije"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Ogled v Trgovini Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Podatki o različici"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Različica %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Odprtokodne licence"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Obvestila"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Dosežena je omejitev za priloge"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Nalaganje priloge ni uspelo."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Ali želite dodati med stike?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Dodaj stik"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Zadeva"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Zadeva: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Nalaganje osebne vizitke"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Vizitke ni bilo mogoče naložiti"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Ogled osebne vizitke"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> stik</item>
+ <item quantity="two"><xliff:g id="COUNT_1">%d</xliff:g> stika</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> stiki</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> stikov</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Vizitke"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Rojstni dan"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Zapiski"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Posredovanje sporočila"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Odgovor"</string>
+ <string name="plus_one" msgid="9010288417554932581">"še 1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"več kot %d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS-ji so onemogočeni"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Če želite pošiljati, nastavite Sporočila kot privzeto aplikacijo za sporočila SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Nastavite Sporočila kot privzeto aplikacijo za sporočila SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Spremeni"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Če želite prejemati sporočila, nastavite Sporočila kot privzeto aplikacijo za sporočila SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Ni izbrane prednostne kartice SIM za pošiljanje sporočil SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Lastnik naprave ne dovoli uporabe te aplikacije."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"V redu"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Preveč udeležencev v pogovoru"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Neveljavni stiki</item>
+ <item quantity="two">Neveljavni stiki</item>
+ <item quantity="few">Neveljavni stiki</item>
+ <item quantity="other">Neveljavni stiki</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Slike fotoaparata ni bilo mogoče naložiti"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Vi: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Osnutek"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Ko začnete nov pogovor, bo naveden tukaj"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Tu so prikazani arhivirani pogovori"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Nalaganje pogovorov ..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Slika"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Zvočni posnetek"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Videoklic"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Osebna vizitka"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Razveljavi"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Poskusi znova"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Če želite začeti novo sporočilo, vnesite ime stika ali telefonsko številko"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blokiraj"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blokiraj stik <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Odblokiraj stik <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Želite blokirati <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Še vedno boste prejemali sporočila s te številke, vendar ne boste več obveščeni. Ta pogovor bo arhiviran."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blokirani stiki"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ODBLOKIRAJ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blokirani stiki"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Izbira slike iz knjižnice dokumentov"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Pošiljanje sporočila"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Sporočilo poslano"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Prenos podatkov v mobilnem omrežju je izklopljen. Preverite nastavitve."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"V načinu za letalo ni mogoče pošiljati sporočil"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Sporočila ni bilo mogoče poslati."</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Sporočilo preneseno"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Prenos podatkov v mobilnem omrežju je izklopljen. Preverite nastavitve."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"V načinu za letalo ni mogoče prenašati sporočil"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Sporočila ni bilo mogoče prenesti"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nič"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Ena"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dva"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tri"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Štiri"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Pet"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Šest"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sedem"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Osem"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Devet"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Sporočila prek operaterja <xliff:g id="CARRIERNAME">%1$s</xliff:g> ni mogoče poslati, napaka <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Sporočila prek neznanega operaterja ni mogoče poslati, napaka <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Sporočilo ni bilo poslano: storitev ni aktivirana v omrežju"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Sporočilo ni poslano: neveljaven naslov prejemnika"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Sporočilo ni poslano: neveljavno sporočilo"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Sporočilo ni poslano: nepodprta vsebina"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Sporočilo ni poslano: nepodprto sporočilo"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Sporočilo ni bilo poslano: prevelika velikost"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Novo sporočilo"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Pogled"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Slika"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Ni bilo mogoče najti primerne aplikacije"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Odstrani prejemnika"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Novo sporočilo"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Prekliči"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Urejanje dostopne točke"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ni nastavljeno"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Ime"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"Storitveni center za sporočila MMS"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Strežnik proxy za sporočila MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Vrata za sporočila MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"Mobilna koda države"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"Koda mobilnega omrežja"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Vrsta APN-ja"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Izbriši APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nov APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Shrani"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Zavrzi"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Polje z imenom ne sme biti prazno."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ne sme biti prazen."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Polje z mobilno kodo države mora vsebovati 3 števke."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Polje s kodo mobilnega omrežja mora vsebovati 2 ali 3 števke."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Obnavljanje privzetih nastavitev APN-ja."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Ponastavi na privzeto"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Ponastavitev privzetih nastavitev APN-ja je končana."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Neimenovano"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Imena dostopnih točk"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-ji"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nov APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Nastavitve imena dostopne točke niso na voljo za tega uporabnika"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Želite kopirati v odložišče?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiraj"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"v kartico <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Splošno"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Dodatno"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Splošne nastavitve"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Dodatne nastavitve"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Kartica SIM »<xliff:g id="SIM_NAME">%s</xliff:g>«"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Pošiljanje posameznih sporočil SMS vsem prejemnikom. Morebitne odgovore boste prejeli samo vi."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Pošiljanje enega sporočila MMS vsem prejemnikom"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Neznana številka"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Novo sporočilo"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Novo sporočilo."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Izbirnik kartice SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Izbrana kartica SIM <xliff:g id="SIM_0">%1$s</xliff:g>, izbirnik kartic SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Uredi zadevo"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Izberi kartico SIM ali uredi zadevo"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Dotakni se in pridrži za snemanje zvoka"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Začenjanje novega pogovora"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Sporočila"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Seznam sporočil"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Sporočila"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Novo sporočilo"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Seznam pogovorov"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Nalaganje pogovorov"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Nalaganje sporočil"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Več pogovorov"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Prikaz več sporočil"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Pogovor je izbrisan"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Pogovor je izbrisan. Dotaknite se, če želite prikazati drug pogovor v Sporočilih."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blokiran(-a)"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Odblokiran(-a)"</string>
+ <string name="db_full" msgid="8459265782521418031">"Primanjkuje prostora za shranjevanje. Nekateri podatki bodo morda izgubljeni."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Izbira prilog"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Potrditev izbora"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Št. izbranih: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Odstranite eno ali več prilog in poskusite znova."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Lahko poskusite poslati sporočilo, vendar morda ne bo dostavljeno, razen če odstranite eno ali več prilog."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Pošljete lahko samo en videoposnetek na sporočilo. Odstranite druge videoposnetke in poskusite znova."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Sporočila niso uspela naložiti priloge."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Vseeno pošlji"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Pogovora ni bilo mogoče začeti"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Izbrana kartica SIM <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-sq-rAL/arrays.xml b/res/values-sq-rAL/arrays.xml
new file mode 100644
index 0000000..2744180
--- /dev/null
+++ b/res/values-sq-rAL/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"pa subjekt"</item>
+ <item msgid="272485471009191934">"pa subjekt"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Po"</item>
+ <item msgid="6049132459802288033">"Jo"</item>
+ <item msgid="3084376867445867895">"Në rregull!"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Faleminderit"</item>
+ <item msgid="4881335087096496747">"Pranoj"</item>
+ <item msgid="2422296858597420738">"Këndshëm"</item>
+ <item msgid="4805581752819452687">"Jam rrugës"</item>
+ <item msgid="4746700499431366214">"Në rregull, më lejo të të marr më vonë"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
new file mode 100644
index 0000000..b98ea92
--- /dev/null
+++ b/res/values-sq-rAL/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mesazhet"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mesazhet"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Përzgjidh bisedën"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Cilësimet"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Dërgo mesazh"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Shto një bashkëngjitje"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Ndihma"</string>
+ <string name="welcome" msgid="2857560951820802321">"Mirë se erdhe"</string>
+ <string name="skip" msgid="7238879696319945853">"Kapërce"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Përpara &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Përpara"</string>
+ <string name="exit" msgid="1905187380359981199">"Dil"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Cilësimet &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Cilësimet"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mesazhet kanë nevojë për leje për në SMS, Telefon dhe Kontakte."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Lejet mund t\'i ndryshosh te Cilësimet, Aplikacionet, Mesazhet, Lejet."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Lejet mund t\'i ndryshosh te Cilësimet, Aplikacionet, Mesazhet, Lejet."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Të shpeshta"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Të gjitha kontaktet"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Dërgoje te <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Regjistro fotografi ose video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Zgjidh imazhe nga kjo pajisje"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Regjistro audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Zgjidh foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Artikulli \"media\" u zgjodh."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Artikulli \"media\" nuk u zgjodh."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"U përzgjodhën <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"imazhi <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" msgid="8142175029053370662">"imazhi"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Regjistro audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Shpërnda"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Pikërisht tani"</string>
+ <string name="posted_now" msgid="867560789350406701">"Tani"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> minuta</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> minutë</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> orë</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> orë</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ditë</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ditë</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> javë</item>
+ <item quantity="one">një javë</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> muaj</item>
+ <item quantity="one">një muaj</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> vite</item>
+ <item quantity="one">një vit</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Mesazh i kategorisë 0"</string>
+ <string name="save" msgid="5081141452059463572">"Ruaj"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Në pajisje ka mbetur pak hapësirë e lirë. Mesazhet do të fshijnë automatikisht mesazhet e vjetra për të liruar hapësirë."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Hapësira ruajtëse po mbaron"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"\"Mesazhet\" mund të mos dërgojë ose pranojë mesazhe derisa të ketë hapësirë tjetër të lirë në pajisjen tënde."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Hapësirë e paktë ruajtëse për SMS-tw. Mund të të duhet të fshish mesazhe."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Konfirmo numrin tënd të telefonit"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Ky hap që kryhet vetëm një herë do të garantojë që Mesazhet t\'i dorëzojë siç duhet mesazhet e tua në grup."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Numri i telefonit"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Fshiji të gjitha mesazhet me klipe \"media\""</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Fshi mesazhe më të vjetra se <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Fshi automatikisht mesazhe më të vjetra se <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Shpërfill"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Të fshihen të gjitha mesazhet me klipe \"media\"?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Të fshihen mesazhet më të vjetra se <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Të fshihen mesazhet më të vjetra se <xliff:g id="DURATION">%s</xliff:g> dhe të aktivizohet fshirja automatike?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> tha"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Ti the"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mesazh nga <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Dërgove një mesazh"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Po dërgon..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Nuk u dërgua. Prek për të provuar sërish."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Nuk u dërgua. Po provon sërish…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Dërgo sërish ose fshi"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Bëj një telefonatë me zë drejt shërbimeve të urgjencës. Mesazhi yt me tekst nuk arriti të dërgohej këtë herë."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Dështoi"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Mesazh i ri MMS-je për të shkarkuar"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Mesazh i ri MMS-je"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nuk arriti ta shkarkonte"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Prek për të provuar sërish"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Prek për të shkarkuar"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Shkarko ose fshi"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Po shkarkon…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Mesazhi skadoi ose nuk mund të përdoret"</string>
+ <string name="mms_info" msgid="3402311750134118165">"madhësia: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, skadimi: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Nuk mund ta dërgojë. Marrësi është i pavlefshëm."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Shërbimi nuk është i aktivizuar në rrjet"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Nuk arriti ta dërgonte për shkak të një problemit në rrjet"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Mesazhi skadoi ose nuk mundësohet"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Pa subjekt)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Dërgues i panjohur"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Dorëzuar"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Nuk arriti të shkarkonte mesazhin <xliff:g id="SUBJECT">%1$s</xliff:g> nga <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Nuk arriti të përfundonte operacionin e bazës së të dhënave për shkak të kujtesës së pakët"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Mesazhi nuk është dërguar"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Disa mesazhe nuk janë dërguar te Mesazhet"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mesazhe në <xliff:g id="CONVERSATIONS">%d</xliff:g> bashkëbisedime</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mesazhe në një bashkëbisedim</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Mesazhi nuk u shkarkua"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Disa mesazhe nuk janë shkarkuar te Mesazhet"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> mesazhe në <xliff:g id="CONVERSATIONS">%d</xliff:g> bashkëbisedime</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> mesazhe në një bashkëbisedim</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Mesazhi për <xliff:g id="NUMBER">%1$s</xliff:g> nuk u dërgua"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Bëj një telefonatë me zë drejt shërbimeve të urgjencës. Mesazhi yt me tekst për <xliff:g id="NUMBER">%1$s</xliff:g> nuk arriti të dërgohej këtë herë."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> mesazhe të reja</item>
+ <item quantity="one">Mesazh i ri</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Fillo"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera nuk mund të përdoret"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera nuk mund të përdoret"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Regjistrimi i videos nuk mundësohet"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Nuk mund të ruajë materiale \"media\""</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Nuk mund të nxjerrë fotografi"</string>
+ <string name="back" msgid="1477626055115561645">"Prapa"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arkivuar"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Fshi"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arkivo"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Hiq nga arkiva"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Çaktivizo njoftimet"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Aktivizo njoftimet"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Shto kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Shkarko"</string>
+ <string name="action_send" msgid="377635240181672039">"Dërgo"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Fshi"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Të fshihet ky mesazh?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Ky veprim nuk mund të zhbëhet."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Fshi"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Fshiji këto biseda?</item>
+ <item quantity="one">Fshije këtë bisedë?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Fshi"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Anulo"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Për"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Përzgjidh shumë imazhe"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Konfirmo përzgjedhjen"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Nuk mund të regjistrojë audio. Provo sërish."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Nuk mund të luajë audio. Provo sërish."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Nuk arriti ta ruante audion. Provo sërish."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Prek dhe mbaj shtypur"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Fotografi"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Klip audioje"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kartë kontakti"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Shkarko"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Përgjigju përmes SMS-së"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Përgjigju përmes MMS-së"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Përgjigju"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> pjesëmarrës</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> pjesëmarrës</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Unë"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt i bllokuar dhe i arkivuar"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakti u zhbllokua dhe arkivimi i tij u anulua"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> të arkivuara"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> të paarkivuara"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Njoftimet janë të çaktivizuara"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Njoftimet janë të aktivizuara"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Gjithçka gati. Prek sërish \"Dërgo\"."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"\"Mesazhet\" u caktua me sukses si aplikacioni me parazgjedhje i SMS-ve."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Hiq bashkëngjitjet</item>
+ <item quantity="one">Hiq bashkëngjitjen</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Bashkëngjitja audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Luaj bashkëngjitjen në audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pauzë"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mesazh nga <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Mesazh i dështuar nga <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mesazh nga <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Mesazh i padërguar për <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Po dërgon mesazh te <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Mesazh i dështuar për <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mesazh për <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Mesazh i dështuar nga <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mesazh nga <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Mesazh i padërguar për <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Po dërgon mesazh te <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Mesazh i dështuar për <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mesazh për <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Ora: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Mesazhi dështoi. Prek për ta provuar sërish."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Bisedë me <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Fshi subjektin"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Regjistro video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Regjistro një pamje të palëvizshme"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Nxirr fotografi"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Fillo regjistrimin në video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Kalo në kamerën me ekran të plotë"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Ndërro mes kamerës para dhe mbrapa"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Ndalo regjistrimin dhe bashkëngjit video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Ndalo regjistrimin e videos"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Foto me mesazhe"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotografi u ruajtën tek albumi \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> fotografi u ruajt tek albumi \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video u ruajtën tek albumi \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video u ruajt tek albumi \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> bashkëngjitje u ruajtën tek albumi\"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bashkëngjitje u ruajt tek albumi \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bashkëngjitje u ruajtën tek \"Shkarkimet\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bashkëngjitje u ruajt tek \"Shkarkimet\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bashkëngjitje u ruajtën</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bashkëngjitje u ruajt</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bashkëngjitje nuk mund të ruheshin</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bashkëngjitje nuk mund të ruhej</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Bashkëgjitje MMS e ruajtur"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Cilësimet"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arkivuar"</string>
+ <string name="action_close" msgid="1840519376200478419">"Mbyll"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Të përparuara"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Korrigjo"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Njoftime"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Zëri"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Hesht"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Dridh"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"I bllokuar"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Raportet e dorëzimit të SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Kërko një raport dorëzimi për çdo SMS që dërgon"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Gjej automatikisht"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Gjej automatikisht mesazhe MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Gjetje automatike në roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Merr automatikisht mesazhin MMS kur je në roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Mesazhe në grup"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Përdor \"MMS\" për të dërguar një mesazh të vetëm kur ka shumë marrës"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Aplikacioni me parazgjedhje i SMS-ve"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Aplikacioni me parazgjedhje i SMS-ve"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Numri yt i telefonit"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"E panjohur"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Tingujt e mesazheve dalëse"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Hidh SMS-të"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Hidhi të dhënat SMS të papërpunuara në skedarin e hapësirës së jashtme ruajtëse"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Hidh MMS-të"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Hidhi të dhënat MMS të papërpunuara në skedarin e hapësirës së jashtme ruajtëse"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Sinjalizimi pa tel"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Opsionet e mesazhit"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopjo tekstin"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Shiko detajet"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Fshi"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Përpara"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Detajet e mesazhit"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Shkruaj: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Mesazh me tekst"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mesazh multimedial"</string>
+ <string name="from_label" msgid="1947831848146564875">"Nga: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Për: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Dërguar: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Marrë: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Subjekti: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Madhësia: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Përparësia: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"Karta SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"E lartë"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normale"</string>
+ <string name="priority_low" msgid="7398724779026801851">"E ulët"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"Numri i kartës SIM: <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Adresë e fshehur e dërguesit"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Nuk mund të dërgojë mesazh kur ngarkon bashkëngjitje."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Nuk mund ta ngarkojë bashkëngjitjen. Provo sërish."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Rrjeti nuk është gati. Provo përsëri."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Fshi tekstin"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Kalo mes futjes së tekstit dhe numrave"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Shto më shumë pjesëmarrës"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Konfirmo pjesëmarrësit"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Fillo bisedë të re"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Përzgjidh këtë artikull"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Luaj video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Kontaktet dhe opsionet"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Korrigjo"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Kontaktet dhe opsionet"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Të përgjithshme"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Kontakte në këtë bisedë"</string>
+ <string name="action_call" msgid="6596167921517350362">"Bëj telefonatë"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Dërgo mesazh"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Dërgo mesazh&lt;br/&gt;&lt;small&gt;nga <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Dërgo fotografitë</item>
+ <item quantity="one">Dërgo fotografinë</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Dërgo audiot</item>
+ <item quantity="one">Dërgo audion</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Dërgo videot</item>
+ <item quantity="one">Dërgo videon</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Dërgo kartat e kontaktit</item>
+ <item quantity="one">Dërgo kartën e kontaktit</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Dërgo bashkëngjitjet</item>
+ <item quantity="one">Dërgo bashkëngjitjen</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> bashkëngjitje gati për dërgim</item>
+ <item quantity="one">Një bashkëngjitje gati për dërgim</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Dërgo përshtypjet"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Shiko në Dyqanin \"Luaj me Google\""</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Informacion mbi versionin"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Versioni %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licenca me burim informacioni të hapur"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Njoftime"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"U arrit kufiri i bashkëngjitjeve"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Dështoi në ngarkimin e bashkëngjitjes."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Shto te kontaktet?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Shto kontakt"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subjekti"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subjekti: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Po ngarkon kartën e kontaktit"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Nuk arriti të ngarkonte kartën e kontakteve"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Shiko kartën e kontaktit"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakte</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kartat e kontakteve"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Ditëlindje"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Shënime"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Transfero mesazhin"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Përgjigju"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Funksioni \"SMS\" u çaktivizua"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Për të dërguar, caktoje \"Mesazhet\" si aplikacionin me parazgjedhje të SMS-ve"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Caktoje \"Mesazhet\" si aplikacionin me parazgjedhje të SMS-ve"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Ndrysho"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Për të pranuar mesazhe, caktoje \"Mesazhet\" si aplikacionin me parazgjedhje të SMS-ve"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Nuk u përzgjodh kartë e preferuar SIM për dërgimin e mesazheve SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Ky aplikacion nuk lejohet nga zotëruesi i pajisjes."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Në rregull!"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Shumë pjesëmarrës në një bisedë"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Kontakte të pavlefshme</item>
+ <item quantity="one">Kontakt i pavlefshëm</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Nuk arriti të ngarkonte pamjen e kamerës"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ti: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draft"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Pasi të fillosh një bisedë të re, do ta shikosh të listuar këtu"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Bisedat e arkivuara shfaqen këtu"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Po ngarkon bisedat…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Fotografi"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Klip audioje"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kartë kontakti"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Zhbëj"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Provo përsëri"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Fut një emër kontakti ose numër telefoni për të nisur një mesazh të ri"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blloko"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blloko <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Zhblloko <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Të bllokohet <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Do të vazhdosh të marrësh mesazhe nga ky numër, por nuk do të njoftohesh më. Kjo bisedë do të arkivohet."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Kontaktet e bllokuara"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ZHBLLOKO"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Kontaktet e bllokuara"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Zgjidh imazh nga biblioteka e dokumenteve"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Po dërgon mesazhin"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Mesazhi u dërgua"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Të dhënat celulare janë të çaktivizuara. Kontrollo cilësimet."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Nuk mund të dërgojë mesazhe në modalitetin \"në aeroplan\""</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Mesazhi nuk u arrit të dërgohej"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Mesazhi u shkarkua"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Të dhënat celulare janë të çaktivizuara. Kontrollo cilësimet."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Nuk mund të shkarkojë mesazhe në modalitetin \"në aeroplan\""</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Mesazhi nuk u arrit të shkarkohej"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Një"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dy"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tre"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Katër"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Pesë"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Gjashtë"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Shtatë"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Tetë"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nëntë"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Mesazhi nuk mund të dërgohet me <xliff:g id="CARRIERNAME">%1$s</xliff:g> - gabimi <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Mesazhi nuk mund të dërgohet me një operator celular të panjohur - gabimi <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Transfero: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Mesazhi nuk u dërgua - shërbimi nuk është i aktivizuar në rrjet"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Mesazhi nuk u dërgua - adresë e pavlefshme vendmbërritje"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Mesazhi nuk u dërgua - mesazh i pavlefshëm"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Mesazhi nuk u dërgua - përmbajtje e pambështetur"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Mesazhi nuk u dërgua - mesazh i pambështetur"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Mesazhi nuk u dërgua: shumë i madh"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Mesazh i ri"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Shiko"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Imazh"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Nuk arriti të gjente një aplikacion të përshtatshëm"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Hiq marrësin"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Mesazh i ri"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Anulo"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Redakto pikën e qasjes"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Nuk është vendosur"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Emri"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN-ja"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC-ja"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Përfaqësuesi i MMS-ve"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Porta e MMS-ve"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC-ja"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Lloji i APN-së"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Fshi APN-në"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN e re"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Ruaj"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Injoro"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Fusha \"Emri\" nuk të lihet bosh."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN-ja nuk mund të lihet bosh."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Fusha e MCC-së duhet të ketë 3 shifra."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Fusha e MNC-së duhet të ketë 2 ose 3 shifra."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Po restauron cilësimet e APN-së me parazgjedhje."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Rivendos në parazgjedhje"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Rivendosja e cilësimeve të APN-së me parazgjedhje përfundoi."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Pa titull"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Emrat e Pikës së Qasjes"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN-të"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN e re"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Cilësimet e Emrit të Pikës së Qasjes nuk mund të përdoren për këtë përdorues"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Të kopjohet në kujtesën e fragmenteve"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopjo"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"për <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Të përgjithshme"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Të përparuara"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Cilësimet e përgjithshme"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Cilësimet e përparuara"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"Emri i kartës SIM: \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Dërgoju mesazhe SMS të gjithë marrësve. Vetëm ti do të marrësh përgjigje"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Dërgoju një MMS të vetme, të gjithë marrësve"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Numër i panjohur"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Mesazh i ri."</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Mesazh i ri."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Përzgjedhësi i kartës SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> i përzgjedhur, përzgjedhësi i kartës SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Redakto subjektin"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Përzgjidh kartën SIM ose redakto subjektin"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Prek dhe mbaj shtypur për të regjistruar audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Fillo bisedë të re"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mesazhet"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Lista e Mesazheve"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mesazhet"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Mesazh i ri"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Lista e bisedave"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Po ngarkon bisedat"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Po ngarkon mesazhet"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Shiko më shumë biseda"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Shiko më shumë mesazhe"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Biseda është fshirë"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Biseda u fshi. Prek për të shfaqur një bisedë tjetër te \"Mesazhet\""</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"I bllokuar"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"I zhbllokuar"</string>
+ <string name="db_full" msgid="8459265782521418031">"Hapësira ruajtëse është e vogël. Disa të dhëna mund të humbin."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Përzgjidh bashkëngjitje"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Konfirmo përzgjedhjen"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"U përzgjodhën <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Hiq një ose më shumë bashkëngjitje dhe provo sërish."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Mund të provosh ta dërgosh mesazhin, por mund të mos dërgohet pa hequr një ose disa bashkëngjitje."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Mund të dërgosh vetëm një video për çdo mesazh. Hiqi videot shtesë dhe provo përsëri."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Mesazhet dështuan në ngarkimin e bashkëngjitjes."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Dërgoje gjithsesi"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Nuk arriti të fillonte bisedën"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> u zgjodh"</string>
+</resources>
diff --git a/res/values-sr/arrays.xml b/res/values-sr/arrays.xml
new file mode 100644
index 0000000..f099488
--- /dev/null
+++ b/res/values-sr/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"без наслова"</item>
+ <item msgid="272485471009191934">"нема наслова"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Да"</item>
+ <item msgid="6049132459802288033">"Не"</item>
+ <item msgid="3084376867445867895">"Потврди"</item>
+ <item msgid="3155097332660174689">"Хе хе"</item>
+ <item msgid="2611328818571146775">"Хвала"</item>
+ <item msgid="4881335087096496747">"Слажем се"</item>
+ <item msgid="2422296858597420738">"Фино"</item>
+ <item msgid="4805581752819452687">"Стижем ускоро"</item>
+ <item msgid="4746700499431366214">"Добро, јавићу ти се касније"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..7155cd9
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,545 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Размена порука"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Размена порука"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Изаберите преписку"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Подешавања"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Пошаљи поруку"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Додај прилог"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Помоћ"</string>
+ <string name="welcome" msgid="2857560951820802321">"Добро дошли"</string>
+ <string name="skip" msgid="7238879696319945853">"Прескочи"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Следеће &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Даље"</string>
+ <string name="exit" msgid="1905187380359981199">"Изађи"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Подешавања &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Подешавања"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Размена порука тражи дозволу за SMS, телефон и контакте."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Можете да мењате дозволе у одељку Подешавања &gt; Апликације &gt; Размена порука &gt; Дозволе."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Можете да мењате дозволе у одељку Подешавања, Апликације, Размена порука, Дозволе."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Често контактирани"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Сви контакти"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Пошаљи на <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Сними слику или видео снимак"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Избор слика са овог уређаја"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Сними звук"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Изаберите слику"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Изабрали сте медијски садржај."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Опозвали сте избор медијског садржаја."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Изабраних: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"слика <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" msgid="8142175029053370662">"слика"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Снимање звука"</string>
+ <string name="action_share" msgid="2143483844803153871">"Дели"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Управо сад"</string>
+ <string name="posted_now" msgid="867560789350406701">"Сада"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> мин</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> мин</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> мин</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> сат</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> сата</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> сати</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> дан</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> дана</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> дана</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> недеље</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> недеље</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> недеља</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> месеца</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> месеца</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> месеци</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> године</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> године</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> година</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Порука класе 0"</string>
+ <string name="save" msgid="5081141452059463572">"Сачувај"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Понестаје простора на уређају. Апликација за размену порука ће аутоматски обрисати старије поруке да би ослободила простор."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Понестаје складишног простора"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Размена порука можда неће моћи да шаље нити прима поруке док још простора не буде доступно на уређају."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Понестаје меморије за SMS. Можда ћете морати да избришете поруке."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Потврдите број телефона"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Овим једнократним кораком обезбеђујете да Размена порука правилно испоручује групне поруке."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Број телефона"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Избриши све поруке са медијским садржајем"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Избриши поруке старије од <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Аутоматски бриши поруке старије од <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Игнориши"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Желите ли да избришете све поруке са медијским садржајем?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Желите ли да избришете поруке старије од <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Желите ли да избришете поруке старије од <xliff:g id="DURATION">%s</xliff:g> и да укључите аутоматско брисање?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> каже"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Рекли сте"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Порука од корисника <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Послали сте поруку"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Шаље се..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Слање није успело. Додирните да бисте покушали поново."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Слање није успело. Покушавамо поново…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Пошаљите поново или избришите"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Упутите гласовни позив службама за хитне случајеве. Слање SMS-а тренутно није успело."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Није успело"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Нова MMS порука за преузимање"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Нова MMS порука"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Преузимање није успело"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Додирните да бисте покушали поново"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Додирните да бисте преузели"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Преузмите или избришите"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Преузима се..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Порука је истекла или није доступна"</string>
+ <string name="mms_info" msgid="3402311750134118165">"величина: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, истиче: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Слање није могуће. Прималац није важећи."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Услуга није активирана у мрежи"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Слање није могуће због проблема са мрежом"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Порука је истекла или није доступна"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Без наслова)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Непознати пошиљалац"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Испоручено"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Није могуће преузети поруку <xliff:g id="SUBJECT">%1$s</xliff:g> пошиљаоца <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Није могуће довршити операцију у вези са базом података због недостатка меморије"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Порука није послата"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Неке поруке нису послате у Размени порука"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> поруке(а) у <xliff:g id="CONVERSATIONS">%d</xliff:g> конверзацији</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> поруке(а) у <xliff:g id="CONVERSATIONS">%d</xliff:g> конверзације</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> поруке(а) у <xliff:g id="CONVERSATIONS">%d</xliff:g> конверзација</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Порука није преузета"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Неке поруке нису преузете у Размени порука"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> поруке(а) у <xliff:g id="CONVERSATIONS">%d</xliff:g> конверзацији</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> поруке(а) у <xliff:g id="CONVERSATIONS">%d</xliff:g> конверзације</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> поруке(а) у <xliff:g id="CONVERSATIONS">%d</xliff:g> конверзација</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Порука на <xliff:g id="NUMBER">%1$s</xliff:g> није послата"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Упутите гласовни позив службама за хитне случајеве. Слање SMS-а на <xliff:g id="NUMBER">%1$s</xliff:g> тренутно није успело."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> нова порука</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> нове поруке</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> нових порука</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Покрени"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Камера није доступна"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Камера није доступна"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Снимање видео снимака није доступно"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Чување медија није могуће"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Није успело снимање слике"</string>
+ <string name="back" msgid="1477626055115561645">"Назад"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архивирано"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Избриши"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архивирај"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Опозови архивирање"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Искључи обавештења"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Укључи обавештења"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Додај контакт"</string>
+ <string name="action_download" msgid="7786338136368564146">"Преузми"</string>
+ <string name="action_send" msgid="377635240181672039">"Пошаљи"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Избриши"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Желите да избришете ову поруку?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Ова радња не може да се опозове."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Избриши"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Желите ли да избришете ове конверзације?</item>
+ <item quantity="few">Желите ли да избришете ове конверзације?</item>
+ <item quantity="other">Желите ли да избришете ове конверзације?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Избриши"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Откажи"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Прима"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Изаберите више слика"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Потврди избор"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Снимање звука није успело. Покушајте поново."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Пуштање звука није успело. Покушајте поново."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Чување звука није успело. Покушајте поново."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Додирните и задржите"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Слика"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудио исечак"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Видео"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Картица контакта"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Преузми"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Одговори преко SMS-а"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Одговори MMS-ом"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Одговорите"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> учесник</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> учесника</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> учесника</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ја"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Контакт је блокиран и архивиран"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Контакт је деблокиран и архивирање је опозвано"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Архивирано: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Опозвано је архивирање за: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Обавештења су искључена"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Обавештења су укључена"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Све је спремно. Додирните Пошаљи поново."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Размена порука је подешена као подразумевана апликација за SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Одбаци прилоге</item>
+ <item quantity="few">Одбаци прилоге</item>
+ <item quantity="other">Одбаци прилоге</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Аудио прилог"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Пусти аудио прилог"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Пауза"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> вам шаље нову поруку: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Није примљена порука од корисника <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Порука од корисника <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Порука није послата кориснику <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Шаље се порука кориснику <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Није успело слање поруке кориснику <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Порука кориснику <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Није примљена порука од корисника <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Порука од корисника <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Порука која није послата групи <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Шаље се порука групи <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Није успело слање поруке групи <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Порука групи <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Време: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Слање поруке није успело. Додирните да бисте поново покушали."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Конверзација са учесницима <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Брисање наслова"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Снимите видео"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Снимите слику"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Снимите слику"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Почните да снимате видео"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Пређите на камеру целог екрана"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Пребацујте са предње на задњу камеру и обрнуто"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Заустави снимање и приложи видео"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Заустави снимање звука"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Слике из Размене порука"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> слика је сачувана у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> слике су сачуване у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> слика је сачувано у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> видео је сачуван у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> видео снимка су сачувана у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> видео снимака је сачувано у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> прилог је сачуван у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> прилога су сачувана у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> прилога је сачувано у албуму „<xliff:g id="ALBUMNAME_3">%s</xliff:g>“</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> прилог је сачуван у директоријуму „Преузимања“</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> прилога су сачувана у директоријуму „Преузимања“</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прилога је сачувано у директоријуму „Преузимања“</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> прилог је сачуван</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> прилога су сачувана</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> прилога је сачувано</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Нисмо успели да сачувамо <xliff:g id="QUANTITY_1">%d</xliff:g> прилог</item>
+ <item quantity="few">Нисмо успели да сачувамо <xliff:g id="QUANTITY_1">%d</xliff:g> прилога</item>
+ <item quantity="other">Нисмо успели да сачувамо <xliff:g id="QUANTITY_1">%d</xliff:g> прилога</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Сачувани прилог MMS-а"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Подешавања"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архивирано"</string>
+ <string name="action_close" msgid="1840519376200478419">"Затвори"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Напредно"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Отклањање грешака"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Обавештења"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Звук"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Нечујно"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Вибрација"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Блокирано"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Извештаји о испоруци SMS-ова"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Захтевајте извештај о испоруци за сваки послати SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Аутоматско преузимање"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Аутоматско преузимање MMS порука"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Аутоматски преузимај у ромингу"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Аутоматски преузимај MMS у ромингу"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Групна размена порука"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Користите MMS за слање једне поруке када постоји више прималаца"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Подразумевана апликација за SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Подразумевана апликација за SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Број телефона"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Непознато"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Звукови за одлазне поруке"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Направи сирову копију SMS-а"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Направи сирову копију необрађених података примљеног SMS-а у датотеци спољног складишног простора"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Направи сирову копију MMS-а"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Направи сирову копију необрађених података примљеног MMS-а у датотеци спољног складишног простора"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Обавештења преко мобилне мреже"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Опције поруке"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Копирај текст"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Погледајте детаље"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Избриши"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Проследи"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Детаљи поруке"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Тип: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Мултимедијална порука"</string>
+ <string name="from_label" msgid="1947831848146564875">"Од: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Коме: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Послато: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Примљено: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Наслов: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Величина: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Приоритет: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM картица: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Висок"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Нормалан"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Низак"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"Отвор за SIM картицу <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Скривена адреса пошиљаоца"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Не можете да пошаљете поруку док се прилози учитавају."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Учитавање прилога није успело. Покушајте поново."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Мрежа није спремна. Покушајте поново."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Избриши текст"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Пребацивање између уноса текста и бројева"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Додај још учесника"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Потврди учеснике"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Започни нову конверзацију"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Изаберите ову ставку"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Пусти видео"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Људи и опције"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Отклони грешке"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Људи и опције"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Опште"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Људи у овој конверзацији"</string>
+ <string name="action_call" msgid="6596167921517350362">"Позови"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Пошаљите поруку"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Пошаљите поруку&lt;br/&gt;&lt;small&gt;са картице <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Пошаљите слике</item>
+ <item quantity="few">Пошаљите слике</item>
+ <item quantity="other">Пошаљите слике</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Пошаљите аудио снимке</item>
+ <item quantity="few">Пошаљите аудио снимке</item>
+ <item quantity="other">Пошаљите аудио снимке</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Пошаљите видео снимке</item>
+ <item quantity="few">Пошаљите видео снимке</item>
+ <item quantity="other">Пошаљите видео снимке</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Пошаљите картице контаката</item>
+ <item quantity="few">Пошаљите картице контаката</item>
+ <item quantity="other">Пошаљите картице контаката</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Пошаљите прилоге</item>
+ <item quantity="few">Пошаљите прилоге</item>
+ <item quantity="other">Пошаљите прилоге</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прилог је спреман за слање</item>
+ <item quantity="few"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прилога су спремна за слање</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> прилога је спремно за слање</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Пошаљи повратне информације"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Прикажи у Google Play продавници"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Информације о верзији"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Верзија %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Лиценце отвореног кода"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Обавештења"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Ограничење за прилоге је достигнуто"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Учитавање прилога није успело."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Желите ли да додате у контакте?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Додај контакт"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Наслов"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Наслов: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Учитава се картица контакта"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Учитавање картице контакта није успело"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Погледајте картицу контакта"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> контакт</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> контакта</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> контаката</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Картице контаката"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Рођендан"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Белешке"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Проследите поруку"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Одговорите"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS је онемогућен"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Да бисте слали поруке, подесите Размену порука као подразумевану апликацију за SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Подесите Размену порука као подразумевану апликацију за SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Промени"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Да бисте примали поруке, подесите Размену порука као подразумевану апликацију за SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Није изабран жељени SIM за слање SMS-ова"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Власник уређаја не дозвољава ову апликацију."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Потврди"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Превише учесника у конверзацији"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Неважећи контакти</item>
+ <item quantity="few">Неважећи контакти</item>
+ <item quantity="other">Неважећи контакти</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Учитавање слике са камере није успело"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ви: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Недовршена порука"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Када започнете нову конверзацију, видећете је наведену овде"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Овде се појављују архивиране преписке"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Преписке се учитавају..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Слика"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудио клип"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Видео"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Картица контакта"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Опозови"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Покушај поново"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Унесите име или број телефона контакта да бисте започели нову поруку"</string>
+ <string name="action_block" msgid="9032076625645190136">"Блокирај"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Блокирај <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Деблокирај <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Желите ли да блокирате <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"И даље ћете примати поруке са овог броја, али нећете више добијати обавештења. Ова преписка ће бити архивирана."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Блокирани контакти"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ДЕБЛОКИРАЈ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Блокирани контакти"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Изаберите слику из библиотеке докумената"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Порука се шаље"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Порука је послата"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Подаци за мобилне уређаје су искључени. Проверите подешавања."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Слање порука није могуће у Режиму рада у авиону"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Порука није послата"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Порука је преузета"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Подаци за мобилне уређаје су искључени. Проверите подешавања."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Није могуће преузимати поруке у режиму рада у авиону"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Преузимање поруке није успело"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Нула"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Један"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Два"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Три"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Четири"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Пет"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Шест"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Седам"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Осам"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Девет"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Није могуће послати поруку помоћу мобилног оператера <xliff:g id="CARRIERNAME">%1$s</xliff:g>, грешка <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Није могуће послати поруку помоћу непознатог мобилног оператера, грешка <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Порука није послата: услуга није активирана на мрежи"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Порука није послата: неважећа одредишна адреса"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Порука није послата: неважећа порука"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Порука није послата: неподржани садржај"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Порука није послата: неподржана порука"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Порука није послата: превелика је"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Нова порука"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Прикажи"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Слика"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Проналажење одговарајуће апликације није успело"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Уклањање примаоца"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Нова порука"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Откажи"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Измените приступну тачку"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Није подешено"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Назив"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Назив приступне тачке"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS прокси"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS порт"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Тип назива приступне тачке"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Избриши назив приступне тачке"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Нов назив приступне тачке"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Сачувај"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Одбаци"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Поље Назив не сме да буде празно."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Поље Назив приступне тачке не сме да буде празно."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Поље MCC мора да садржи 3 цифре."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Поље MNC мора да садржи 2 или 3 цифре."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Враћање подразумеваних подешавања назива приступне тачке."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Ресетуј подразумеване вредности"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Ресетовање подразумеваних подешавања назива приступне тачке је довршено."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Ненасловљено"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Називи приступних тачака"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Називи приступних тачака"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Нов назив приступне тачке"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Подешавања назива приступне тачке нису доступна за овог корисника"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Желите ли да копирате у привремену меморију?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Копирај"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"на картицу <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Опште"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Напредно"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Општа подешавања"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Напредна подешавања"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"„<xliff:g id="SIM_NAME">%s</xliff:g>“ SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Шаљите појединачне SMS поруке свим примаоцима. Само ви ћете добијати одговоре"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Пошаљите један MMS свим примаоцима"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Непознат број"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Нова порука"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Нова порука."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Бирач SIM картице"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Изабран је <xliff:g id="SIM_0">%1$s</xliff:g>, бирач SIM картица"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Измени наслов"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Изабери SIM или измени наслов"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Додирните и задржите за снимање звука"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Започни нову конверзацију"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Размена порука"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Листа из Размене порука"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Размена порука"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Нова порука"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Листа преписки"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Учитавају се конверзације"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Учитавају се поруке"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Прикажи још конверзација"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Прикажи још порука"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Конверзација је избрисана"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Конверзација је избрисана. Додирните да бисте приказали неку другу конверзацију Размене порука"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Блокирано"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Деблокирано"</string>
+ <string name="db_full" msgid="8459265782521418031">"Нема довољно меморијског простора. Неки подаци ће можда бити изгубљени."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Изабери прилоге"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Потврди избор"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Изабраних: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Уклоните један прилог или више њих и покушајте поново."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Можете да покушате да пошаљете поруку, али можда неће бити испоручена ако не уклоните један прилог или више њих."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Можете да пошаљете само један видео по поруци. Уклоните друге видео снимке и покушајте поново."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Размена порука није успела да учита прилог."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Ипак пошаљи"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Започињање преписке није успело"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Изабрали сте <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml
new file mode 100644
index 0000000..8f03e4b
--- /dev/null
+++ b/res/values-sv/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"inget ämne"</item>
+ <item msgid="272485471009191934">"inget ämne"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ja"</item>
+ <item msgid="6049132459802288033">"Nej"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Tack"</item>
+ <item msgid="4881335087096496747">"Jag accepterar"</item>
+ <item msgid="2422296858597420738">"Nice"</item>
+ <item msgid="4805581752819452687">"På väg"</item>
+ <item msgid="4746700499431366214">"OK, jag återkommer senare"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..36ad85c
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Sms/mms"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Sms/mms"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Välj konversation"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Inställningar"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Skicka meddelande"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Lägg till en bilaga"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Hjälp"</string>
+ <string name="welcome" msgid="2857560951820802321">"Välkommen"</string>
+ <string name="skip" msgid="7238879696319945853">"Hoppa över"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Nästa &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Nästa"</string>
+ <string name="exit" msgid="1905187380359981199">"Stäng"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Inställningar &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Inställningar"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Meddelanden behöver ha åtkomst till Sms, Telefon och Kontakter."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Du kan ändra behörigheterna i Inställningar &gt; Appar &gt; Meddelanden &gt; Behörigheter."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Du kan ändra behörigheterna i Inställningar, Appar, Meddelanden, Behörigheter."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Frekventa"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Alla kontakter"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Skicka till <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Ta bilder eller spela in video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Välj bilder från den här enheten"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Spela in ljud"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Välj foto"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Video-/bildobjektet har markerats."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Video-/bildobjektet har avmarkerats."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> har valts"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"bild <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" msgid="8142175029053370662">"bild"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Spela in ljud"</string>
+ <string name="action_share" msgid="2143483844803153871">"Dela"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Nyss"</string>
+ <string name="posted_now" msgid="867560789350406701">"Nu"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> timmar</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> timme</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dagar</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dag</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> veckor</item>
+ <item quantity="one">en vecka</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> månader</item>
+ <item quantity="one">en månad</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> år</item>
+ <item quantity="one">ett år</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Meddelande, klass 0"</string>
+ <string name="save" msgid="5081141452059463572">"Spara"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Det finns ont om utrymme på enheten. Om du skickar och tar emot sms kommer äldre sms att raderas för att göra plats."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Lagringsutrymmet börjar ta slut"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Du kan behöva frigöra utrymme på enheten för att kunna skicka eller ta emot sms med Meddelanden."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Lagringsutrymmet för sms är fullt. Du kan behöva ta bort sms."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Bekräfta ditt telefonnummer"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Genom detta engångssteg säkerställer du att dina gruppmeddelanden levereras som de ska med Meddelanden."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefonnummer"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Ta bort alla meddelanden med mediefiler"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Ta bort meddelanden som är äldre än <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Ta automatiskt bort meddelanden som är äldre än <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ignorera"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Vill du ta bort alla meddelanden med media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Vill du ta bort meddelanden som är äldre än <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Vill du ta bort meddelanden som är äldre än <xliff:g id="DURATION">%s</xliff:g> och aktivera automatisk borttagning?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> sade"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Du sade"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Meddelande från <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Du har skickat ett meddelande"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Skickar …"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Det gick inte att skicka. Tryck om du vill försöka igen."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Det gick inte att skicka. Försöker igen ..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Skicka igen eller ta bort"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Ring larmcentralen. Det gick inte att skicka ett sms just nu."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Misslyckades"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Nya MMS att ladda ned"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Nytt MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Nedladdningen genomfördes inte"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tryck om du vill försöka igen"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Ladda ned genom att trycka"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Ladda ned eller radera"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Hämtar ..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Meddelandet har upphört att gälla eller är inte tillgängligt"</string>
+ <string name="mms_info" msgid="3402311750134118165">"storlek: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, finns kvar till: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Det gick inte att skicka. Mottagaren är inte giltig."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Tjänsten är inte aktiverad i nätverket"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Det gick inte att skicka på grund av nätverksproblem"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Meddelandet har upphört att gälla eller är inte tillgängligt."</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Inget ämne)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Okänd avsändare"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Levererat"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Det gick inte att ladda ned meddelandet <xliff:g id="SUBJECT">%1$s</xliff:g> från <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Det gick inte att slutföra åtgärden i databasen på grund av för låg minneskapacitet"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Meddelandet har inte skickats"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Vissa meddelanden skickades inte i Meddelanden"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> meddelanden i <xliff:g id="CONVERSATIONS">%d</xliff:g> konversationer</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> meddelanden i en konversation</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Meddelandet har inte laddats ned"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Vissa meddelanden laddades inte ned i Meddelanden"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> meddelanden i <xliff:g id="CONVERSATIONS">%d</xliff:g> konversationer</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> meddelanden i en konversation</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Meddelandet till <xliff:g id="NUMBER">%1$s</xliff:g> skickades inte"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Ring larmcentralen. Det gick inte att skicka ett sms till <xliff:g id="NUMBER">%1$s</xliff:g> just nu."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> nya meddelanden</item>
+ <item quantity="one">Nytt meddelande</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Börja"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kameran är inte tillgänglig"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kameran är inte tillgänglig"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Det går inte att spela in video"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Det går inte att spara media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Det går inte att fota"</string>
+ <string name="back" msgid="1477626055115561645">"Föregående"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arkiverat"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Ta bort"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arkivera"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Ångra arkivering"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Inaktivera aviseringar"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Aktivera aviseringar"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Lägg till kontakt"</string>
+ <string name="action_download" msgid="7786338136368564146">"Ladda ned"</string>
+ <string name="action_send" msgid="377635240181672039">"Skicka"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Ta bort"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Vill du ta bort meddelandet?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Åtgärden kan inte ångras."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Ta bort"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Vill du ta bort dessa konversationer?</item>
+ <item quantity="one">Vill du ta bort konversationen?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Ta bort"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Avbryt"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Till"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Markera flera bilder"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Bekräfta ditt val"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Det gick inte att spela in ljud. Försök igen."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Det gick inte att spela upp ljudet. Försök igen."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Det gick inte att spara ljud. Försök igen."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Tryck länge"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Bild"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Ljudklipp"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Visitkort"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Ladda ned"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Svara via sms"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Svara via mms"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Svara"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> deltagare</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> deltagare</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Jag"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakten har blockerats och arkiverats"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakten har återaktiverats och hämtats från arkivet"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> har arkiverats"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> arkiveringar har ångrats"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Aviseringar har inaktiverats"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Aviseringar har aktiverats"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Allt klart. Tryck på Skicka igen."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Meddelanden har angetts som standardapp för sms."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Släng bilagor</item>
+ <item quantity="one">Släng bilaga</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Ljudbilaga"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Spela upp ljudbilaga"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Pausa"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Meddelande från <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Meddelande som inte kunde skickas från <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Meddelande från <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Meddelande som inte har skickats till <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Skickar meddelande till <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Meddelande som inte kunde skickas till <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Meddelande till <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Meddelande som inte kunde skickas från <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Meddelande från <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Meddelande som inte har skickats till <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Skickar meddelande till <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Meddelande som inte kunde skickas till <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Meddelande till <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Tidpunkt: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Det här meddelandet gick inte att skicka. Tryck om du vill försöka igen."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Samtal med <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Radera ämne"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Spela in video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Ta en stillbild"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Ta bild"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Börja spela in video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Byt till fullskärmskamera"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Växla mellan främre och bakre kamera"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Stoppa inspelningen och bifoga en video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Sluta spela in video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Skicka foton med Meddelanden"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> foton har sparats i albumet <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> foto har sparats i albumet <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> videor har sparats i albumet <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video har sparats i albumet <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> bilagor har sparats i albumet <xliff:g id="ALBUMNAME_3">%s</xliff:g></item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bilaga har sparats i albumet <xliff:g id="ALBUMNAME_1">%s</xliff:g></item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bilagor har sparats i Nedladdningar</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bilaga har sparats i Nedladdningar</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> bilagor sparas</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> bilaga har sparats</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Det gick inte att spara <xliff:g id="QUANTITY_1">%d</xliff:g> bilagor</item>
+ <item quantity="one">Det gick inte att spara <xliff:g id="QUANTITY_0">%d</xliff:g> bilaga</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Mms-bilaga har sparats"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Inställningar"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arkiverat"</string>
+ <string name="action_close" msgid="1840519376200478419">"Stäng"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"Mms"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Avancerat"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Felsökning"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Aviseringar"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ljud"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Tyst"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Vibration"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Blockerad"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Sms-leveransrapporter"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Begär en leveransrapport för varje sms du skickar"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Automatisk hämtning"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Hämta mms-meddelanden automatiskt"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Autohämtning vid roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Hämta mms automatiskt vid roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Gruppmeddelanden"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Använd mms för att skicka ett meddelande när det finns flera mottagare"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Standardapp för sms"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Standardapp för sms"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Ditt telefonnummer"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Okänt"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Ljud vid utgående meddelande"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Dumpa sms"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Dumpa rådata för mottagna sms i en extern lagringsfil"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Dumpa mms"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Dumpa rådata för mottagna mms i en extern lagringsfil"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Trådlösa varningar"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Meddelandealternativ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopiera text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Visa mer information"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Ta bort"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Vidarebefordra"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Meddelandeinformation"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Typ: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Sms"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Mms"</string>
+ <string name="from_label" msgid="1947831848146564875">"Från: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Till: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Skickat: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Mottaget: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Ämne: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Storlek: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Prioritet: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Hög"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Låg"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Avsändaradress dold"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Kan inte skicka meddelandet samtidigt som bilagor läses in."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Det gick inte att läsa in bilagan. Försök igen."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Nätverket är inte klart. Försök igen."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Ta bort text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Växla mellan att ange text och siffror"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Lägg till fler deltagare"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Bekräfta deltagare"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Starta ny konversation"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Välj det här objektet"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Spela upp video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Personer och alternativ"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Felsökning"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Personer och alternativ"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Allmänt"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Personer i den här konversationen"</string>
+ <string name="action_call" msgid="6596167921517350362">"Ring ett samtal"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Skicka meddelande"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Skicka meddelandet&lt;br/&gt;&lt;small&gt;från <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Skicka foton</item>
+ <item quantity="one">Skicka foto</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Skicka ljud</item>
+ <item quantity="one">Skicka ljud</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Skicka videor</item>
+ <item quantity="one">Skicka video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Skicka kontaktkort</item>
+ <item quantity="one">Skicka kontaktkort</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Skicka bilagor</item>
+ <item quantity="one">Skicka bilaga</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> bilagor är klara att skicka</item>
+ <item quantity="one">En bilaga är klar att skicka</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Skicka feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Visa i Google Play Butik"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Version"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Version %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Licenser, öppen källkod"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Aviseringar"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Gränsen för bilagor har uppnåtts"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Det gick inte att läsa in bilagan."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Vill du lägga till detta i Kontakter?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Lägg till kontakt"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Ämne"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Ämne: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Läser in visitkort"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Det gick inte att läsa in kontaktkortet"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Visa visitkort"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kontakter</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontaktkort"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Födelsedatum"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Anteckningar"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Vidarebefordra meddelande"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Svara"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+ 1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Sms har inaktiverats"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Ange Meddelanden som standardapp för sms om du vill skicka"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Ange Meddelanden som standardapp för sms"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Ändra"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Ställ in Meddelanden som standardapp för sms om du vill ta emot meddelanden"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Inget SIM-kort har angetts som primärt för att skicka sms"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Appen tillåts inte av enhetsägaren."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"För många deltagare i en konversation"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Ogiltiga kontakter</item>
+ <item quantity="one">Ogiltig kontakt</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Det gick inte att läsa in kamerabild"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Du: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Utkast"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"När du startar en ny konversation listas den här"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arkiverade konversationer visas här"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Läser in konversationer …"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Bild"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Ljudklipp"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Visitkort"</string>
+ <string name="mms_text" msgid="1528791558806015806">"Mms"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Ångra"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Försök igen"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Ange namnet på en kontakt eller ett telefonnummer för att starta ett nytt meddelande"</string>
+ <string name="action_block" msgid="9032076625645190136">"Blockera"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Blockera <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Återaktivera <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Vill du blockera <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Du kan fortfarande få meddelanden från det här numret, men du får inga fler aviseringar. Den här konversationen arkiveras."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Blockerade kontakter"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ÅTERAKTIVERA"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Blockerade kontakter"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Välj en bild från dokumentbiblioteket"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Skickar meddelande"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Meddelandet har skickats"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobildata har inaktiverats. Kontrollera inställningarna."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Det går inte att skicka meddelanden i flygplansläge"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Meddelandet kunde inte skickas"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Meddelandet har laddats ned"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobildata har inaktiverats. Kontrollera inställningarna."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Det går inte att ladda ned meddelanden i flygplansläge"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Det gick inte att ladda ned meddelandet"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Noll"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Ett"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Två"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tre"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Fyra"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Fem"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sex"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Sju"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Åtta"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Nio"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Det gick inte att skicka meddelandet med <xliff:g id="CARRIERNAME">%1$s</xliff:g>, fel <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Det gick inte att skicka meddelandet med okänd operatör, fel <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Vidarebefordra: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Meddelandet har inte skickats: tjänsten är inte aktiverad i nätverket"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Meddelandet skickades inte: ogiltig e-postadress"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Meddelandet skickades inte: ogiltigt meddelande"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Meddelandet skickades inte: inget stöd för innehållet"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Meddelandet skickades inte: inget stöd för meddelandet"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Meddelandet skickades inte: för stort"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Nytt meddelande"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Visa"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Bild"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Ingen app för detta hittades"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Ta bort mottagare"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Nytt meddelande"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Avbryt"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Redigera åtkomstpunkt"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Har inte angetts"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Namn"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS-proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS-port"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN-typ"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Ta bort APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Nytt APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Spara"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Ignorera"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Fältet Namn får inte vara tomt."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN får inte vara tomt."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC-fältet måste bestå av 3 siffror."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC-fältet måste bestå av 2 eller 3 siffror."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Återställer standardinställningar för APN."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Återställ standardinställningarna"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Standardinställningar för APN har återställts."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Namnlös"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Åtkomstpunktens namn (APN)"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN:er"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Nytt APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Inställningarna för åtkomstpunktens namn är inte tillgängliga för den här användaren"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Vill du kopiera till urklipp?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopiera"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"till <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Allmänt"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Avancerat"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Allmänna inställningar"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Avancerade inställningar"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM-kortet <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Skicka enskilda sms till alla mottagare. Det är endast du som får de eventuella svaren."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Skicka samma mms till alla mottagare."</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Okänt nummer"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Nytt meddelande"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Nytt meddelande."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM-kortsväljaren"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> har valts, SIM-kortsväljare"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Redigera ämne"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Välj SIM-kort eller redigera ämne"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Tryck länge för att spela in ljud"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Starta ny konversation"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Meddelanden"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Meddelandelista"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Meddelanden"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Nytt meddelande"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Konversationslista"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Konversationer hämtas"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Läser in meddelanden"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Visa fler konversationer"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Visa fler meddelanden"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Konversationen har raderats"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Konversationen raderades. Tryck här om du vill visa en annan konversation i Meddelanden."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Blockerad"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Återaktiverad"</string>
+ <string name="db_full" msgid="8459265782521418031">"Lagringsutrymmet är nästan fullt. All data kanske inte sparas."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Välj bilagor"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Bekräfta ditt val"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> har valts"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Ta bort en eller flera av bilagorna och försök igen."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Du kan testa att skicka meddelandet, men det kanske inte kommer fram om du inte tar bort en eller flera av bilagorna först."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Det går bara att skicka en video per meddelande. Ta bort ytterligare videor och försök igen."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Det gick inte att läsa in bilagan i Meddelanden."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Skicka ändå"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Det gick inte att starta en konversation"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> har valts"</string>
+</resources>
diff --git a/res/values-sw/arrays.xml b/res/values-sw/arrays.xml
new file mode 100644
index 0000000..1641f55
--- /dev/null
+++ b/res/values-sw/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"kichwa hakijaongezwa"</item>
+ <item msgid="272485471009191934">"kichwa hakijaongezwa"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ndiyo"</item>
+ <item msgid="6049132459802288033">"Hapana"</item>
+ <item msgid="3084376867445867895">"Sawa"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Asante"</item>
+ <item msgid="4881335087096496747">"Nakubali"</item>
+ <item msgid="2422296858597420738">"Shwari"</item>
+ <item msgid="4805581752819452687">"Niko njiani"</item>
+ <item msgid="4746700499431366214">"Sawa, nitawasiliana na wewe baadaye"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..b415367
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Utumaji ujumbe"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Utumaji ujumbe"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Chagua mazungumzo"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Mipangilio"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Tuma Ujumbe"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Ongeza kiambatisho"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Usaidizi"</string>
+ <string name="welcome" msgid="2857560951820802321">"Karibu"</string>
+ <string name="skip" msgid="7238879696319945853">"Ruka"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Inayofuata &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Inayofuata"</string>
+ <string name="exit" msgid="1905187380359981199">"Ondoka"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Mipangilio &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Mipangilio"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Programu ya Ujumbe inahitaji ruhusa za SMS, Simu na Anwani."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Unaweza kubadilisha ruhusa katika Mipangilio&gt; Programu&gt; Ujumbe&gt; Ruhusa."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Unaweza kubadilisha ruhusa katika Mipangilio, Programu, Ujumbe, Ruhusa."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Mara kwa mara"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Anwani zote"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Tuma kwa <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Piga picha au urekodi video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Chagua picha kutoka kifaa hiki"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Rekodi sauti"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Chagua picha"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Maudhui yameteuliwa."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Maudhui yameondolewa uteuzi."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> zimechaguliwa"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"picha <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" msgid="8142175029053370662">"picha"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Rekodi sauti"</string>
+ <string name="action_share" msgid="2143483844803153871">"Shiriki"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Sasa hivi tu"</string>
+ <string name="posted_now" msgid="867560789350406701">"Sasa"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other">Dakika <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="one">Dakika <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other">Saa <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="one">Saa <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other">Siku <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="one">Siku <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other">wiki <xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="one">wiki</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other">miezi <xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="one">mwezi</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other">miaka <xliff:g id="COUNT">%d</xliff:g></item>
+ <item quantity="one">mwaka</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Ujumbe wa tabaka 0"</string>
+ <string name="save" msgid="5081141452059463572">"Hifadhi"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Nafasi haitoshi kwenye kifaa. Programu ya Ujumbe itafuta kiotomatiki ujumbe wa zamani ili kuacha nafasi kwenye kifaa."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Nafasi ya kuhafadhi inakaribia kujaa"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Huenda programu ya Ujumbe isitume au kupokea ujumbe hadi nafasi zaidi ipatikane katika kifaa chako."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Hifadhi ya SMS ni ndogo. Itakubidi ufute ujumbe."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Thibitisha nambari yako ya simu"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Hatua hii ya kufanywa mara moja itahakikisha kuwa programu ya Ujumbe itawasilisha ujumbe wako wa kikundi bila tatizo."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Nambari ya simu"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Futa ujumbe wote ulio na picha, sauti au video"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Futa ujumbe uliokaa zaidi ya <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Futa kiotomatiki ujumbe uliokaa zaidi ya <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Puuza"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Je, ungependa kufuta ujumbe wote ulio na maudhui?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Je, ungependa kufuta ujumbe uliokaa zaidi ya <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Je, ungependa kufuta ujumbe uliokaa zaidi ya <xliff:g id="DURATION">%s</xliff:g> na uwashe kufuta kiotomatiki?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> alisema"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Ulisema"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Ujumbe kutoka <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Ulituma ujumbe"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Inatuma…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Haikutumwa. Gusa ili ujaribu tena."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Haikutumwa. Jaribu tena..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Tuma tena au ufute"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Tafadhali piga simu ya sauti kwa huduma za dharura. Ujumbe wako haukuweza kupokelewa kwa wakati huu."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Imeshindikana"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Ujumbe mpya wa MMS wa kupakua"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Ujumbe mpya wa MMS"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Haikuweza kupakua"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Gusa ili ujaribu tena"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Gusa ili upakue"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Pakua au ufute"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Inapakua…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Ujumbe umepitwa na wakati au haupo"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ukubwa: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, mwisho: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Haiwezi kutuma. Mpokeaji si sahihi."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Huduma haijaanza kutumika kwenye mtandao."</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Haikutuma kwa sababu ya hitilafu ya mtandao"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Ujumbe umepitwa na wakati au haupo"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Kichwa hakijaongezwa)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Mtumaji asiyejulikana"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Umewasilishwa"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Haikuweza kupakua ujumbe <xliff:g id="SUBJECT">%1$s</xliff:g> kutoka <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Haikuweza kukamilisha utendaji wa hifadhidata kwa sababu ya kumbukumbu ndogo"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Ujumbe haujatumwa"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Baadhi ya ujumbe haukutumwa katika programu ya Ujumbe"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other">Ujumbe <xliff:g id="MESSAGES_1">%d</xliff:g> katika mazungumzo <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="one">Ujumbe <xliff:g id="MESSAGES_0">%d</xliff:g> katika mazungumzo moja</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Ujumbe haujapakuliwa"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Baadhi ya ujumbe haukupakuliwa katika programu ya Ujumbe"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other">Ujumbe <xliff:g id="MESSAGES_1">%d</xliff:g>katika mazungumzo <xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="one">Ujumbe <xliff:g id="MESSAGES_0">%d</xliff:g> katika mazungumzo moja</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Ujumbe kwa <xliff:g id="NUMBER">%1$s</xliff:g> haukutumwa"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Tafadhali piga simu ya sauti kwa huduma za dharura. Ujumbe wako kwa <xliff:g id="NUMBER">%1$s</xliff:g> haukuweza kupokelewa kwa wakati huu."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other">Ujumbe <xliff:g id="MESSAGES">%d</xliff:g> mpya</item>
+ <item quantity="one">Ujumbe mpya</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Anza"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera haipatikani"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera haipatikani"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Kurekodi video hakupatikani"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Haiwezi kuhifadhi maudhui ya picha au video"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Haiwezi kupiga picha"</string>
+ <string name="back" msgid="1477626055115561645">"Nyuma"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Imewekwa kwenye kumbukumbu"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Futa"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Kumbukumbu"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Ondoa kwenye kumbukumbu"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Zima arifa"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Washa arifa"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Ongeza anwani"</string>
+ <string name="action_download" msgid="7786338136368564146">"Pakua"</string>
+ <string name="action_send" msgid="377635240181672039">"Tuma"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Futa"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Je, ungependa kufuta ujumbe huu?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Kitendo hiki hakiwezi kutenduliwa."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Futa"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Ungependa kufuta mazungumzo haya?</item>
+ <item quantity="one">Ungependa kufuta mazungumzo haya?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Futa"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Ghairi"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Kwa"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Chagua picha nyingi"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Thibitisha uteuzi"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"<xliff:g id="COUNT">%d</xliff:g>+"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Haiwezi kurekodi sauti. Jaribu tena."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Haiwezi kucheza sauti. Jaribu tena."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Haikuweza kuhifadhi sauti. Jaribu tena."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Gusa na ushikilie"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Picha"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Klipu ya sauti"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kadi ya anwani"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Pakua"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Jibu kupitia SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Jibu kupitia MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Jibu"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other">Washiriki <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="one">Mshiriki <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Mimi"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Anwani imezuiwa na imewekwa kwenye kumbukumbu"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Anwani imeacha kuzuiliwa na imetolewa kwenye kumbukumbu"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> yamewekwa kwenye kumbukumbu"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> yameondolewa kwenye kumbukumbu"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Arifa zimezimwa"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Arifa zimewashwa"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Imewekwa. Gusa Tuma tena."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Umefaulu kuweka Programu ya Ujumbe kuwa programu ya chaguo-msingi ya SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Tupa viambatisho</item>
+ <item quantity="one">Tupa kiambatisho</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Kiambatisho cha sauti"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Cheza kiambatisho cha sauti"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Sitisha"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Ujumbe kutoka kwa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Ujumbe ulioshindikana kutoka kwa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Ujumbe kutoka <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Ujumbe ambao haukutumwa kwa <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Inatuma ujumbe kwa <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Ujumbe ulioshindikana kwa <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Ujumbe kwa <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Ujumbe ulioshindikana kutoka kwa <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Ujumbe kutoka <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Ujumbe ambao haukutumwa kwa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Inatuma ujumbe kwa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Ujumbe ulioshindikana kwenda kwa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Ujumbe kwa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Saa: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Ujumbe ulishindikana. Gusa ili ujaribu tena."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Mazungumzo na <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Futa kichwa"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Rekodi video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Piga picha ya mnato"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Piga picha"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Anza kurekodi video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Badili utumie kamera ya skrini nzima"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Badili kati ya kamera ya nyuma na mbele"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Acha kurekodi na kuambatisha video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Acha kurekodi video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Picha za programu ya Ujumbe"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">Picha <xliff:g id="QUANTITY_2">%d</xliff:g> zimehifadhiwa kwenye albamu ya \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Picha <xliff:g id="QUANTITY_0">%d</xliff:g> imehifadhiwa kwenye albamu ya \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">Video <xliff:g id="QUANTITY_2">%d</xliff:g> zimehifadhiwa kwenye albamu ya \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one">Video <xliff:g id="QUANTITY_0">%d</xliff:g> imehifadhiwa kwenye albamu ya \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">Viambatisho <xliff:g id="QUANTITY_2">%d</xliff:g> vimehifadhiwa kwenye albamu ya \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" </item>
+ <item quantity="one">Kiambatisho <xliff:g id="QUANTITY_0">%d</xliff:g> kimehifadhiwa kwenye albamu ya \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">Viambatisho <xliff:g id="QUANTITY_1">%d</xliff:g> vimehifadhiwa kwenye \"Vipakuliwa\"</item>
+ <item quantity="one">Kiambatisho <xliff:g id="QUANTITY_0">%d</xliff:g> kimehifadhiwa kwenye \"Vipakuliwa\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">Imehifadhi viambatisho <xliff:g id="QUANTITY_1">%d</xliff:g></item>
+ <item quantity="one">Imehifadhi kiambatisho <xliff:g id="QUANTITY_0">%d</xliff:g></item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Haikuweza kuhifadhi viambatisho <xliff:g id="QUANTITY_1">%d</xliff:g></item>
+ <item quantity="one">Haikuweza kuhifadhi kiambatisho <xliff:g id="QUANTITY_0">%d</xliff:g></item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Kiambatisho cha MMS kimehifadhiwa"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Mipangilio"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Imewekwa kwenye kumbukumbu"</string>
+ <string name="action_close" msgid="1840519376200478419">"Funga"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Mahiri"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Tatua"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Arifa"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Mlio"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Kimya"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Tetema"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Imezuiwa"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Ripoti za kupokelewa kwa SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Omba ripoti ya kuthibitisha kuwa kila SMS unayotuma imewasilishwa"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Pakua kiotomatiki"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Rejesha ujumbe wa MMS kiotomatiki"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Rejesha kiotomatiki wakati wa kutumia mitandao mingine"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Rejesha MMS kiotomatiki unapotumia mitandao mingine"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Utumaji ujumbe kwa kikundi"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Tumia MMS kutuma ujumbe mmoja wakati kuna wapokeaji wengi"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Programu chaguo-msingi ya SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Programu chaguo-msingi ya SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Nambari yako ya simu"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Isiyojulikana"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Sauti za ujumbe unaotumwa"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Tupa SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Tupa data ghafi za SMS zilizopokelewa kwenye faili ya hifadhi ya nje"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Tupa ujumbe wa MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Tupa data ghafi za ujumbe wa MMS uliopokelewa kwenye faili ya hifadhi ya nje"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Arifa ya dharura"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Chaguo za ujumbe"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Nakili maandishi"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Tazama maelezo"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Futa"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Sambaza"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Maelezo ya ujumbe"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Aina: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Ujumbe wa maandishi"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Ujumbe anuai"</string>
+ <string name="from_label" msgid="1947831848146564875">"Kutoka: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kwa: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Imetumwa: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Imepokelewa: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Kichwa: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Ukubwa: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Kipaumbele: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Juu"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Kawaida"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Chini"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Anwani ya mtumaji iliyofichwa"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Haiwezi kutuma ujumbe wakati inapakiwa viambatisho."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Haiwezi kupakia kiambatisho. Jaribu tena."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Mtandao hauko tayari. Jaribu tena."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Futa ujumbe"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Badili kati ya kuweka maandishi na nambari"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Ongeza washiriki zaidi"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Thibitisha washiriki"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Anzisha mazungumzo mapya"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Chagua bidhaa hii"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Cheza video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Watu na chaguo"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Tatua"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Watu na chaguo"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Jumla"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Watu katika mazungumzo haya"</string>
+ <string name="action_call" msgid="6596167921517350362">"Piga simu"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Tuma ujumbe"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Tuma ujumbe&lt;br/&gt;&lt;small&gt;kutoka <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Tuma picha</item>
+ <item quantity="one">Tuma picha</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Tuma sauti</item>
+ <item quantity="one">Tuma sauti</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Tuma video</item>
+ <item quantity="one">Tuma video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Tuma kadi za anwani</item>
+ <item quantity="one">Tuma kadi ya anwani</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Tuma viambatisho</item>
+ <item quantity="one">Tuma kiambatisho</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other">Viambatisho <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> viko tayari kutumwa</item>
+ <item quantity="one">Kiambatisho kimoja kiko tayari kutumwa</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Tuma maoni"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Angalia katika Duka la Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Maelezo kuhusu toleo hili"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Toleo la %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Leseni za programu huria"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Arifa"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Kikomo cha kiambatisho kilifikiwa"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Imeshindwa kupakia kiambatisho."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Ungependa kuongeza kwenye Anwani?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Ongeza Anwani"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Kichwa"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Kichwa: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Inapakia kadi ya anwani"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Haikuweza kupakia kadi ya anwani"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Angalia kadi ya anwani"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other">Anwani <xliff:g id="COUNT_1">%d</xliff:g></item>
+ <item quantity="one">Anwani <xliff:g id="COUNT_0">%d</xliff:g></item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kadi za mawasiliano"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Siku ya kuzaliwa"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Madokezo"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Sambaza ujumbe"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Jibu"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS imezimwa"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Ili utume ujumbe, weka programu ya Ujumbe kuwa programu ya chaguo-msingi ya SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Weka programu ya Ujumbe kuwa programu ya chaguo-msingi ya SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Badilisha"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Ili kupokea ujumbe, weka programu ya Ujumbe iwe programu ya chaguo-msingi ya SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Hakuna SIM inayopendelewa kwa kutuma ujumbe wa SMS iliyochaguliwa"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Programu hii hairuhusiwi na mmiliki kifaa."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"SAWA"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Washiriki wengi mno katika mazungumzo"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Anwani zisizo sahihi</item>
+ <item quantity="one">Anwani isiyo sahihi</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Haikuweza kupakia picha ya kamera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Wewe: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Rasimu"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Utakapoanzisha mazungumzo mapya, utayaona yameorodheshwa hapa"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Mazungumzo yaliyowekwa kwenye kumbukumbu huonekana hapa"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Inapakia mazungumzo..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Picha"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Klipu ya sauti"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kadi ya anwani"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Tendua"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Jaribu tena"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Andika jina la anwani au nambari ya simu ili uanze ujumbe mpya"</string>
+ <string name="action_block" msgid="9032076625645190136">"Zuia"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Zuia <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Ondoa kizuizi kwa <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Ungependa kuzuia <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Utaendelea kupokea ujumbe kutoka nambari hii ila hutapewa arifa tena. Mazungumzo haya yatawekwa kwenye kumbukumbu."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Anwani zilizozuiwa"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ONDOA KIZUIZI"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Anwani zilizozuiwa"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Chagua picha kutoka maktaba ya hati"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Inatuma ujumbe"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Ujumbe umetumwa"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Data ya simu za mkononi imezimwa. Angalia mipangilio yako."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Haiwezi kutuma ujumbe katika Hali ya ndegeni"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Ujumbe haukutumwa"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Ujumbe umepakuliwa"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Data ya simu za mkononi imezimwa. Angalia mipangilio yako."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Haiwezi kupakua ujumbe katika Hali ya ndegeni"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Ujumbe haukuweza kupakuliwa"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Sifuri"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Moja"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Mbili"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tatu"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Nne"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Tano"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sita"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Saba"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Nane"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Tisa"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Huwezi kutuma ujumbe ukitumia <xliff:g id="CARRIERNAME">%1$s</xliff:g>, hitilafu <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Huwezi kutuma ujumbe ukitumia mtoa huduma asiyejulikana, hitilafu <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Ujumbe haukutumwa: huduma haijaanza kutumika kwenye mtandao."</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Ujumbe haujatumwa: anwani ya unakotuma si sahihi"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Ujumbe haujatumwa: ujumbe usio sahihi"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Ujumbe haujatumwa: maudhui yasiyoweza kutumika"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Ujumbe haujatumwa: ujumbe usioweza kutumika"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Ujumbe haujatumwa: ni mkubwa mno"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Ujumbe mpya"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Tazama"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Picha"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Haikuweza kupata programu inayofaa"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Ondoa mpokeaji"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Ujumbe mpya"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Ghairi"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Badilisha mahali pa kufikia"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Haijawekwa"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Jina"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proksi ya MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Mlango wa MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Aina ya APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Futa APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN Mpya"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Hifadhi"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Tupa"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Sehemu hii haiwezi kuachwa ikiwa haijajazwa."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN haiwezi kuwa tupu."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Sehemu ya MCC lazima iwe na nambari 3."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Sehemu ya MNC lazima iwe na nambari 2 au 3."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Inarejesha mipangilio mbadala ya APN"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Rudisha kwenye chaguo-msingi"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Kuweka upya kwa mipangilio mbadala ya APN kumekamilika"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Haina Kichwa"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Majina ya Lango la Mtandao"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN Mpya"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Mipangilio ya Jina la Lango la Mtandao (APN) haipatikani kwa mtumiaji huyu"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Je, ungependa kunakili kwenye ubao klipu?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Nakili"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"kwa <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Jumla"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Mahiri"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Mipangilio ya jumla"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Mipangilio ya kina"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Tuma ujumbe binafsi wa SMS kwa wapokeaji wote. Wewe tu ndiye utakayepata majibu yoyote"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Tuma MMS moja kwa wapokeaji wote"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Nambari isiyojulikana"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Ujumbe mpya"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Ujumbe mpya."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Kichagua SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> imechaguliwa, Kichagua SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Badilisha kichwa"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Chagua SIM au ubadilishe kichwa"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Gusa na ushikilie ili urekodi sauti"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Anzisha mazungumzo mapya"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Ujumbe"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Orodha ya Ujumbe"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Ujumbe"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Ujumbe mpya"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Orodha ya mazungumzo"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Inapakia mazungumzo"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Inapakia ujumbe"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Tazama mazungumzo zaidi"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Angalia ujumbe zaidi"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Mazungumzo yamefutwa"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Mazungumzo yamefutwa. Gusa ili uonyeshe mazungumzo tofauti ya programu ya Ujumbe"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Amezuiwa"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Ameondolewa kizuizi"</string>
+ <string name="db_full" msgid="8459265782521418031">"Nafasi ya hifadhi ni ndogo. Unaweza kupoteza baadhi ya data."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Chagua viambatisho"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Thibitisha uteuzi"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> vimechaguliwa"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Tafadhali ondoa moja au zaidi ya viambatisho na ujaribu tena."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Unaweza kujaribu kutuma ujumbe wako, lakini huenda usitumwe hadi uondoe kiambatisho kimoja au zaidi."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Unaweza kutuma video moja pekee kwa kila ujumbe. Tafadhali ondoa video za ziada na ujaribu tena."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Programu ya Ujumbe haikupakia kiambatisho."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Tuma tu"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Haikuweza kuanza mazungumzo"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> imechaguliwa"</string>
+</resources>
diff --git a/res/values-ta-rIN/arrays.xml b/res/values-ta-rIN/arrays.xml
new file mode 100644
index 0000000..9953b60
--- /dev/null
+++ b/res/values-ta-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"தலைப்பு இல்லை"</item>
+ <item msgid="272485471009191934">"தலைப்பு இல்லை"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ஆம்"</item>
+ <item msgid="6049132459802288033">"இல்லை"</item>
+ <item msgid="3084376867445867895">"சரி"</item>
+ <item msgid="3155097332660174689">"ஹிஹி"</item>
+ <item msgid="2611328818571146775">"நன்றி"</item>
+ <item msgid="4881335087096496747">"ஆம்"</item>
+ <item msgid="2422296858597420738">"அருமை"</item>
+ <item msgid="4805581752819452687">"வந்துகொண்டிருக்கிறேன்"</item>
+ <item msgid="4746700499431366214">"பின்னர் அழைக்கிறேன்"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
new file mode 100644
index 0000000..e107ffe
--- /dev/null
+++ b/res/values-ta-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"மெசேஜ்"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"மெசேஜ்"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"உரையாடலைத் தேர்ந்தெடுக்கவும்"</string>
+ <string name="action_settings" msgid="1329008122345201684">"அமைப்பு"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"செய்தி அனுப்பு"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"இணைப்பைச் சேர்"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"உதவி"</string>
+ <string name="welcome" msgid="2857560951820802321">"நல்வரவு"</string>
+ <string name="skip" msgid="7238879696319945853">"தவிர்"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"அடுத்து &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"அடுத்து"</string>
+ <string name="exit" msgid="1905187380359981199">"வெளியேறு"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"அமைப்புகள் &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"அமைப்புகள்"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"செய்தியிடலுக்கு SMS, ஃபோன் மற்றும் தொடர்புகள் ஆகியவற்றுக்கான அனுமதி தேவை."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"அமைப்புகள் &gt; பயன்பாடுகள் &gt; செய்தியிடல் &gt; அனுமதிகள் என்பதில் அனுமதிகளை மாற்றலாம்."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"அமைப்புகள், பயன்பாடுகள், செய்தியிடல், அனுமதிகள் என்பதில் அனுமதிகளை மாற்றலாம்."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"அடிக்கடி தொடர்புகொண்டவர்கள்"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"எல்லா தொடர்புகளும்"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> க்கு அனுப்பு"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"படங்கள் அல்லது வீடியோவை எடுக்கவும்"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"இந்தச் சாதனத்தில் இருந்து படங்களைத் தேர்வு செய்யவும்"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ஆடியோவைப் பதிவுசெய்"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"படத்தைத் தேர்வுசெய்"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"மீடியா தேர்ந்தெடுக்கப்பட்டது."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"மீடியா தேர்வுநீக்கப்பட்டது."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> தேர்ந்தெடுக்கப்பட்டது"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"படம் <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" msgid="8142175029053370662">"படம்"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ஆடியோவைப் பதிவுசெய்"</string>
+ <string name="action_share" msgid="2143483844803153871">"பகிர்"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"சற்றுமுன்"</string>
+ <string name="posted_now" msgid="867560789350406701">"இப்போது"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> நிமிடங்கள்</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> நிமிடம்</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> மணிநேரம்</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> மணிநேரம்</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> நாட்கள்</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> நாள்</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> வாரங்கள்</item>
+ <item quantity="one">ஒரு வாரம்</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> மாதங்கள்</item>
+ <item quantity="one">ஒரு மாதம்</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ஆண்டுகள்</item>
+ <item quantity="one">ஒரு ஆண்டு</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"கிளாஸ் 0 செய்தி"</string>
+ <string name="save" msgid="5081141452059463572">"சேமி"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"சாதனத்தில் இடம் குறைவாக உள்ளது. இடத்தைக் காலியாக்க, செய்தியிடலானது பழைய உரைச் செய்திகளை தானாகவே நீக்கும்."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"சேமிப்பிடம் குறைகிறது"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"உங்கள் சாதனத்தில் கூடுதல் இடம் கிடைக்கும் வரை செய்தியிடல் செய்திகளை அனுப்பாமல் அல்லது பெறாமல் இருக்கக் கூடும்."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS சேமிப்பகம் குறைவாக உள்ளது. நீங்கள் செய்திகளை நீக்க வேண்டியிருக்கலாம்."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"உங்கள் ஃபோன் எண்ணை உறுதிசெய்யவும்"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"இந்த ஒரு முறை செயல்பாடானது, செய்தியிடல் உங்கள் குழுச் செய்திகளைச் சரியாக வழங்குவதை உறுதிப்படுத்தும்."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ஃபோன் எண்"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"மீடியாவுடனான செய்திகளை நீக்கு"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> க்கு முந்தைய செய்திகளை நீக்கு"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> காலத்திற்கு முந்தைய செய்திகளைத் தானாகவே நீக்கு"</string>
+ <string name="ignore" msgid="7063392681130898793">"புறக்கணி"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"மீடியாவுடனான எல்லா செய்திகளையும் நீக்க வேண்டுமா?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> காலத்திற்கு முந்தைய செய்திகளை நீக்க வேண்டுமா?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> காலத்திற்கு முந்தைய செய்திகளை நீக்கி, தானாக நீக்கும் அம்சத்தை இயக்கவா?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> கூறியது"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"நீங்கள் கூறியது"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> இடமிருந்து செய்தி"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"செய்தி அனுப்பியுள்ளீர்கள்"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"அனுப்புகிறது…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"அனுப்பவில்லை. மீண்டும் முயல, தொடவும்."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"அனுப்பவில்லை. மீண்டும் முயல்கிறது…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"மீண்டும் அனுப்பு அல்லது நீக்கு"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"அவசரகால சேவைகளை அழைக்கவும். உங்கள் உரைச் செய்தியை தற்போது வழங்க முடியவில்லை."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"தோல்வி"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"இறக்க, புதிய MMS செய்தி உள்ளது"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"புதிய MMS செய்தி"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"இறக்க முடியவில்லை"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"மீண்டும் முயல, தொடவும்"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"இறக்க, தொடவும்"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"இறக்கு அல்லது நீக்கு"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"இறக்குகிறது…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"செய்தி காலாவதியானது அல்லது இல்லை"</string>
+ <string name="mms_info" msgid="3402311750134118165">"அளவு: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, காலாவதியாகும் தேதி: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"அனுப்ப முடியவில்லை. பெறுநர் விவரம் தவறாக உள்ளது."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"நெட்வொர்க்கில் சேவை இயக்கப்படவில்லை"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"நெட்வொர்க் சிக்கல் காரணமாக அனுப்ப முடியவில்லை"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"செய்தி காலாவதியானது அல்லது இல்லை"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(தலைப்பு இல்லை)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"அறியப்படாத அனுப்புநர்"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"டெலிவரி செய்யப்பட்டது"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> இலிருந்து <xliff:g id="SUBJECT">%1$s</xliff:g> செய்தியைப் பதிவிறக்க முடியவில்லை."</string>
+ <string name="low_memory" msgid="5300743415198486619">"குறைந்த நினைவகம் காரணமாகத் தரவுத்தளச் செயல்பாட்டை முடிக்க முடியவில்லை"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"செய்தி அனுப்பப்படவில்லை"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"செய்தியிடலில் சில செய்திகள் அனுப்பப்படவில்லை"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> உரையாடல்களில் <xliff:g id="MESSAGES_1">%d</xliff:g> செய்திகள் உள்ளன</item>
+ <item quantity="one">ஒரு உரையாடலில் <xliff:g id="MESSAGES_0">%d</xliff:g> செய்திகள் உள்ளன</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"செய்தி இறக்கப்படவில்லை"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"செய்தியிடலில் சில செய்திகள் இறக்கப்படவில்லை"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> உரையாடல்களில் <xliff:g id="MESSAGES_1">%d</xliff:g> செய்திகள் உள்ளன</item>
+ <item quantity="one">ஒரு உரையாடலில் <xliff:g id="MESSAGES_0">%d</xliff:g> செய்திகள் உள்ளன</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g>க்குச் செய்தி அனுப்பப்படவில்லை"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"அவசரகால சேவைகளை அழைக்கவும். <xliff:g id="NUMBER">%1$s</xliff:g>_1க்கு நீங்கள் அனுப்பிய செய்தியை தற்போது வழங்க முடியவில்லை."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> புதிய செய்திகள்</item>
+ <item quantity="one">புதிய செய்தி</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"தொடங்கு"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"கேமரா இல்லை"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"கேமரா இல்லை"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"வீடியோ படமெடுக்க முடியாது"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"மீடியாவைச் சேமிக்க முடியவில்லை"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"படமெடுக்க முடியாது"</string>
+ <string name="back" msgid="1477626055115561645">"முந்தையது"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"காப்பகப்படுத்தப்பட்டன"</string>
+ <string name="action_delete" msgid="4076795795307486019">"நீக்கு"</string>
+ <string name="action_archive" msgid="5437034800324083170">"காப்பிடு"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"காப்பிட வேண்டாம்"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"அறிவிப்புகளை முடக்கு"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"அறிவிப்புகளை இயக்கு"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"தொடர்பைச் சேர்"</string>
+ <string name="action_download" msgid="7786338136368564146">"இறக்கு"</string>
+ <string name="action_send" msgid="377635240181672039">"அனுப்பு"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"நீக்கு"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"இந்தச் செய்தியை நீக்கவா?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"இந்தச் செயலை பின்னர் செயல்தவிர்க்க முடியாது."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"நீக்கு"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">உரையாடல்களை நீக்கவா?</item>
+ <item quantity="one">உரையாடலை நீக்கவா?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"நீக்கு"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"ரத்துசெய்"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"பெறுநர்"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"பல படங்களைத் தேர்ந்தெடு"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"தேர்வை உறுதிப்படுத்து"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ஆடியோவைப் பதிவு செய்ய இயலவில்லை. மீண்டும் முயலவும்."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ஆடியோவை இயக்க முடியவில்லை. மீண்டும் முயலவும்."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ஆடியோவைச் சேமிக்க முடியவில்லை. மீண்டும் முயலவும்."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"தொட்டுப் பிடித்திருக்கவும்"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"படம்"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ஆடியோ கிளிப்"</string>
+ <string name="notification_video" msgid="4331423498662606204">"வீடியோ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"தொடர்பு கார்டு"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"இறக்கு"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS மூலம் பதிலளி"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS மூலம் பதிலளி"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"பதிலளி"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> பங்கேற்பாளர்கள்</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> பங்கேற்பாளர்</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"எனக்கு"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"தொடர்பு தடுக்கப்பட்டு காப்பகப்படுத்தப்பட்டது"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"தொடர்பு தடைநீக்கப்பட்டுள்ளது &amp; பட்டியலில் தோன்றும்"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> காப்பகப்படுத்தப்பட்டன"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> பட்டியலில் தோன்றும்"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"அறிவிப்புகள் முடக்கப்பட்டுள்ளன"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"அறிவிப்புகள் இயக்கப்பட்டுள்ளன"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"எல்லாம் முடிந்தது. மீண்டும் அனுப்பு என்பதைத் தொடவும்."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"செய்தியிடல் இயல்பு SMS பயன்பாடாக அமைக்கப்பட்டது."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">இணைப்புகளை நிராகரி</item>
+ <item quantity="one">இணைப்பை நிராகரி</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ஆடியோ இணைப்பு"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ஆடியோ இணைப்பை இயக்கவும்"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"இடைநிறுத்து"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"செய்தி அனுப்பியவர்: <xliff:g id="SENDER">%s</xliff:g> - <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>க்கு அனுப்பிய செய்தி தோல்வி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> அனுப்பிய செய்தி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>க்கு அனுப்பப்படாத செய்தி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>க்குச் செய்தி அனுப்புகிறது: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>க்கு அனுப்பிய செய்தி தோல்வி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>க்கான செய்தி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>க்கு அனுப்பிய செய்தி தோல்வி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> இடமிருந்து செய்தி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g>க்கு அனுப்பப்படாத செய்தி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g>க்குச் செய்தி அனுப்புகிறது: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>க்கு அனுப்பிய செய்தி தோல்வி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>க்கான செய்தி: <xliff:g id="MESSAGE">%s</xliff:g>. நேரம்: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"செய்தி அனுப்புவதில் தோல்வி. மீண்டும் முயற்சிக்க, தொடவும்."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> உடனான உரையாடல்"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"தலைப்பை நீக்கு"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"வீடியோ எடு"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ஸ்டில் படமெடு"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"படமெடு"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"வீடியோவைப் படமெடு"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"முழுத் திரை கேமராவிற்கு மாறு"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"முன் மற்றும் பின்பக்க கேமராவிற்கு இடையே மாறு"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"பதிவுசெய்வதை நிறுத்தி வீடியோவை இணைக்கவும்"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"வீடியோ பதிவு செய்வதை நிறுத்து"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"செய்தியிடல் படங்கள்"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> படங்கள், \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ஆல்பத்தில் சேமிக்கப்பட்டன</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> படம், \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ஆல்பத்தில் சேமிக்கப்பட்டது</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> வீடியோக்கள், \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ஆல்பத்தில் சேமிக்கப்பட்டன</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> வீடியோ, \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ஆல்பத்தில் சேமிக்கப்பட்டது</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> இணைப்புகள், \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ஆல்பத்தில் சேமிக்கப்பட்டன</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> இணைப்பு, \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ஆல்பத்தில் சேமிக்கப்பட்டது</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> இணைப்புகள், \"இறக்கங்கள்\" என்பதில் சேமிக்கப்பட்டன</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> இணைப்பு, \"இறக்கங்கள்\" என்பதில் சேமிக்கப்பட்டது</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> இணைப்புகள் சேமிக்கப்பட்டன</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> இணைப்பு சேமிக்கப்பட்டது</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> இணைப்புகளைச் சேமிக்க முடியவில்லை</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> இணைப்பைச் சேமிக்க முடியவில்லை</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"சேமித்த MMS இணைப்பு"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"அமைப்பு"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"காப்பகப்படுத்தப்பட்டன"</string>
+ <string name="action_close" msgid="1840519376200478419">"மூடு"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"மேம்பட்டவை"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"பிழைத்திருத்து"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"அறிவிப்புகள்"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ஒலி"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"நிசப்தம்"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"அதிர்வு"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"தடுக்கப்பட்டது"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS டெலிவரி அறிக்கைகள்"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"நீங்கள் அனுப்பும் ஒவ்வொரு SMS க்கும் வழங்கல் அறிக்கையைக் கோரவும்"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"தானாக மீட்டெடு"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"தானாக MMS செய்திகளை மீட்டெடு"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"ரோமிங்கில் தானாக மீட்டெடு"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ரோமிங்கின் போது தானாகவே MMSஐ மீட்டெடு"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"குழு மெசேஜ்"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ஒரு செய்தியைப் பல பெறுநர்களுக்கு அனுப்புவதற்கு MMS ஐப் பயன்படுத்து"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"இயல்புநிலை SMS பயன்பாடு"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"இயல்புநிலை SMS பயன்பாடு"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"மொபைல் எண்"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"தெரியாதது"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"வெளிச்செல்லும் செய்தி ஒலிகள்"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS ஐ நகலெடு"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"பெற்ற SMS அசல் தரவை வெளிப்புறச் சேமிப்பகக் கோப்பில் நகலெடு"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS ஐ நகலெடு"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"பெற்ற MMS அசல் தரவை வெளிப்புறச் சேமிப்பகச் கோப்பில் நகலெடு"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"வயர்லெஸ் விழிப்பூட்டல்கள்"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"செய்தி விருப்பங்கள்"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"உரையை நகலெடு"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"விவரங்களைக் காட்டு"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"நீக்கு"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"முன்னனுப்பு"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"செய்தி விவரங்கள்"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"வகை: "</string>
+ <string name="text_message" msgid="7415419755252205721">"உரைச் செய்தி"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"மல்டிமீடியா செய்தி"</string>
+ <string name="from_label" msgid="1947831848146564875">"அனுப்புநர்: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"பெறுநர்: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"அனுப்பியது: "</string>
+ <string name="received_label" msgid="4442494712757995203">"பெறப்பட்டது: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"தலைப்பு: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"அளவு: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"முன்னுரிமை: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"உயர்தரம்"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"இயல்பு"</string>
+ <string name="priority_low" msgid="7398724779026801851">"குறைந்த தரம்"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"மறைவிலிருக்கும் அனுப்புநர் முகவரி"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"இணைப்புகளை ஏற்றும் போது செய்தியை அனுப்ப முடியாது."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"இணைப்பை ஏற்ற முடியவில்லை. மீண்டும் முயலவும்."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"நெட்வொர்க் தயாராக இல்லை. மீண்டும் முயற்சிக்கவும்."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"உரையை நீக்கு"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"உரை மற்றும் எண்களை உள்ளிடுவதன் இடையே மாறு"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"மேலும் பங்கேற்பாளர்களைச் சேர்"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"பங்கேற்பாளர்களை உறுதிப்படுத்தவும்"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"புதிய உரையாடலைத் தொடங்கு"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"இதனைத் தேர்வுசெய்"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"வீடியோவை இயக்கு"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"நபர்களும் விருப்பங்களும்"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"பிழைத்திருத்து"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"நபர்களும் விருப்பங்களும்"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"பொது"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"உரையாடலில் உள்ளவர்கள்"</string>
+ <string name="action_call" msgid="6596167921517350362">"அழை"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"செய்தி அனுப்பு"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"செய்தி அனுப்ப:&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">படங்களை அனுப்பு</item>
+ <item quantity="one">படத்தை அனுப்பு</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">ஆடியோக்களை அனுப்பு</item>
+ <item quantity="one">ஆடியோவை அனுப்பு</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">வீடியோக்களை அனுப்பு</item>
+ <item quantity="one">வீடியோவை அனுப்பு</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">தொடர்பு கார்டுகளை அனுப்பு</item>
+ <item quantity="one">தொடர்பு கார்டை அனுப்பு</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">இணைப்புகளை அனுப்பு</item>
+ <item quantity="one">இணைப்பை அனுப்பு</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> இணைப்புகள் அனுப்புவதற்குத் தயாராக உள்ளன</item>
+ <item quantity="one">ஒரு இணைப்பு அனுப்புவதற்குத் தயாராக உள்ளது</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"கருத்தை அனுப்பு"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store இல் காட்டு"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"பதிப்புத் தகவல்"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"பதிப்பு %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ஓப்பன் சோர்ஸ் உரிமங்கள்"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"அறிவிப்புகள்"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"இணைப்பிற்கான வரம்பை அடைந்துவிட்டீர்கள்"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"இணைப்பை ஏற்றுவதில் தோல்வி."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"தொடர்புகளில் சேர்க்கவா?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"தொடர்பைச் சேர்"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"தலைப்பு"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"தலைப்பு: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"தொடர்பு கார்டை ஏற்றுகிறது"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"தொடர்பு கார்டை ஏற்ற முடியவில்லை"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"தொடர்பு கார்டைக் காட்டு"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> தொடர்புகள்</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> தொடர்பு</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"தொடர்பு கார்டுகள்"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"பிறந்தநாள்"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"குறிப்புகள்"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"செய்தியை முன்னனுப்பு"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"பதிலளிக்கவும்"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS முடக்கப்பட்டது"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"அனுப்ப, இயல்பு SMS பயன்பாடாக செய்தியிடலை அமைக்கவும்"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"இயல்பு SMS பயன்பாடாக செய்தியிடலை அமைக்கவும்"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"மாற்று"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"செய்திகளைப் பெற, இயல்பு SMS பயன்பாடாக செய்தியிடலை அமைக்கவும்"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS செய்திகளை அனுப்புவதற்கு விருப்பமான சிம் எதுவும் தேர்ந்தெடுக்கப்படவில்லை"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"சாதன உரிமையாளர் இந்தப் பயன்பாட்டை அனுமதிக்கவில்லை."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"சரி"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"உரையாடலில் பலர் உள்ளனர்"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">தவறான தொடர்புகள்</item>
+ <item quantity="one">தவறான தொடர்பு</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"கேமரா படத்தை ஏற்ற முடியவில்லை"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"நீங்கள்: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"வரைவு"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"புதிய உரையாடலைத் தொடங்கியதும், அது இங்கு பட்டியலிடப்படும்"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"காப்பகப்படுத்திய உரையாடல்கள் இங்கே தோன்றும்"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"உரையாடல்களை ஏற்றுகிறது…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"படம்"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ஆடியோ கிளிப்"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"வீடியோ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"தொடர்பு கார்டு"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"செயல்தவிர்"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"மீண்டும் முயற்சிக்கவும்"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"புதிய செய்தி உருவாக்க, தொடர்பின் பெயர் அல்லது மொபைல் எண்ணை உள்ளிடவும்"</string>
+ <string name="action_block" msgid="9032076625645190136">"தடு"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>ஐத் தடு"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> இன் தடுப்பை நீக்கு"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>ஐத் தடைசெய்யவா?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"தொடர்ந்து இந்த எண்ணில் இருந்து செய்திகளைப் பெறுவீர்கள், அது குறித்து இனி உங்களுக்கு அறிவிக்கப்படாது. உரையாடல் காப்பகப்படுத்தப்படும்."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"தடுக்கப்பட்ட தொடர்புகள்"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"தடுப்பை நீக்கு"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"தடுக்கப்பட்ட தொடர்புகள்"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"ஆவண நூலகத்தில் இருந்து படத்தைத் தேர்வு செய்யவும்"</string>
+ <string name="sending_message" msgid="6363584950085384929">"செய்தி அனுப்பப்படுகிறது"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"செய்தி அனுப்பப்பட்டது"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"செல்லுலார் தரவு முடக்கப்பட்டுள்ளது. அமைப்புகளைச் சரிபார்க்கவும்."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"விமானப் பயன்முறையில் செய்திகளை அனுப்ப முடியாது"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"செய்தியை அனுப்ப முடியவில்லை"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"செய்தி பதிவிறக்கப்பட்டது"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"செல்லுலார் தரவு முடக்கப்பட்டுள்ளது. அமைப்புகளைச் சரிபார்க்கவும்."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"விமானப் பயன்முறையில் செய்திகளை இறக்க முடியாது"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"செய்தி இறக்கப்படவில்லை"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"பூஜ்ஜியம்"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ஒன்று"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"இரண்டு"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"மூன்று"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"நான்கு"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ஐந்து"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ஆறு"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ஏழு"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"எட்டு"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"ஒன்பது"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> மூலம் செய்தியை அனுப்ப முடியவில்லை, பிழை <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"அறியப்படாத மொபைல் நிறுவனம் மூலம் செய்தியை அனுப்ப முடியவில்லை, பிழை <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"அனுப்பு: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"செய்தி அனுப்பப்படவில்லை: நெட்வொர்க்கில் சேவை செயல்படுத்தப்படவில்லை"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"செய்தி அனுப்பப்படவில்லை: தவறான இலக்கு முகவரி"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"செய்தி அனுப்பப்படவில்லை: தவறான செய்தி"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"செய்தி அனுப்பப்படவில்லை: ஆதரிக்கப்படாத உள்ளடக்கம்"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"செய்தி அனுப்பப்படவில்லை: ஆதரிக்கப்படாத செய்தி"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"செய்தி அனுப்பப்படவில்லை: மிகப் பெரியது"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"புதிய செய்தி"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"காட்டு"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"படம்"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"தகுந்த பயன்பாட்டைக் கண்டுபிடிக்க இயலவில்லை"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"பெறுநரை அகற்று"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"புதிய செய்தி"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"ரத்துசெய்"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ஆக்சஸ் பாயிண்ட்டைத் திருத்து"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"அமைக்கப்படவில்லை"</string>
+ <string name="apn_name" msgid="1572691851070894985">"பெயர்"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS ப்ராக்ஸி"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS போர்ட்"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN வகை"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APNஐ நீக்கு"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"புதிய APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"சேமி"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"நிராகரி"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"பெயர் புலம் வெறுமையாக இருக்கக்கூடாது."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN வெறுமையாக இருக்கக்கூடாது."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC புலம் கண்டிப்பாக 3 இலக்கங்களில் இருக்க வேண்டும்."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC புலம் கண்டிப்பாக 2 அல்லது 3 இலக்கங்களில் இருக்க வேண்டும்."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"இயல்புநிலை APN அமைப்புகளை மீட்டமைக்கிறது."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"இயல்புநிலைக்கு மீட்டமை"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"இயல்புநிலை APN அமைப்புகளை மீட்டமைப்பது முடிந்தது."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"பெயரிடப்படாதது"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ஆக்சஸ் பாயிண்ட் நேம்கள்"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNகள்"</string>
+ <string name="menu_new" msgid="8286285392706532511">"புதிய APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"இவரால் ஆக்சஸ் பாயிண்ட் நேம் அமைப்புகளை மாற்ற முடியாது"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"கிளிப்போர்டுக்கு நகலெடுக்கவா?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"நகலெடு"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"பொது"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"மேம்பட்டவை"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"பொது அமைப்பு"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"மேம்பட்ட அமைப்பு"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" சிம்"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"எல்லா பெறுநர்களுக்கும் SMS செய்திகளைத் தனித்தனியாக அனுப்பவும். நீங்கள் மட்டுமே பதில்களைப் பெறுவீர்கள்"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"எல்லா பெறுநர்களுக்கும் MMS செய்திகளைத் தனித்தனியாக அனுப்பவும்"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"தெரியாத எண்"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"புதிய செய்தி"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"புதிய செய்தி."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"சிம் தேர்வி"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> தேர்ந்தெடுக்கப்பட்டது, சிம் தேர்வி"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"தலைப்பைத் திருத்து"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"சிம்மைத் தேர்ந்தெடுக்கவும் அல்லது தலைப்பைத் திருத்தவும்"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ஆடியோவைப் பதிவுசெய்ய, தொட்டுப் பிடித்திருக்கவும்"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"புதிய உரையாடலைத் தொடங்கு"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"செய்தியிடல்"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"செய்தியிடல் பட்டியல்"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"செய்தியிடல்"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"புதிய செய்தி"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"உரையாடல் பட்டியல்"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"உரையாடல்களை ஏற்றுகிறது"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"செய்திகளை ஏற்றுகிறது"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"மேலும் உரையாடல்களைக் காட்டு"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"கூடுதல் செய்திகளைக் காட்டு"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"உரையாடல் நீக்கப்பட்டது"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"உரையாடல் நீக்கப்பட்டது. வேறொரு செய்தியிடல் உரையாடலைக் காட்ட, தொடவும்"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"தடுக்கப்பட்டார்"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"தடுப்பு நீக்கப்பட்டார்"</string>
+ <string name="db_full" msgid="8459265782521418031">"சேமிப்பகம் குறைவாக உள்ளது. சில தரவை இழக்க நேரிடலாம்."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"இணைப்புகளைத் தேர்ந்தெடு"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"தேர்வை உறுதிப்படுத்து"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> தேர்ந்தெடுக்கப்பட்டன"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"ஒன்று அல்லது அதற்கு மேற்பட்ட இணைப்புகளை அகற்றிவிட்டு, முயற்சிக்கவும்."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"நீங்கள் செய்தியை அனுப்ப முயற்சிக்கலாம், ஆனால் ஒன்று அல்லது அதற்கு மேற்பட்ட இணைப்புகளை நீக்கும் வரை இது வழங்கப்படாமல் இருக்கலாம்."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"ஒரு செய்தியில் ஒரு வீடியோவை மட்டுமே நீங்கள் அனுப்ப முடியும். கூடுதல் வீடியோக்களை அகற்றிவிட்டு, மீண்டும் முயற்சிக்கவும்."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"செய்தியிடலால் இணைப்பை ஏற்ற முடியவில்லை."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"எப்படியேனும் அனுப்பு"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"உரையாடலைத் தொடங்க முடியவில்லை"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> தேர்ந்தெடுக்கப்பட்டது"</string>
+</resources>
diff --git a/res/values-te-rIN/arrays.xml b/res/values-te-rIN/arrays.xml
new file mode 100644
index 0000000..c18a1b6
--- /dev/null
+++ b/res/values-te-rIN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"విషయం లేదు"</item>
+ <item msgid="272485471009191934">"విషయం లేదు"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"అవును"</item>
+ <item msgid="6049132459802288033">"వద్దు"</item>
+ <item msgid="3084376867445867895">"సరే"</item>
+ <item msgid="3155097332660174689">"హెహే"</item>
+ <item msgid="2611328818571146775">"ధన్యవాదాలు"</item>
+ <item msgid="4881335087096496747">"నేను అంగీకరిస్తున్నాను"</item>
+ <item msgid="2422296858597420738">"బాగుంది"</item>
+ <item msgid="4805581752819452687">"నేను ఆ పని మీదే ఉన్నాను"</item>
+ <item msgid="4746700499431366214">"సరే, నేను మీతో తర్వాత మాట్లాడుతాను"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
new file mode 100644
index 0000000..4a58e0f
--- /dev/null
+++ b/res/values-te-rIN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"మెసేజింగ్"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"మెసేజింగ్"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"సంభాషణను ఎంచుకోండి"</string>
+ <string name="action_settings" msgid="1329008122345201684">"సెట్టింగ్‌లు"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"సందేశం పంపు"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"జోడింపును జోడించండి"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"సహాయం"</string>
+ <string name="welcome" msgid="2857560951820802321">"స్వాగతం"</string>
+ <string name="skip" msgid="7238879696319945853">"దాటవేయి"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"తదుపరి &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"తదుపరి"</string>
+ <string name="exit" msgid="1905187380359981199">"నిష్క్రమించండి"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"సెట్టింగ్‌లు &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"సెట్టింగ్‌లు"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"సందేశ సేవకు SMS, ఫోన్ మరియు పరిచయాల ప్రాప్యత అనుమతి ఇవ్వడం అవసరం."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"మీరు సెట్టింగ్‌లు &gt; అనువర్తనాలు &gt; సందేశ సేవ &gt; అనుమతులులో అనుమతులను మార్చవచ్చు."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"మీరు సెట్టింగ్‌లు, అనువర్తనాలు, సందేశ సేవ, అనుమతులులో అనుమతులను మార్చవచ్చు."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"తరచుగా సంప్రదించినవి"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"అన్ని పరిచయాలు"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g>కి పంపండి"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"చిత్రాలను లేదా వీడియోను సంగ్రహించు"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"ఈ పరికరం నుండి చిత్రాలను ఎంచుకోండి"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"ఆడియోను రికార్డ్ చేయి"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"ఫోటోను ఎంచుకోండి"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"మీడియా ఎంచుకోబడింది."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"మీడియా ఎంపిక తీసివేయబడింది."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ఎంచుకోబడింది"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"చిత్రం <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" msgid="8142175029053370662">"చిత్రం"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"ఆడియోను రికార్డ్ చేయి"</string>
+ <string name="action_share" msgid="2143483844803153871">"భాగస్వామ్యం చేయి"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ఇప్పుడే"</string>
+ <string name="posted_now" msgid="867560789350406701">"ఇప్పుడు"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> నిమి</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> నిమి</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> గంటలు</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> గంట</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> రోజులు</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> రోజు</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> వారాలు</item>
+ <item quantity="one">ఒక వారం</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> నెలలు</item>
+ <item quantity="one">ఒక నెల</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> సంవత్సరాలు</item>
+ <item quantity="one">ఒక సంవత్సరం</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"తరగతి 0 సందేశం"</string>
+ <string name="save" msgid="5081141452059463572">"సేవ్ చేయి"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"పరికరంలో ఖాళీ స్థలం తక్కువగా ఉంది. స్థలం ఖాళీ చేయడానికి సందేశ సేవ స్వయంచాలకంగా పాత సందేశాలను తొలగిస్తుంది."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"నిల్వ స్థలం అయిపోతోంది"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"మీ పరికరంలో మరింత ఖాళీ స్థలం లభించే వరకు సందేశ సేవ సందేశాలను పంపలేకపోవచ్చు లేదా స్వీకరించలేకపోవచ్చు."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS నిల్వ తక్కువగా ఉంది. మీరు సందేశాలను తొలగించాల్సి రావచ్చు."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"మీ ఫోన్ నంబర్‌ను నిర్ధారించండి"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ఈ ఒక పర్యాయ దశ సందేశ సేవ మీ సమూహ సందేశాలను సరిగ్గా బట్వాడా చేస్తుందని నిర్ధారిస్తుంది."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"ఫోన్ నంబర్"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"మీడియాలోని అన్ని సందేశాలను తొలగించండి"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> కంటే మునుపు ఉన్న సందేశాలను తొలగించండి"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> కంటే పాత సందేశాలను స్వయంచాలకంగా తొలగించు"</string>
+ <string name="ignore" msgid="7063392681130898793">"విస్మరించు"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"మీడియా ఉన్న అన్ని సందేశాలను తొలగించాలా?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> కంటే పాత సందేశాలను తొలగించాలా?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> కంటే పాత సందేశాలను తొలగించి, స్వయంచాలక తొలగింపుని ఆన్ చేయాలా?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> చెప్పారు"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"మీరు చెప్పారు"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> నుండి సందేశం"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"మీరు సందేశాన్ని పంపారు"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"పంపుతోంది..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"పంపబడలేదు. మళ్లీ ప్రయత్నించడానికి తాకండి."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"పంపబడలేదు. మళ్లీ ప్రయత్నిస్తోంది..."</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"మళ్లీ పంపండి లేదా తొలగించండి"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"దయచేసి అత్యవసర సేవలకు వాయిస్ కాల్ చేయండి. మీ వచన సందేశాన్ని ప్రస్తుతం బట్వాడా చేయడం సాధ్యపడలేదు."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"విఫలమైంది"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"డౌన్‌లోడ్ చేయడానికి కొత్త MMS సందేశం"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"కొత్త MMS సందేశం"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"మళ్లీ ప్రయత్నించడానికి తాకండి"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"డౌన్‌లోడ్ చేయడానికి తాకండి"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"డౌన్‌లోడ్ చేయండి లేదా తొలగించండి"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"డౌన్‌లోడ్ చేస్తోంది…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"సందేశం గడువు ముగిసింది లేదా అందుబాటులో లేదు"</string>
+ <string name="mms_info" msgid="3402311750134118165">"పరిమాణం: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, గడువు: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"పంపడం సాధ్యపడదు. చెల్లని స్వీకర్త."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"నెట్‌వర్క్‌లో సేవ సక్రియం చేయబడలేదు"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"నెట్‌వర్క్ సమస్య కారణంగా పంపడం సాధ్యపడలేదు"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"సందేశం గడువు ముగిసింది లేదా అందుబాటులో లేదు"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(విషయం లేదు)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"పంపినవారు తెలియదు"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"బట్వాడా చేయబడింది"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> నుండి <xliff:g id="SUBJECT">%1$s</xliff:g> సందేశాన్ని డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు."</string>
+ <string name="low_memory" msgid="5300743415198486619">"మెమరీ తక్కువగా ఉన్న కారణంగా డేటాబేస్ నిర్వహణను పూర్తి చేయడం సాధ్యపడలేదు"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"సందేశం పంపబడలేదు"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"సందేశ సేవలో కొన్ని సందేశాలు పంపబడలేదు"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> సంభాషణల్లో <xliff:g id="MESSAGES_1">%d</xliff:g> సందేశాలు</item>
+ <item quantity="one">ఒక సంభాషణలో <xliff:g id="MESSAGES_0">%d</xliff:g> సందేశాలు</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"సందేశం డౌన్‌లోడ్ కాలేదు"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"సందేశ సేవలో కొన్ని సందేశాలు డౌన్‌లోడ్ కాలేదు"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> సంభాషణల్లో <xliff:g id="MESSAGES_1">%d</xliff:g> సందేశాలు</item>
+ <item quantity="one">ఒక సంభాషణలో <xliff:g id="MESSAGES_0">%d</xliff:g> సందేశాలు</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g>కి సందేశం పంపబడలేదు"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"దయచేసి అత్యవసర సేవలకు వాయిస్ కాల్ చేయండి. మీరు <xliff:g id="NUMBER">%1$s</xliff:g>కి పంపిన వచన సందేశాన్ని ప్రస్తుతం బట్వాడా చేయడం సాధ్యపడలేదు."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> కొత్త సందేశాలు</item>
+ <item quantity="one">కొత్త సందేశం</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"ప్రారంభించు"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"కెమెరా అందుబాటులో లేదు"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"కెమెరా అందుబాటులో లేదు"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"వీడియో సంగ్రహణ అందుబాటులో లేదు"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"మీడియాను సేవ్ చేయడం సాధ్యపడదు"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"చిత్రం తీయడం సాధ్యపడలేదు"</string>
+ <string name="back" msgid="1477626055115561645">"వెనుకకు"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"ఆర్కైవ్ చేయబడినవి"</string>
+ <string name="action_delete" msgid="4076795795307486019">"తొలగించు"</string>
+ <string name="action_archive" msgid="5437034800324083170">"ఆర్కైవ్ చేయి"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ఆర్కైవ్ నుండి తీసివేయి"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"నోటిఫికేషన్‌లను ఆఫ్ చేయి"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"నోటిఫికేషన్‌లను ఆన్ చేయి"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"పరిచయాన్ని జోడించు"</string>
+ <string name="action_download" msgid="7786338136368564146">"డౌన్‌లోడ్ చేయి"</string>
+ <string name="action_send" msgid="377635240181672039">"పంపు"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"తొలగించు"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ఈ సందేశాన్ని తొలగించాలా?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"ఈ చర్య రద్దు చేయబడదు."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"తొలగించు"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">ఈ సంభాషణలను తొలగించాలా?</item>
+ <item quantity="one">ఈ సంభాషణను తొలగించాలా?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"తొలగించు"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"రద్దు చేయి"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"వీరికి"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"బహుళ చిత్రాలను ఎంచుకోండి"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"ఎంపికను నిర్ధారించు"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ఆడియోను రికార్డ్ చేయడం సాధ్యపడదు. మళ్లీ ప్రయత్నించండి."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ఆడియోను ప్లే చేయడం సాధ్యపడదు. మళ్లీ ప్రయత్నించండి."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ఆడియోను సేవ్ చేయడం సాధ్యపడలేదు. మళ్లీ ప్రయత్నించండి."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"తాకి ఉంచండి"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"చిత్రం"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"ఆడియో క్లిప్"</string>
+ <string name="notification_video" msgid="4331423498662606204">"వీడియో"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"పరిచయ కార్డు"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"డౌన్‌లోడ్ చేయి"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS ద్వారా ప్రత్యుత్తరం ఇవ్వండి"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMSలో ప్రత్యు."</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ప్రత్యుత్తరం పంపండి"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> మంది పాల్గొన్నారు</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> పాల్గొన్నారు</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"నేను"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"పరిచయం బ్లాక్ చేయబడింది &amp; ఆర్కైవ్ చేయబడింది"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"పరిచయం అన్‌బ్లాక్ చేయబడింది &amp; అన్ఆర్కైవ్ చేయబడింది"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ఆర్కైవ్ చేయబడింది/ఆర్కైవ్ చేయబడ్డాయి"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ఆర్కైవ్ నుండి తీసివేయబడింది/ఆర్కైవ్ నుండి తీసివేయబడ్డాయి"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"నోటిఫికేషన్‌లు ఆఫ్ చేయబడ్డాయి"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"నోటిఫికేషన్‌లు ఆన్ చేయబడ్డాయి"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"అంతా సెట్ చేశారు. మళ్లీ పంపు తాకండి."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"సందేశ సేవ డిఫాల్ట్ SMS అనువర్తనంగా విజయవంతంగా సెట్ చేయబడింది."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">జోడింపులను విస్మరించండి</item>
+ <item quantity="one">జోడింపును విస్మరించండి</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ఆడియో జోడింపు"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"ఆడియో జోడింపుని ప్లే చేయండి"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"పాజ్ చేయి"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> నుండి సందేశం: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> నుండి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపడంలో విఫలమైంది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> నుండి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం వచ్చింది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపబడలేదు. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపబడుతోంది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపడంలో విఫలమైంది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపబడింది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> నుండి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపడంలో విఫలమైంది. సమయం: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> నుండి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం వచ్చింది. సమయం: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపబడలేదు. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపబడుతోంది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపడంలో విఫలమైంది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g>కి <xliff:g id="MESSAGE">%s</xliff:g> సందేశం పంపబడింది. సమయం: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"సందేశం పంపడం విఫలమైంది. మళ్లీ ప్రయత్నించడానికి తాకండి."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g>తో సంభాషణ"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"విషయాన్ని తొలగించండి"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"వీడియోను క్యాప్చర్ చేయండి"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"నిలకడగా ఉన్న చిత్రాన్ని క్యాప్చర్ చేయండి"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"చిత్రం తీయండి"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"వీడియో రికార్డింగ్‌ను ప్రారంభించండి"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"కెమెరాను పూర్తి స్క్రీన్‌కి మార్చండి"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"ముందు మరియు వెనుక కెమెరా మోడ్‌ల మధ్య మారండి"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"రికార్డింగ్‌ను ఆపివేసి, వీడియోను చేర్చండి"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"వీడియో రికార్డ్ చేయడాన్ని ఆపివేయండి"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"సందేశ సేవ ఫోటోలు"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ఫోటోలు \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ఆల్బమ్‌కి సేవ్ చేయబడ్డాయి</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ఫోటో \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ఆల్బమ్‌కి సేవ్ చేయబడింది</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> వీడియోలు \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ఆల్బమ్‌కి సేవ్ చేయబడ్డాయి</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> వీడియో \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ఆల్బమ్‌కి సేవ్ చేయబడింది</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> జోడింపులు \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" ఆల్బమ్‌కి సేవ్ చేయబడ్డాయి</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> జోడింపు \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" ఆల్బమ్‌కి సేవ్ చేయబడింది</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> జోడింపులు \"డౌన్‌లోడ్‌లు\"కి సేవ్ చేయబడ్డాయి</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> జోడింపు \"డౌన్‌లోడ్‌లు\"కి సేవ్ చేయబడింది</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> జోడింపులు సేవ్ చేయబడ్డాయి</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> జోడింపు సేవ్ చేయబడింది</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> జోడింపులను సేవ్ చేయలేకపోయింది</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> జోడింపును సేవ్ చేయలేకపోయింది</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS జోడింపు సేవ్ చేయబడింది"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"సెట్టింగ్‌లు"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"ఆర్కైవ్ చేయబడింది"</string>
+ <string name="action_close" msgid="1840519376200478419">"మూసివేయి"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"అధునాతనం"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"డీబగ్"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"నోటిఫికేషన్‌లు"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"ధ్వని"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"నిశ్శబ్దం"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"వైబ్రేట్"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"బ్లాక్ చేయబడింది"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS బట్వాడా నివేదికలు"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"మీరు పంపే ప్రతి SMS కోసం బట్వాడా నివేదికను అభ్యర్థించండి"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"స్వయంచాలకంగా తిరిగి పొందడం"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS సందేశాలను స్వయంచాలకంగా పునరుద్ధరించండి"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"రోమింగ్‌లో ఉన్నప్పుడు స్వీయ-పునరుద్ధరణ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"రోమింగ్‌లో ఉన్నప్పుడు MMSని స్వయంచాలకంగా తిరిగి పొందుతుంది"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"సమూహ సందేశాలు"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ఒక సందేశాన్ని బహుళ స్వీకర్తలకు పంపాల్సినప్పుడు MMSను ఉపయోగించండి"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"డిఫాల్ట్ SMS అనువర్తనం"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"డిఫాల్ట్ SMS అనువర్తనం"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"మీ ఫోన్ నంబర్"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"తెలియదు"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"అవుట్‌గోయింగ్ సందేశ ధ్వనులు"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMSను డంప్ చేయండి"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"స్వీకరించబడిన SMS ప్రాసెస్ చేయబడని డేటాను బాహ్య నిల్వ ఫైల్‌లోకి డంప్ చేయండి"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMSను డంప్ చేయండి"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"స్వీకరించబడిన MMS ప్రాసెస్ చేయబడని డేటాను బాహ్య నిల్వ ఫైల్‌లోకి డంప్ చేయండి"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"వైర్‌లెస్ హెచ్చరికలు"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"సందేశ ఎంపికలు"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"వచనాన్ని కాపీ చేయి"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"వివరాలను వీక్షించండి"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"తొలగించు"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ఫార్వార్డ్ చేయండి"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"సందేశ వివరాలు"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"రకం: "</string>
+ <string name="text_message" msgid="7415419755252205721">"వచన సందేశం"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"మల్టీమీడియా సందేశం"</string>
+ <string name="from_label" msgid="1947831848146564875">"వీరి నుండి: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"వీరికి: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"పంపినది: "</string>
+ <string name="received_label" msgid="4442494712757995203">"స్వీకరించినది: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"విషయం: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"పరిమాణం: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ప్రాధాన్యత: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"అధికం"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"సాధారణం"</string>
+ <string name="priority_low" msgid="7398724779026801851">"తక్కువ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"లేఖరి చిరునామా దాచబడింది"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"జోడింపులు లోడ్ అవుతున్నప్పుడు సందేశాన్ని పంపలేరు."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"జోడింపుని లోడ్ చేయడం సాధ్యపడదు. మళ్లీ ప్రయత్నించండి."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"నెట్‌వర్క్ సిద్ధంగా లేదు. మళ్లీ ప్రయత్నించండి."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"వచనాన్ని తొలగించు"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"వచనం మరియు సంఖ్యలను నమోదు చేయడానికి వాటిని అటుఇటు మార్చు"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"మరింతమంది పాల్గొనేవారిని జోడించు"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"పాల్గొనేవారిని నిర్ధారించండి"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"కొత్త సంభాషణను ప్రారంభించండి"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"ఈ అంశాన్ని ఎంచుకోండి"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"వీడియోను ప్లే చేయండి"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"వ్యక్తులు &amp; ఎంపికలు"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"డీబగ్"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"వ్యక్తులు &amp; ఎంపికలు"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"సాధారణం"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ఈ సంభాషణలో పాల్గొన్న వ్యక్తులు"</string>
+ <string name="action_call" msgid="6596167921517350362">"కాల్ చేయి"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"సందేశాన్ని పంపండి"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"సందేశాన్ని &lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g> నుండి&lt;/small&gt;&lt;br/&gt;పంపు"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">ఫోటోలను పంపండి</item>
+ <item quantity="one">ఫోటోను పంపండి</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">ఆడియోలను పంపండి</item>
+ <item quantity="one">ఆడియోను పంపండి</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">వీడియోలను పంపండి</item>
+ <item quantity="one">వీడియోను పంపండి</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">పరిచయ కార్డ్‌లను పంపండి</item>
+ <item quantity="one">పరిచయ కార్డ్‌ను పంపండి</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">జోడింపులను పంపండి</item>
+ <item quantity="one">జోడింపుని పంపండి</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> జోడింపులు పంపడానికి సిద్ధంగా ఉన్నాయి</item>
+ <item quantity="one">ఒక జోడింపు పంపడానికి సిద్ధంగా ఉంది</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"అభిప్రాయం పంపండి"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Storeలో వీక్షించండి"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"సంస్కరణ సమాచారం"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"సంస్కరణ %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"ఓపెన్ సోర్స్ లైసెన్స్‌లు"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"నోటిఫికేషన్‌లు"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"జోడింపు పరిమితిని చేరుకున్నారు"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"జోడింపుని లోడ్ చేయడంలో విఫలమైంది."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"పరిచయాలకు జోడించాలా?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"పరిచయాన్ని జోడించు"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"విషయం"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"విషయం: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"పరిచయ కార్డ్‌ను లోడ్ చేస్తోంది"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"పరిచయ కార్డ్‌ను లోడ్ చేయడం సాధ్యపడలేదు"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"పరిచయ కార్డ్‌ను వీక్షించండి"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> పరిచయాలు</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> పరిచయం</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"పరిచయ కార్డ్‌లు"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"పుట్టినరోజు"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"గమనికలు"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"సందేశాన్ని ఫార్వార్డ్ చేయండి"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ప్రత్యుత్తరం"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS నిలిపివేయబడింది"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"పంపడానికి, సందేశ సేవను డిఫాల్ట్ SMS అనువర్తనంగా సెట్ చేయండి"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"సందేశ సేవను డిఫాల్ట్ SMS అనువర్తనంగా సెట్ చేయండి"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"మార్చు"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"సందేశాలు స్వీకరించడానికి, సందేశ సేవను డిఫాల్ట్ SMS అనువర్తనంగా సెట్ చేయండి"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS సందేశాలు పంపడానికి ప్రాధాన్య SIM ఏదీ ఎంచుకోలేదు"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"ఈ అనువర్తనాన్ని పరికరం యజమాని అనుమతించలేదు."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"సరే"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"సంభాషణలో చాలా ఎక్కువ మంది పాల్గొన్నారు"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">పరిచయాలు చెల్లవు</item>
+ <item quantity="one">పరిచయం చెల్లదు</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"కెమెరా చిత్రాన్ని లోడ్ చేయడం సాధ్యపడలేదు"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"మీరు: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"చిత్తుప్రతి"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"మీరు కొత్త సంభాషణను ప్రారంభిస్తే, అది ఇక్కడ జాబితా కావడం మీకు కనిపిస్తుంది"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"ఆర్కైవ్ చేసిన సంభాషణలు ఇక్కడ కనిపిస్తాయి"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"సంభాషణలను లోడ్ చేస్తోంది…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"చిత్రం"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"ఆడియో క్లిప్"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"వీడియో"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"పరిచయ కార్డ్"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"చర్య రద్దు చేయి"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"మళ్లీ ప్రయత్నించు"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"కొత్త సందేశాన్ని ప్రారంభించడానికి పరిచయం పేరు లేదా ఫోన్ నంబర్‌ని నమోదు చేయండి"</string>
+ <string name="action_block" msgid="9032076625645190136">"బ్లాక్ చేయి"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g>ని బ్లాక్ చేయి"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g>ని అన్‌బ్లాక్ చేయి"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g>ని బ్లాక్ చేయాలా?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"మీరు ఈ నంబర్ నుండి సందేశాలను స్వీకరించడం కొనసాగుతుంది కానీ ఇకపై వాటి గురించి తెలియజేయబడదు. ఈ సంభాషణ ఆర్కైవ్ చేయబడుతుంది."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"బ్లాక్ చేయబడిన పరిచయాలు"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"అన్‌బ్లాక్ చేయి"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"బ్లాక్ చేయబడిన పరిచయాలు"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"పత్రం లైబ్రరీ నుండి చిత్రాన్ని ఎంచుకోండి"</string>
+ <string name="sending_message" msgid="6363584950085384929">"సందేశం పంపబడుతోంది"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"సందేశం పంపబడింది"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"సెల్యులార్ డేటా ఆఫ్ చేయబడింది. మీ సెట్టింగ్‌లను తనిఖీ చేయండి."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"ఎయిర్‌ప్లైన్ మోడ్‌లో సందేశాలు పంపడం సాధ్యపడదు"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"సందేశం పంపడం సాధ్యపడలేదు"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"సందేశం డౌన్‌లోడ్ చేయబడింది"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"సెల్యులార్ డేటా ఆఫ్ చేయబడింది. మీ సెట్టింగ్‌లను తనిఖీ చేయండి."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ఎయిర్‌ప్లైన్ మోడ్‌లో సందేశాలను డౌన్‌లోడ్ చేయడం సాధ్యపడదు"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"సందేశాన్ని డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"సున్నా"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ఒకటి"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"రెండు"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"మూడు"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"నాలుగు"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ఐదు"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"ఆరు"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"ఏడు"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"ఎనిమిది"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"తొమ్మిది"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g>తో సందేశం పంపలేరు, దోషం <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"తెలియని క్యారియర్‌తో సందేశం పంపలేరు, లోపం <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"ఫార్వా.: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"సందేశం పంపబడలేదు: నెట్‌వర్క్‌లో సేవ సక్రియం కాలేదు"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"సందేశం పంపబడలేదు: గమ్యస్థాన చిరునామా చెల్లదు"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"సందేశం పంపబడలేదు: సందేశం చెల్లదు"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"సందేశం పంపబడలేదు: మద్దతు లేని కంటెంట్"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"సందేశం పంపబడలేదు: మద్దతు లేని సందేశం"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"సందేశం పంపబడలేదు: చాలా పెద్దది"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"కొత్త సందేశం"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"వీక్షించండి"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"చిత్రం"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"సముచిత అనువర్తనాన్ని కనుగొనడం సాధ్యపడలేదు"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"స్వీకర్తను తీసివేయండి"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"కొత్త సందేశం"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"రద్దు చేయి"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"ప్రాప్యత స్థానాన్ని సవరించు"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"సెట్ చేయలేదు"</string>
+ <string name="apn_name" msgid="1572691851070894985">"పేరు"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS ప్రాక్సీ"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS పోర్ట్"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN రకం"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APNను తొలగించు"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"కొత్త APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"సేవ్ చేయి"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"విస్మరించు"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"పేరు ఫీల్డ్ ఖాళీగా ఉండకూడదు."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN ఖాళీగా ఉండకూడదు."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC ఫీల్డ్‌లో తప్పనిసరిగా 3 అంకెలు ఉండాలి."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC ఫీల్డ్‌లో తప్పనిసరిగా 2 లేదా 3 అంకెలు ఉండాలి."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"డిఫాల్ట్ APN సెట్టింగ్‌లను పునరుద్ధరిస్తోంది."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"డిఫాల్ట్‌కు రీసెట్ చేయి"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"డిఫాల్ట్ APN సెట్టింగ్‌లను రీసెట్ చేయడం పూర్తయింది."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"శీర్షిక లేదు"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ప్రాప్యత స్థానం పేర్లు"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNలు"</string>
+ <string name="menu_new" msgid="8286285392706532511">"కొత్త APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"ప్రాప్యత స్థానం పేరు సెట్టింగ్‌లు ఈ వినియోగదారుకి అందుబాటులో లేవు"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"క్లిప్‌బోర్డ్‌కు కాపీ చేయాలా?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"కాపీ చేయి"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g>కి"</string>
+ <string name="general_settings" msgid="5409336577057897710">"సాధారణం"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"అధునాతనం"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"సాధారణ సెట్టింగ్‌లు"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"అధునాతన సెట్టింగ్‌లు"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"స్వీకర్తలందరికీ వేర్వేరుగా SMS సందేశాలను పంపండి. ఏవైనా ప్రత్యుత్తరాలను మీరు మాత్రమే పొందుతారు"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"స్వీకర్తలందరికీ ఒకే MMS పంపండి"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"తెలియని నంబర్"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"కొత్త సందేశం"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"కొత్త సందేశం."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM ఎంపిక సాధనం"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> ఎంచుకోబడింది, SIM ఎంపిక"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"విషయాన్ని సవరించు"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM ఎంచుకోండి లేదా విషయాన్ని సవరించండి"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"ఆడియోను రికార్డ్ చేయడానికి తాకి, అలాగే ఉంచండి"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"కొత్త సంభాషణను ప్రారంభించండి"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"సందేశ సేవ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"సందేశ సేవ జాబితా"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"సందేశ సేవ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"కొత్త సందేశం"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"సంభాషణ జాబితా"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"సంభాషణలను లోడ్ చేస్తోంది"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"సందేశాలు లోడ్ అవుతున్నాయి"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"మరిన్ని సంభాషణలను వీక్షించండి"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"మరిన్ని సందేశాలను వీక్షించండి"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"సంభాషణ తొలగించబడింది"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"సంభాషణ తొలగించబడింది. విభిన్న సందేశ సేవ సంభాషణను చూపడానికి తాకండి"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"బ్లాక్ చేయబడ్డారు"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"అన్‌బ్లాక్ చేయబడ్డారు"</string>
+ <string name="db_full" msgid="8459265782521418031">"నిల్వ స్థలంలో ఖాళీ తక్కువగా ఉంది. కొంత డేటా కోల్పోవచ్చు."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"జోడింపులను ఎంచుకోండి"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"ఎంపికను నిర్ధారించు"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ఎంచుకోబడింది/ఎంచుకోబడ్డాయి"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"దయచేసి ఒకటి లేదా అంతకంటే ఎక్కువ జోడింపులను తీసివేసి, మళ్లీ ప్రయత్నించండి."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"మీరు మీ సందేశం పంపడాన్ని ప్రయత్నించవచ్చు, కానీ ఒకటి లేదా అంతకంటే ఎక్కువ జోడింపులను తీసివేస్తే మినహా అది బట్వాడా చేయబడదు."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"మీరు ఒక సందేశానికి ఒక వీడియోను మాత్రమే పంపగలరు. దయచేసి అదనపు వీడియోలను తీసివేసి, ఆపై మళ్లీ ప్రయత్నించండి."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"సందేశ సేవ జోడింపును లోడ్ చేయడంలో విఫలమైంది."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ఏదేమైనా పంపు"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"సంభాషణను ప్రారంభించలేరు"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ఎంచుకోబడింది"</string>
+</resources>
diff --git a/res/values-th/arrays.xml b/res/values-th/arrays.xml
new file mode 100644
index 0000000..749516e
--- /dev/null
+++ b/res/values-th/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"ไม่มีเรื่อง"</item>
+ <item msgid="272485471009191934">"ไม่มีชื่อเรื่อง"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ใช่"</item>
+ <item msgid="6049132459802288033">"ไม่ใช่"</item>
+ <item msgid="3084376867445867895">"ตกลง"</item>
+ <item msgid="3155097332660174689">"อิอิ"</item>
+ <item msgid="2611328818571146775">"ขอบคุณ"</item>
+ <item msgid="4881335087096496747">"ฉันเห็นด้วย"</item>
+ <item msgid="2422296858597420738">"เยี่ยมเลย"</item>
+ <item msgid="4805581752819452687">"เดินทางอยู่"</item>
+ <item msgid="4746700499431366214">"ตกลง ฉันจะติดต่อกลับมาในภายหลัง"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..d191be9
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"การรับส่งข้อความ"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"การรับส่งข้อความ"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"เลือกการสนทนา"</string>
+ <string name="action_settings" msgid="1329008122345201684">"การตั้งค่า"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"ส่งข้อความ"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"เพิ่มไฟล์แนบ"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"ความช่วยเหลือ"</string>
+ <string name="welcome" msgid="2857560951820802321">"ยินดีต้อนรับ"</string>
+ <string name="skip" msgid="7238879696319945853">"ข้าม"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"ถัดไป &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"ถัดไป"</string>
+ <string name="exit" msgid="1905187380359981199">"ออก"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"การตั้งค่า &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"การตั้งค่า"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"การรับส่งข้อความต้องใช้สิทธิ์เข้าถึง SMS, โทรศัพท์ และรายชื่อติดต่อ"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"คุณสามารถเปลี่ยนแปลงสิทธิ์ได้ในการตั้งค่า &gt; แอป &gt; การรับส่งข้อความ &gt; สิทธิ์"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"คุณสามารถเปลี่ยนแปลงสิทธิ์ได้ในการตั้งค่า แอป การรับส่งข้อความ และสิทธิ์"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"ผู้ที่ติดต่อบ่อย"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"รายชื่อติดต่อทั้งหมด"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"ส่งไปยัง <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"จับภาพหรือวิดีโอ"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"เลือกรูปภาพจากอุปกรณ์นี้"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"บันทึกเสียง"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"เลือกรูปภาพ"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"เลือกสื่อแล้ว"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"ไม่มีการเลือกสื่อ"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"เลือกไว้ <xliff:g id="COUNT">%d</xliff:g> รายการ"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"ภาพเมื่อวันที่ <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" msgid="8142175029053370662">"ภาพ"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"บันทึกเสียง"</string>
+ <string name="action_share" msgid="2143483844803153871">"แชร์"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"เดี๋ยวนี้เอง"</string>
+ <string name="posted_now" msgid="867560789350406701">"ตอนนี้"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> นาที</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> นาที</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ชั่วโมง</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ชั่วโมง</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> วัน</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> วัน</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> สัปดาห์</item>
+ <item quantity="one">1 สัปดาห์</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> เดือน</item>
+ <item quantity="one">1 เดือน</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ปี</item>
+ <item quantity="one">1 ปี</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"ข้อความคลาส 0"</string>
+ <string name="save" msgid="5081141452059463572">"บันทึก"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"อุปกรณ์มีพื้นที่เหลือน้อย การรับส่งข้อความจะลบข้อความเก่าโดยอัตโนมัติเพื่อเพิ่มพื้นที่ว่าง"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"พื้นที่จัดเก็บเหลือน้อย"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"การรับส่งข้อความอาจไม่ส่งหรือรับข้อความจนกว่าจะมีพื้นที่เพิ่มมากขึ้นบนอุปกรณ์"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"พื้นที่จัดเก็บ SMS เหลือน้อย คุณอาจต้องลบข้อความ"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"ยืนยันหมายเลขโทรศัพท์"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"ขั้นตอนที่ทำเพียงครั้งเดียวนี้จะช่วยให้การรับส่งข้อความส่งข้อความของกลุ่มได้อย่างถูกต้อง"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"หมายเลขโทรศัพท์"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"ลบข้อความทั้งหมดที่มีสื่อ"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"ลบข้อความที่เก่ากว่า <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"ลบข้อความที่เก่ากว่า <xliff:g id="DURATION">%s</xliff:g> โดยอัตโนมัติ"</string>
+ <string name="ignore" msgid="7063392681130898793">"ไม่สนใจ"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"ลบข้อความทั้งหมดที่มีสื่อใช่ไหม"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"ลบข้อความที่เก่ากว่า <xliff:g id="DURATION">%s</xliff:g> ใช่ไหม"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"ลบข้อความที่เก่ากว่า <xliff:g id="DURATION">%s</xliff:g> และเปิดการลบอัตโนมัติใช่ไหม"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> กล่าว"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"คุณกล่าว"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"ข้อความจาก <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"คุณส่งข้อความแล้ว"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"กำลังส่ง…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"ไม่สามารถส่ง แตะเพื่อลองอีกครั้ง"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"ไม่สามารถส่ง ลองอีกครั้ง…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"ส่งอีกครั้งหรือลบ"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"โปรดโทรไปที่ฝ่ายบริการฉุกเฉิน ระบบไม่สามารถส่งข้อความของคุณได้ในขณะนี้"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"ล้มเหลว"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"มีข้อความ MMS ใหม่ให้ดาวน์โหลด"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"ข้อความ MMS ใหม่"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ไม่สามารถดาวน์โหลดได้"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"แตะเพื่อลองอีกครั้ง"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"แตะเพื่อดาวน์โหลด"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ดาวน์โหลดหรือลบ"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"กำลังดาวน์โหลด…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"ข้อความหมดอายุหรือไม่สามารถใช้ได้"</string>
+ <string name="mms_info" msgid="3402311750134118165">"ขนาด: <xliff:g id="MESSAGESIZE">%1$s</xliff:g> หมดอายุ: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"ไม่สามารถส่งได้ ผู้รับไม่ถูกต้อง"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"ไม่ได้เปิดใช้บริการบนเครือข่าย"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"ไม่สามารถส่งได้เนื่องจากปัญหาของเครือข่าย"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"ข้อความหมดอายุหรือไม่สามารถใช้ได้"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(ไม่มีเรื่อง)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"ไม่ทราบผู้ส่ง"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ส่งแล้ว"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"ไม่สามารถดาวน์โหลดข้อความ <xliff:g id="SUBJECT">%1$s</xliff:g> จาก <xliff:g id="FROM">%2$s</xliff:g>"</string>
+ <string name="low_memory" msgid="5300743415198486619">"ไม่สามารถดำเนินการฐานข้อมูลให้เสร็จสิ้นเนื่องจากมีหน่วยความจำเหลือน้อย"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"ไม่ได้ส่งข้อความ"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"ไม่ได้ส่งข้อความบางรายการในการรับส่งข้อความ"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ข้อความใน <xliff:g id="CONVERSATIONS">%d</xliff:g> การสนทนา</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> ข้อความใน 1 การสนทนา</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"ข้อความไม่ได้รับการดาวน์โหลด"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"ไม่ได้ดาวน์โหลดข้อความบางรายการในการรับส่งข้อความ"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ข้อความใน <xliff:g id="CONVERSATIONS">%d</xliff:g> การสนทนา</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> ข้อความใน 1 การสนทนา</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"ไม่ได้ส่งข้อความถึง <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"โปรดโทรไปที่ฝ่ายบริการฉุกเฉิน ระบบไม่สามารถส่งข้อความของคุณไปยัง <xliff:g id="NUMBER">%1$s</xliff:g> ได้ในขณะนี้"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ข้อความใหม่</item>
+ <item quantity="one">ข้อความใหม่</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"เริ่ม"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"กล้องไม่สามารถใช้ได้"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"กล้องไม่สามารถใช้ได้"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"การจับภาพวิดีโอไม่พร้อมใช้งาน"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"ไม่สามารถบันทึกสื่อได้"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"ไม่สามารถถ่ายภาพ"</string>
+ <string name="back" msgid="1477626055115561645">"กลับ"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"เก็บถาวรแล้ว"</string>
+ <string name="action_delete" msgid="4076795795307486019">"ลบ"</string>
+ <string name="action_archive" msgid="5437034800324083170">"เก็บถาวร"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"ยกเลิกการเก็บถาวร"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"ปิดการแจ้งเตือน"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"เปิดการแจ้งเตือน"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"เพิ่มรายชื่อติดต่อ"</string>
+ <string name="action_download" msgid="7786338136368564146">"ดาวน์โหลด"</string>
+ <string name="action_send" msgid="377635240181672039">"ส่ง"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"ลบ"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"ลบข้อความนี้ไหม"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"การดำเนินการนี้ไม่สามารถยกเลิกได้"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"ลบ"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">ลบการสนทนาเหล่านี้ใช่ไหม</item>
+ <item quantity="one">ลบการสนทนานี้ใช่ไหม</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"ลบ"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"ยกเลิก"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"ถึง"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"เลือกภาพหลายภาพ"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"ยืนยันการเลือก"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"ไม่สามารถบันทึกเสียงได้ ลองอีกครั้ง"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"ไม่สามารถเล่นเสียงได้ ลองอีกครั้ง"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"ไม่สามารถบันทึกเสียงได้ ลองอีกครั้ง"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"แตะค้างไว้"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"ภาพ"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"คลิปเสียง"</string>
+ <string name="notification_video" msgid="4331423498662606204">"วิดีโอ"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"บัตรติดต่อ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ดาวน์โหลด"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"ตอบกลับทาง SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"ตอบกลับผ่าน MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"ตอบ"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other">ผู้เข้าร่วม <xliff:g id="COUNT_1">%d</xliff:g> คน</item>
+ <item quantity="one">ผู้เข้าร่วม <xliff:g id="COUNT_0">%d</xliff:g> คน</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"ฉัน"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"บล็อกและเก็บผู้ติดต่อแล้ว"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"เลิกบล็อกและเลิกเก็บรายชื่อติดต่อแล้ว"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"เก็บแล้ว <xliff:g id="COUNT">%d</xliff:g> รายการ"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"เลิกเก็บแล้ว <xliff:g id="COUNT">%d</xliff:g> รายการ"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"ปิดการแจ้งเตือนแล้ว"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"เปิดการแจ้งเตือนแล้ว"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"ตั้งค่าเรียบร้อย แตะ \"ส่ง\" อีกครั้ง"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้นเรียบร้อยแล้ว"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">ยกเลิกไฟล์แนบ</item>
+ <item quantity="one">ยกเลิกไฟล์แนบ</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"ไฟล์แนบเสียง"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"เล่นไฟล์เสียงที่แนบอยู่"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"หยุดชั่วคราว"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"ข้อความจาก <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"ไม่สามารถรับข้อความจาก <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"ข้อความจาก <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"ข้อความที่ยังไม่ได้ส่งถึง <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"กำลังส่งข้อความถึง <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"ไม่สามารถส่งข้อความถึง <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"ส่งข้อความถึง <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"ไม่สามารถรับข้อความจาก <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g> <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"ข้อความจาก <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g> <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"ข้อความที่ยังไม่ได้ส่งถึง <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"กำลังส่งข้อความถึง <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"ไม่สามารถส่งข้อความถึง <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"ส่งข้อความถึง <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g> เวลา: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ข้อความล้มเหลว แตะเพื่อลองใหม่"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"การสนทนากับ <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"ลบเรื่อง"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"จับภาพวิดีโอ"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"จับภาพนิ่ง"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"ถ่ายภาพ"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"เริ่มบันทึกวิดีโอ"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"สลับไปยังกล้องแบบเต็มหน้าจอ"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"สลับระหว่างกล้องหน้าและกล้องหลัง"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"หยุดการบันทึกและแนบวิดีโอ"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"หยุดการบันทึกวิดีโอ"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"รูปภาพในการรับส่งข้อความ"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other">บันทึก <xliff:g id="QUANTITY_2">%d</xliff:g> ภาพไปยังอัลบั้ม \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" แล้ว</item>
+ <item quantity="one">บันทึก <xliff:g id="QUANTITY_0">%d</xliff:g> ภาพไปยังอัลบั้ม \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" แล้ว</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other">บันทึกวิดีโอ <xliff:g id="QUANTITY_2">%d</xliff:g> รายการไปยังอัลบั้ม \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" แล้ว</item>
+ <item quantity="one">บันทึกวิดีโอ <xliff:g id="QUANTITY_0">%d</xliff:g> รายการไปยังอัลบั้ม \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" แล้ว</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">บันทึก <xliff:g id="QUANTITY_2">%d</xliff:g> ไฟล์แนบไปยังอัลบั้ม \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" แล้ว</item>
+ <item quantity="one">บันทึก <xliff:g id="QUANTITY_0">%d</xliff:g> ไฟล์แนบไปยังอัลบั้ม \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" แล้ว</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">บันทึก <xliff:g id="QUANTITY_1">%d</xliff:g> ไฟล์แนบไปยัง \"ดาวน์โหลด\" แล้ว</item>
+ <item quantity="one">บันทึก <xliff:g id="QUANTITY_0">%d</xliff:g> ไฟล์แนบไปยัง \"ดาวน์โหลด\" แล้ว</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">บันทึกไฟล์แนบ <xliff:g id="QUANTITY_1">%d</xliff:g> รายการแล้ว</item>
+ <item quantity="one">บันทึกไฟล์แนบ <xliff:g id="QUANTITY_0">%d</xliff:g> รายการแล้ว</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">ไม่สามารถบันทึกไฟล์แนบ <xliff:g id="QUANTITY_1">%d</xliff:g> รายการ</item>
+ <item quantity="one">ไม่สามารถบันทึกไฟล์แนบ <xliff:g id="QUANTITY_0">%d</xliff:g> รายการ</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"ไฟล์แนบของ MMS ที่บันทึกไว้"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"การตั้งค่า"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"เก็บถาวรแล้ว"</string>
+ <string name="action_close" msgid="1840519376200478419">"ปิด"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"ขั้นสูง"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"แก้ไขข้อบกพร่อง"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"การแจ้งเตือน"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"เสียง"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"เงียบ"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"สั่น"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"ถูกบล็อก"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"รายงานการส่ง SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"ขอรายงานการส่ง SMS แต่ละครั้ง"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"เรียกอัตโนมัติ"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"เรียกดูข้อความ MMS อัตโนมัติ"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"เรียกข้อความอัตโนมัติขณะโรมมิ่ง"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"ดึง MMS โดยอัตโนมัติเมื่อโรมมิ่ง"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"การรับส่งข้อความแบบกลุ่ม"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"ใช้ MMS ส่งข้อความเดียวเมื่อมีผู้รับหลายราย"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"แอป SMS เริ่มต้น"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"แอป SMS เริ่มต้น"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"หมายเลขโทรศัพท์ของคุณ"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"ไม่รู้จัก"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"เสียงข้อความขาออก"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"ถ่ายโอน SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"ถ่ายโอนข้อมูลดิบของ SMS ที่ได้รับลงในไฟล์พื้นที่จัดเก็บข้อมูลภายนอก"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"ถ่ายโอน MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"ถ่ายโอนข้อมูลดิบของ MMS ที่ได้รับลงในไฟล์พื้นที่จัดเก็บข้อมูลภายนอก"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"การแจ้งเตือนแบบไร้สาย"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"ตัวเลือกข้อความ"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"คัดลอกข้อความ"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"ดูรายละเอียด"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"ลบ"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"ส่งต่อ"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"รายละเอียดข้อความ"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"ประเภท: "</string>
+ <string name="text_message" msgid="7415419755252205721">"ข้อความ"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"ข้อความมัลติมีเดีย"</string>
+ <string name="from_label" msgid="1947831848146564875">"จาก: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"ถึง: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ส่งแล้ว: "</string>
+ <string name="received_label" msgid="4442494712757995203">"ได้รับ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"เรื่อง: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"ขนาด: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ลำดับความสำคัญ: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"ซิม: "</string>
+ <string name="priority_high" msgid="728836357310908368">"สูง"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"ปกติ"</string>
+ <string name="priority_low" msgid="7398724779026801851">"ต่ำ"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"ซิม <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"ที่อยู่ผู้ส่งที่ซ่อนไว้"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"ไม่สามารถส่งข้อความเสียงขณะโหลดเอกสารแนบ"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"ไม่สามารถโหลดไฟล์แนบได้ ลองอีกครั้ง"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"เครือข่ายยังไม่พร้อม ลองอีกครั้ง"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"ลบข้อความ"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"สลับระหว่างการป้อนข้อความและตัวเลข"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"เพิ่มผู้เข้าร่วมเพิ่มเติม"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"ยืนยันผู้เข้าร่วม"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"เริ่มต้นการสนทนาใหม่"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"เลือกรายการนี้"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"เล่นวิดีโอ"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"ผู้คนและตัวเลือก"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"แก้ไขข้อบกพร่อง"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"ผู้คนและตัวเลือก"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"ทั่วไป"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"ผู้เข้าร่วมการสนทนานี้"</string>
+ <string name="action_call" msgid="6596167921517350362">"โทรออก"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"ส่งข้อความ"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"ส่งข้อความ&lt;br/&gt;&lt;small&gt;จาก <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">ส่งรูปภาพ</item>
+ <item quantity="one">ส่งรูปภาพ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">ส่งไฟล์เสียง</item>
+ <item quantity="one">ส่งไฟล์เสียง</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">ส่งวิดีโอ</item>
+ <item quantity="one">ส่งวิดีโอ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">ส่งการ์ดรายชื่อติดต่อ</item>
+ <item quantity="one">ส่งการ์ดรายชื่อติดต่อ</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">ส่งไฟล์แนบ</item>
+ <item quantity="one">ส่งไฟล์แนบ</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ไฟล์แนบพร้อมส่ง</item>
+ <item quantity="one">1 ไฟล์แนบพร้อมส่ง</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"ส่งความคิดเห็น"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"ดูใน Google Play สโตร์"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ข้อมูลเวอร์ชัน"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"เวอร์ชัน %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"สัญญาอนุญาตสำหรับโอเพนซอร์ส"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"การแจ้งเตือน"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"ถึงขีดจำกัดของไฟล์แนบแล้ว"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"ไม่สามารถโหลดไฟล์แนบได้"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"เพิ่มในรายชื่อติดต่อใช่ไหม"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"เพิ่มรายชื่อติดต่อ"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"เรื่อง"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"เรื่อง: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"กำลังโหลดบัตรติดต่อ"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"ไม่สามารถโหลดการ์ดรายชื่อติดต่อได้"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"ดูบัตรติดต่อ"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other">รายชื่อติดต่อ <xliff:g id="COUNT_1">%d</xliff:g> ราย</item>
+ <item quantity="one">รายชื่อติดต่อ <xliff:g id="COUNT_0">%d</xliff:g> ราย</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"การ์ดรายชื่อติดต่อ"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"วันเกิด"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"บันทึก"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"ส่งต่อข้อความ"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"ตอบ"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"ปิดใช้ SMS แล้ว"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"ในการส่งข้อความ ให้ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้น"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้น"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"เปลี่ยน"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"ในการรับข้อความ ให้ตั้งค่าการรับส่งข้อความเป็นแอป SMS ค่าเริ่มต้น"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"ยังไม่ได้เลือกซิมสำหรับการส่งข้อความ SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"เจ้าของอุปกรณ์ไม่อนุญาตแอปนี้"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ตกลง"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"มีผู้เข้าร่วมในการสนทนามากเกินไป"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">รายชื่อติดต่อไม่ถูกต้อง</item>
+ <item quantity="one">รายชื่อติดต่อไม่ถูกต้อง</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"ไม่สามารถโหลดภาพจากกล้องได้"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"คุณ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"ข้อความร่าง"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"เมื่อคุณเริ่มการสนทนาใหม่ คุณจะเห็นการสนทนาใหม่ที่นี่"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"การสนทนาที่เก็บไว้จะปรากฏที่นี่"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"กำลังโหลดการสนทนา…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"ภาพ"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"คลิปเสียง"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"วิดีโอ"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"บัตรติดต่อ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"เลิกทำ"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"ลองอีกครั้ง"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"ป้อนชื่อผู้ติดต่อหรือหมายเลขโทรศัพท์เพื่อเริ่มเขียนข้อความใหม่"</string>
+ <string name="action_block" msgid="9032076625645190136">"บล็อก"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"บล็อก <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"เลิกบล็อก <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"ต้องการบล็อก <xliff:g id="DESTINATION">%s</xliff:g> ไหม"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"คุณจะยังคงได้รับข้อความจากหมายเลขนี้ต่อไปแต่จะไม่ได้รับการแจ้งเตือนอีก ระบบจะเก็บการสนทนานี้ไว้"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"รายชื่อที่บล็อกไว้"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"เลิกบล็อก"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"รายชื่อที่บล็อกไว้"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"เลือกรูปภาพจากห้องสมุดเอกสาร"</string>
+ <string name="sending_message" msgid="6363584950085384929">"กำลังส่งข้อความ"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"ส่งข้อความแล้ว"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"ข้อมูลมือถือปิดอยู่ โปรดตรวจสอบการตั้งค่า"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"ไม่สามารถส่งข้อความในโหมดบนเครื่องบินได้"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"ไม่สามารถส่งข้อความนี้"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"ดาวน์โหลดข้อความแล้ว"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"ข้อมูลมือถือปิดอยู่ โปรดตรวจสอบการตั้งค่า"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ไม่สามารถดาวน์โหลดข้อความในโหมดบนเครื่องบินได้"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"ไม่สามารถดาวน์โหลดข้อความได้"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"ศูนย์"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"หนึ่ง"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"สอง"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"สาม"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"สี่"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"ห้า"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"หก"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"เจ็ด"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"แปด"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"เก้า"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"ไม่สามารถส่งข้อความผ่าน <xliff:g id="CARRIERNAME">%1$s</xliff:g> ข้อผิดพลาด <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"ไม่สามารถส่งข้อความผ่านผู้ให้บริการที่ไม่รู้จัก ข้อผิดพลาด <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"ส่งต่อ: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"ไม่มีการส่งข้อความ เพราะยังไม่เปิดใช้งานบริการบนเครือข่าย"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"ไม่มีการส่งข้อความ: ที่อยู่ปลายทางไม่ถูกต้อง"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"ไม่มีการส่งข้อความ: ข้อความไม่ถูกต้อง"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"ไม่มีการส่งข้อความ: ระบบไม่สนับสนุนเนื้อหา"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"ไม่มีการส่งข้อความ: ระบบไม่สนับสนุนข้อความ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"ไม่ได้ส่งข้อความ: ขนาดใหญ่เกินไป"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"ข้อความใหม่"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"ดู"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"รูปภาพ"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"ไม่สามารถหาแอปพลิเคชันที่เหมาะสม"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"นำผู้รับออก"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"ข้อความใหม่"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"ยกเลิก"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"แก้ไขจุดเข้าใช้งาน"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"ไม่ได้ตั้งค่า"</string>
+ <string name="apn_name" msgid="1572691851070894985">"ชื่อ"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"พร็อกซี MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"พอร์ต MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"ประเภท APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"ลบ APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN ใหม่"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"บันทึก"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"ยกเลิก"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"ต้องใส่ข้อมูลในช่องชื่อ"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"ต้องใส่ข้อมูล APN"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"ช่อง MCC ต้องเป็นเลข 3 หลัก"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"ช่อง MNC ต้องเป็นเลข 2 หรือ 3 หลัก"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"กำลังคืนการตั้งค่า APN เริ่มต้น"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"รีเซ็ตเป็นค่าเริ่มต้น"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"รีเซ็ตการตั้งค่า APN กลับเป็นค่าเริ่มต้นเรียบร้อยแล้ว"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"ไม่มีชื่อ"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"ชื่อจุดเข้าใช้งาน"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN ใหม่"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"การตั้งค่าจุดเข้าใช้งานไม่สามารถใช้ได้สำหรับผู้ใช้รายนี้"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"คัดลอกไปยังคลิปบอร์ดไหม"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"คัดลอก"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ถึง <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"ทั่วไป"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"ขั้นสูง"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"การตั้งค่าทั่วไป"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"การตั้งค่าขั้นสูง"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"ซิม \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"ส่งข้อความ SMS ส่วนบุคคลให้กับผู้รับทุกคน เฉพาะคุณที่จะได้รับการตอบกลับ"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"ส่ง MMS เดี่ยวให้กับผู้รับทุกคน"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"ไม่ทราบหมายเลข"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"ข้อความใหม่"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"ข้อความใหม่"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"ตัวเลือกซิม"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"เลือก <xliff:g id="SIM_0">%1$s</xliff:g> แล้ว ตัวเลือกซิม"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"แก้ไขเรื่อง"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"เลือกซิมหรือแก้ไขเรื่อง"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"แตะค้างไว้เพื่อบันทึกเสียง"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"เริ่มต้นการสนทนาใหม่"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"การรับส่งข้อความ"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"รายการสนทนาในการรับส่งข้อความ"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"การรับส่งข้อความ"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"ข้อความใหม่"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"รายการการสนทนา"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"กำลังโหลดการสนทนา"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"กำลังโหลดข้อความ"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"ดูการสนทนาเพิ่มเติม"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"ดูข้อความเพิ่มเติม"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"ลบการสนทนาแล้ว"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"ลบการสนทนาแล้ว แตะเพื่อแสดงการสนทนาอื่นๆ ในการรับส่งข้อความ"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"ถูกบล็อก"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"เลิกบล็อกแล้ว"</string>
+ <string name="db_full" msgid="8459265782521418031">"พื้นที่เก็บข้อมูลเหลือน้อย ข้อมูลบางอย่างอาจสูญหายไป"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"เลือกไฟล์แนบ"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"ยืนยันการเลือก"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"เลือกไว้ <xliff:g id="COUNT">%d</xliff:g> รายการ"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"โปรดนำไฟล์แนบออกอย่างน้อย 1 รายการ แล้วลองอีกครั้ง"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"คุณสามารถลองส่งข้อความ แต่ข้อความอาจส่งไม่ได้หากคุณไม่ได้นำไฟล์แนบออกอย่างน้อย 1 รายการ"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"คุณสามารถส่งวิดีโอได้ 1 รายการต่อข้อความเท่านั้น โปรดนำวิดีโอเพิ่มเติมออกและลองอีกครั้ง"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"การรับส่งข้อความไม่สามารถโหลดไฟล์แนบ"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"ส่งเลย"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"ไม่สามารถเริ่มการสนทนาได้"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"เลือก <xliff:g id="SELECTED_SIM">%s</xliff:g> แล้ว"</string>
+</resources>
diff --git a/res/values-tl/arrays.xml b/res/values-tl/arrays.xml
new file mode 100644
index 0000000..6f30a29
--- /dev/null
+++ b/res/values-tl/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"walang paksa"</item>
+ <item msgid="272485471009191934">"nosubject"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Oo"</item>
+ <item msgid="6049132459802288033">"Hindi"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Salamat"</item>
+ <item msgid="4881335087096496747">"Sang-ayon ako"</item>
+ <item msgid="2422296858597420738">"Ayos"</item>
+ <item msgid="4805581752819452687">"Papunta na ako"</item>
+ <item msgid="4746700499431366214">"OK, balikan na lang kita mamaya"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..dddb04b
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Pagmemensahe"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Pagmemensahe"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Pumili ng pag-uusap"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Mga Setting"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Magpadala ng Mensahe"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Magdagdag ng attachment"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Tulong"</string>
+ <string name="welcome" msgid="2857560951820802321">"Maligayang Pagdating"</string>
+ <string name="skip" msgid="7238879696319945853">"Laktawan"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Susunod &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Susunod"</string>
+ <string name="exit" msgid="1905187380359981199">"Lumabas"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Mga Setting &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Mga Setting"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Kailangan ng pahintulot ng Pagmemensahe sa SMS, Telepono at Mga Contact."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Maaari mong baguhin ang mga pahintulot sa Mga Setting &gt; Mga App &gt; Pagmemensahe &gt; Mga Pahintulot."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Maaari mong baguhin ang mga pahintulot sa Mga Setting, Mga App, Pagmemensahe, Mga Pahintulot."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Mga madalas makaugnayan"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Lahat ng contact"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Ipadala sa <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Kumuha ng mga larawan o video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Pumili ng mga larawan mula sa device na ito"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"I-record ang audio"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Pumili ng larawan"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Napili ang media."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Inalis sa pagkakapili ang media."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ang pinili"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"larawan <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" msgid="8142175029053370662">"larawan"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"I-record ang audio"</string>
+ <string name="action_share" msgid="2143483844803153871">"Ibahagi"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Ngayon lang"</string>
+ <string name="posted_now" msgid="867560789350406701">"Ngayon"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> na min</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> oras</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> na oras</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> araw</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> na araw</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> linggo</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> na linggo</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> buwan</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> na buwan</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> taon</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> na taon</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Class 0 na mensahe"</string>
+ <string name="save" msgid="5081141452059463572">"I-save"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Kaunti na lang ang espasyo ng device. Awtomatikong ide-delete ng Pagmemensahe ang mga mas lumang mensahe upang magbakante ng espasyo."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Nauubos na ang espasyo ng storage"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Maaaring hindi makatanggap o makapagpadala ng mga mensahe ang Pagmemensahe hanggang sa maging available ang higit pang espasyo sa iyong device."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Kaunti na ang storage ng SMS. Maaaring kailanganin mong magtanggal ng mga mensahe."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Kumpirmahin ang numero ng iyong telepono"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Titiyakin ng isang beses na hakbang na ito na maihahatid nang maayos ng Pagmemensahe ang iyong mga mensahe sa pangkat."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Numero ng telepono"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Tanggalin ang lahat ng mensahe na may media"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Tangglain ang mga mensaheng lampas na ng <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Awtomatikong i-delete ang mga mensaheng mas luma sa <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Huwag Pansinin"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"I-delete ang lahat ng mensaheng may media?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"I-delete ang mga mensaheng mas luma sa <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Awtomatikong i-delete ang mga mensaheng mas luma sa <xliff:g id="DURATION">%s</xliff:g> at i-on ang awtomatikong pag-delete?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"Sinabi ni <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Sinabi mo"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Mensahe mula kay <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Nagpadala ka ng mensahe"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Ipinapadala..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Hindi naipadala. Pindutin upang subukang muli.."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Hindi naipadala. Sinusubukang muli…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Ipadalang muli o i-delete"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Mangyaring mag-voice call sa mga pang-emergency na serbisyo. Sa ngayon, hindi maipadala ang iyong text message."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Hindi nagawa"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Bagong MMS message na ida-download"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Bagong MMS message"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Hindi mai-download"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Pindutin upang subukang muli"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Pindutin upang i-download"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"I-download o i-delete"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Nagda-download..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Nag-expire o hindi available ang mensahe"</string>
+ <string name="mms_info" msgid="3402311750134118165">"laki: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, expiration: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Hindi maipadala. Di-wasto ang tatanggap."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Hindi naka-activate sa network ang serbisyo"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Hindi maipadala dahil sa problema sa network"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Nag-expire o hindi available ang mensahe"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Walang paksa)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Hindi kilalang nagpadala"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Naihatid"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Hindi ma-download ang mensaheng <xliff:g id="SUBJECT">%1$s</xliff:g> mula kay <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Hindi makumpleto ang operation sa database dahil sa mababang memory"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Hindi naipadala ang mensahe"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Hindi naipadala sa Pagmemensahe ang ilang mensahe"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> mensahe sa <xliff:g id="CONVERSATIONS">%d</xliff:g> pag-uusap</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> na mensahe sa <xliff:g id="CONVERSATIONS">%d</xliff:g> na pag-uusap</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Hindi na-download ang mensahe"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Hindi na-download sa Pagmemensahe ang ilang mensahe"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> mensahe sa <xliff:g id="CONVERSATIONS">%d</xliff:g> pag-uusap</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> na mensahe sa <xliff:g id="CONVERSATIONS">%d</xliff:g> na pag-uusap</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Hindi naipadala ang mensahe sa <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Mangyaring mag-voice call sa mga pang-emergency na serbisyo. Sa ngayon, hindi maipadala ang iyong text message sa <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> bagong mensahe</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> na bagong mensahe</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Magsimula"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Hindi available ang camera"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Hindi available ang camera"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Hindi available ang pagkuha ng video"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Hindi mai-save ang media"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Hindi makakakuha ng larawan"</string>
+ <string name="back" msgid="1477626055115561645">"Bumalik"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Naka-archive"</string>
+ <string name="action_delete" msgid="4076795795307486019">"I-delete"</string>
+ <string name="action_archive" msgid="5437034800324083170">"I-archive"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Alisin sa archive"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"I-off ang mga notification"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"I-on ang mga notification"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Magdagdag ng contact"</string>
+ <string name="action_download" msgid="7786338136368564146">"I-download"</string>
+ <string name="action_send" msgid="377635240181672039">"Ipadala"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"I-delete"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"I-delete ang mensaheng ito?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Hindi maaaring bawiin ang pagkilos na ito."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"I-delete"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">I-delete ang mga pag-uusap na ito?</item>
+ <item quantity="other">I-delete ang mga pag-uusap na ito?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Tanggalin"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Kanselahin"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Para kay"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Pumili ng maramihang larawan"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Kumpirmahin ang pinili"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Hindi mai-record ang audio. Subukang muli."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Hindi mai-play ang audio. Subukang muli."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Hindi mai-save ang audio. Subukang muli."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Pindutin nang matagal"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Larawan"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio clip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Contact card"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"I-download"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Tumugon sa pamamagitan ng SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Tumugon sa pamamagitan ng MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Tumugon"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> kalahok</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> na kalahok</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ako"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Na-block at na-archive ang contact"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Naka-unblock at hindi naka-archive ang contact"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ang na-archive"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ang inalis sa pagkaka-archive"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Naka-off ang mga notification"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Naka-on ang mga notification"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Handa na ang lahat. Pindutin muli ang Ipadala."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Matagumpay na naitakda ang Pagmemensahe bilang default na SMS app."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">I-discard ang mga attachment</item>
+ <item quantity="other">I-discard ang mga attachment</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Attachment na audio"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"I-play ang attachment na audio"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"I-pause"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Mensahe mula kay <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Hindi naipadalang mensahe mula kay <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Mensahe mula kay <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Hindi naipadalang mensahe kay <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Ipinapadala ang mensahe kay <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Hindi naipadalang mensahe kay <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Mensahe kay <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Hindi naipadalang mensahe mula kay <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Mensahe mula kay <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Hindi naipadalang mensahe sa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Ipinapadala ang mensahe sa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Hindi naipadalang mensahe sa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Mensahe sa <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Oras: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Hindi naipadalang mensahe. Pindutin upang subukang muli."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Pag-uusap kasama sina <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"I-delete ang paksa"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Kumuha ng video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Kumuha ng still na larawan"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Kumuha ng larawan"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Simulan ang pag-record ng video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Lumipat sa camera na full screen"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Lumipat sa pagitan ng camera sa harap at likod"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Ihinto ang pagre-record at i-attach ang video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Itigil ang pagre-record ng video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Mga larawan sa pagmemensahe"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> larawan ang naka-save sa album na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> na larawan ang naka-save sa album na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> video ang naka-save sa album na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> na video ang naka-save sa album na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> attachment ang naka-save sa album na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> na attachment ang naka-save sa album na \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> attachment ang naka-save sa \"Mga Download\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> na attachment ang naka-save sa \"Mga Download\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> attachment ang na-save</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> na attachment ang na-save</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Hindi ma-save ang <xliff:g id="QUANTITY_1">%d</xliff:g> attachment</item>
+ <item quantity="other">Hindi ma-save ang <xliff:g id="QUANTITY_1">%d</xliff:g> na attachment</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Na-save na MMS attachment"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Mga Setting"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Naka-archive"</string>
+ <string name="action_close" msgid="1840519376200478419">"Isara"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Advanced"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"I-debug"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Mga Notification"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Tunog"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Naka-silent"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Mag-vibrate"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Naka-block"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Mga ulat sa pagpapadala ng SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Humiling ng ulat sa paghahatid para sa bawat SMS na iyong ipinapadala"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"I-auto-retrieve"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Awtomatikong kunin ang mga mensaheng MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Awtomatikong pagkuha sa roaming"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Awtomatikong kunin ang MMS kapag naka-roaming"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Panggrupong pagpapadala ng mensahe"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Gamitin ang MMS upang magpadala ng iisang mensahe kapag may maraming recipient"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Default na SMS app"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Default na SMS app"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Numero ng iyong telepono"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Hindi kilala"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Mga tunog ng papalabas na mensahe"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Itapon ang SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Itapon ang raw data ng natanggap na SMS sa file ng external storage"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Itapon ang MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Itapon ang raw data ng natanggap na MMS sa file ng external storage"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Mga wireless na alerto"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Mga opsyon sa mensahe"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopyahin ang text"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Tingnan ang mga detalye"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"I-delete"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Ipasa"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Mga detalye ng mensahe"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Uri: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Text message"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedia message"</string>
+ <string name="from_label" msgid="1947831848146564875">"Mula kay: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Para kay: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Ipinadala: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Natanggap: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Paksa: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Laki: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Priyoridad: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Mataas"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Mababa"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Nakatago ang address ng nagpadala"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Hindi maaaring magpadala ng mensahe habang naglo-load ng mga attachment."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Hindi mai-load ang attachment. Subukang muli."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Hindi pa handa ang network. Subukang muli."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"I-delete ang text"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Lumipat sa pagitan ng paglagay ng text at mga numero"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Magdagdag ng higit pang mga kalahok"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Kumpirmahin ang mga kalahok"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Magsimula ng bagong pag-uusap"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Piliin ang item na ito"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"I-play ang video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Mga tao at opsyon"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"I-debug"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Mga tao at opsyon"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Pangkalahatan"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Mga tao sa pag-uusap na ito"</string>
+ <string name="action_call" msgid="6596167921517350362">"Tumawag"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Magpadala ng mensahe"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Magpadala ng mensahe&lt;br/&gt;&lt;small&gt;mula sa <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Ipadala ang mga larawan</item>
+ <item quantity="other">Ipadala ang mga larawan</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Ipadala ang mga audio</item>
+ <item quantity="other">Ipadala ang mga audio</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Ipadala ang mga video</item>
+ <item quantity="other">Ipadala ang mga video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Ipadala ang mga contact card</item>
+ <item quantity="other">Ipadala ang mga contact card</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Ipadala ang mga attachment</item>
+ <item quantity="other">Ipadala ang mga attachment</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> attachment ang handa nang ipadala</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> na attachment ang handa nang ipadala</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Magpadala ng feedback"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Tingnan sa Google Play Store"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Impormasyon sa bersyon"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Bersyon %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Mga open source na lisensya"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Mga Notification"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Naabot na ang limitasyon ng attachment"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Hindi na-load ang attachment."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Idagdag sa Mga Contact?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Magdagdag ng Contact"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Subject"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Subject: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Nilo-load ang contact card"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Hindi mai-load ang contact card"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Tingnan ang contact card"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> contact</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> na contact</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Mga contact card"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Kaarawan"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Mga Tala"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Ipasa ang mensahe"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Tumugon"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Naka-disable ang SMS"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Upang ipadala, itakda ang Pagmemensahe bilang default na SMS app"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Itakda ang Pagmemensahe bilang default na SMS app"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Baguhin"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Upang makatanggap ng mga mensahe, itakda ang Pagmemensahe bilang default na SMS app"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Walang piniling gustong SIM para sa pagpapadala ng mga mensaheng SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Hindi pinapayagan ang app na ito ng may-ari ng device."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Masyadong maraming kalahok sa isang pag-uusap"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Mga di-wastong contact</item>
+ <item quantity="other">Mga di-wastong contact</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Hindi mai-load ang larawan ng camera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ikaw: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Draft"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Sa sandaling magsimula ka ng bagong pag-uusap, makikita mo ito na nakalista rito"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Lumalabas dito ang mga naka-archive na pag-uusap"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Nilo-load ang mga pag-uusap…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Larawan"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio clip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Card sa pakikipag-ugnayan"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"I-undo"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Subukang muli"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Maglagay ng pangalan ng contact o numero ng telepono upang magsimula ng isang bagong mensahe"</string>
+ <string name="action_block" msgid="9032076625645190136">"I-block"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"I-block ang <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Alisin sa pagkaka-block ang <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"I-block ang <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Patuloy kang makakatanggap ng mga mensahe mula sa numerong ito ngunit hindi ka na aabisuhan. Maa-archive ang pag-uusap na ito."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Mga na-block na contact"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ALISIN SA PAGKAKA-BLOCK"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Mga na-block na contact"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Pumili ng larawan mula sa library ng dokumento"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Ipinapadala ang mensahe"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Naipadala na ang mensahe"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Naka-off ang cellular data. Suriin ang iyong mga setting."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Hindi maaaring magpadala ng mga mensahe sa Airplane mode"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Hindi maipadala ang mensahe"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Na-download na ang mensahe"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Naka-off ang cellular data. Tingnan ang iyong mga setting."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Hindi maaaring mag-download ng mga mensahe sa Airplane mode"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Hindi ma-download ang mensahe"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Zero"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Isa"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Dalawa"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Tatlo"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Apat"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Lima"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Anim"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Pito"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Walo"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Siyam"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Hindi maipadala ang mensahe gamit ang <xliff:g id="CARRIERNAME">%1$s</xliff:g>, error na <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Hindi maipadala ang mensahe gamit ang hindi nakikilalang carrier, error na <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Hindi napadala ang mensahe: hindi naka-activate sa network ang serbisyo"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Hindi naipadala ang mensahe: di-wastong patutunguhang address"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Hindi naipadala ang mensahe: di-wastong mensahe"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Hindi naipadala ang mensahe: hindi sinusuportahang content"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Hindi naipadala ang mensahe: hindi sinusuportahang mensahe"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Hindi napadala ang mensahe: masyadong malaki"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Bagong mensahe"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Tingnan"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Larawan"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Hindi makahanap ng naaangkop na application"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Alisin ang tatanggap"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Bagong mensahe"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Kanselahin"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"I-edit ang access point"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Hindi nakatakda"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Pangalan"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy ng MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Port ng MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Uri ng APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"I-delete ang APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Bagong APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"I-save"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"I-discard"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Hindi maaaring walang laman ang field ng Pangalan."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Hindi maaaring walang laman ang APN."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Dapat na 3 digit ang field na MCC."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Dapat na 2 o 3 digit ang field ng MNC."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Nire-restore ang default na mga setting ng APN"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"I-reset sa default"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Nakumpleto na ang pag-reset sa default na mga setting ng APN."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Walang pamagat"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Mga Access Point Name"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Mga APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Bagong APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Hindi available ang mga setting ng Access Point Name para sa user na ito"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopyahin sa clipboard?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopyahin"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"sa <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Pangkalahatan"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Advanced"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Mga pangkalahatang setting"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Mga advanced na setting"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Magpadala ng mga indibidwal na mensaheng SMS sa lahat ng tatanggap. Ikaw lang ang makakatanggap ng anumang mga tugon"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Magpadala ng iisang MMS sa lahat ng tatanggap"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Hindi kilalang numero"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Bagong mensahe"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Bagong mensahe."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM selector"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Pinili ang <xliff:g id="SIM_0">%1$s</xliff:g>, selector ng SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"I-edit ang paksa"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Pumili ng SIM o i-edit ang paksa"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Pindutin nang matagal upang i-record ang audio"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Magsimula ng bagong pag-uusap"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Pagmemensahe"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Listahan ng Pagmemensahe"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Pagmemensahe"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Bagong mensahe"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Listahan ng pag-uusap"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Nilo-load ang mga pag-uusap"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Nilo-load ang mga mensahe"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Tumingin ng higit pang pag-uusap"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Tingnan ang higit pang mga mensahe"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Na-delete na ang pag-uusap"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Na-delete na ang pag-uusap. Pindutin upang magpakita ng iba\'t ibang pag-uusap sa Pagmemensahe"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Na-block"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Na-unblock"</string>
+ <string name="db_full" msgid="8459265782521418031">"Kaunti na lang ang espasyo sa storage. Maaaring mawala ang ilang data."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Pumili ng mga attachment"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Kumpirmahin ang napili"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ang napili"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Mangyaring mag-alis ng isa o higit pang mga attachment at subukang muli."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Maaari mong subukang ipadala ang iyong mensahe, ngunit maaaring hindi ito maipadala maliban kung mag-aalis ka ng isa o higit pang mga attachment."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Maaari ka lang magpadala ng isang video sa bawat mensahe. Mangyaring alisin ang mga karagdagang video at subukang muli."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Hindi na-load ng pagmemensahe ang attachment."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Ipadala pa rin"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Hindi makapagsimula ng pag-uusap"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> ang napili"</string>
+</resources>
diff --git a/res/values-tr/arrays.xml b/res/values-tr/arrays.xml
new file mode 100644
index 0000000..d0b4e84
--- /dev/null
+++ b/res/values-tr/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"konu yok"</item>
+ <item msgid="272485471009191934">"konu yok"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Evet"</item>
+ <item msgid="6049132459802288033">"Hayır"</item>
+ <item msgid="3084376867445867895">"Tamam"</item>
+ <item msgid="3155097332660174689">"Olur"</item>
+ <item msgid="2611328818571146775">"Teşekkürler"</item>
+ <item msgid="4881335087096496747">"Kabul ediyorum"</item>
+ <item msgid="2422296858597420738">"Güzel"</item>
+ <item msgid="4805581752819452687">"Yoldayım"</item>
+ <item msgid="4746700499431366214">"Tamam, sana sonra dönerim"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..6ea1cbe
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Mesajlaşma"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Mesajlaşma"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"İleti dizisini seç"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Ayarlar"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"İleti Gönder"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Dosya ekle"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Yardım"</string>
+ <string name="welcome" msgid="2857560951820802321">"Hoş geldiniz"</string>
+ <string name="skip" msgid="7238879696319945853">"Atla"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Sonraki &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Sonraki"</string>
+ <string name="exit" msgid="1905187380359981199">"Çık"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Ayarlar &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Ayarlar"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Mesajlaşma\'nın SMS, Telefon ve Kişiler için izninize ihtiyacı var."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"İzinleri; Ayarlar &gt; Uygulamalar &gt; Mesajlaşma &gt; İzinler\'de değiştirebilirsiniz."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"İzinleri; Ayarlar, Uygulamalar, Mesajlaşma, İzinler\'de değiştirebilirsiniz."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Sık kullanılanlar"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Tüm kişiler"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Şuraya gönder: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Fotoğraf veya video çekin"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Bu cihazdan resim seç"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Ses kaydedin"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Fotoğraf seç"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Medya seçildi."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Medya seçilmedi."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> öğe seçildi"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"resim <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" msgid="8142175029053370662">"resim"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Ses kaydedin"</string>
+ <string name="action_share" msgid="2143483844803153871">"Paylaş"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Şimdi"</string>
+ <string name="posted_now" msgid="867560789350406701">"Şimdi"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> dk.</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> dk.</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> saat</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> saat</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> gün</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> gün</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> hafta</item>
+ <item quantity="one">bir hafta</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ay</item>
+ <item quantity="one">bir ay</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> yıl</item>
+ <item quantity="one">bir yıl</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Sınıf 0 iletisi"</string>
+ <string name="save" msgid="5081141452059463572">"Kaydet"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Cihazınızda boş alan az. Mesajlaşma boş alan açmak için daha eski iletileri otomatik olarak silecektir."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Depolama alanı bitiyor"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Mesajlaşma, cihazınızda daha fazla yer açılana kadar ileti gönderip almayabilir."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS depolama alanı azaldı. İletileri silmeniz gerekebilir."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Telefon numaranızı doğrulayın"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Bu bir kerelik adım, Mesajlaşma\'nın, grup iletilerini düzgün olarak teslim etmesini sağlayacaktır."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefon numarası"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Medya içeren tüm iletileri sil"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Şundan eski iletileri sil: <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Şundan daha eski iletileri otomatik olarak sil: <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Yoksay"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Medya içeren tüm iletiler silinsin mi?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Şundan daha eski iletiler silinsin mi: <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Şundan daha eski iletiler silinip otomatik silme etkinleştirilsin mi: <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> şunu dedi:"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Şunu dediniz:"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> adlı kişiden ileti"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Bir ileti gönderdiniz"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Gönderiliyor..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Gönderilmedi. Tekrar denemek için dokunun."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Gönderilmedi. Tekrar deniyor…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Yeniden gönderin veya silin"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Lütfen acil durum servislerine telefon edin. Kısa mesajınız şu anda teslim edilemedi."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Başarısız oldu"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"İndirilecek yeni MMS iletisi"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Yeni MMS iletisi"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"İndirilemedi"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Tekrar denemek için dokunun"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"İndirmek için dokunun"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"İndirin veya silin"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"İndiriliyor..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"İletinin süresi doldu veya kullanılamıyor"</string>
+ <string name="mms_info" msgid="3402311750134118165">"boyut: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, süre sonu: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Gönderilemiyor. Alıcı geçersiz."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Hizmet, ağda etkinleştirilmemiş"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Ağ sorunu nedeniyle gönderilemedi"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"İletinin süresi doldu veya kullanılamıyor"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Konu yok)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Bilinmeyen gönderen"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Teslim edildi"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"<xliff:g id="FROM">%2$s</xliff:g> tarafından gönderilen <xliff:g id="SUBJECT">%1$s</xliff:g> konulu ileti indirilemedi."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Yetersiz bellek nedeniyle veritabanı işlemi tamamlanamadı"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"İleti gönderilmedi"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Bazı iletiler Mesajlaşma\'da gönderilmedi"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ileti dizisinde <xliff:g id="CONVERSATIONS">%d</xliff:g> ileti</item>
+ <item quantity="one">Bir ileti dizisinde <xliff:g id="MESSAGES_0">%d</xliff:g> ileti</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"İleti indirilmedi"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Bazı iletiler Mesajlaşma\'dan indirilmedi"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> ileti dizisinde <xliff:g id="CONVERSATIONS">%d</xliff:g> ileti</item>
+ <item quantity="one">Bir ileti dizisinde <xliff:g id="MESSAGES_0">%d</xliff:g> ileti</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> numaralı telefona ileti gönderilemedi"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Lütfen acil durum servislerine telefon edin. <xliff:g id="NUMBER">%1$s</xliff:g> numaralı telefona gönderdiğiniz kısa mesajınız şu anda teslim edilemedi."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> yeni ileti</item>
+ <item quantity="one">Yeni ileti</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Başlat"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera kullanılamıyor"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera kullanılamıyor"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Video çekimi kullanılamıyor"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Medya kaydedilemiyor"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Resim çekilemiyor"</string>
+ <string name="back" msgid="1477626055115561645">"Geri"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arşivlenen"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Sil"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arşivle"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Arşivden kaldır"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Bildirimleri devre dışı bırak"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Bildirimleri aç"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Kişi ekle"</string>
+ <string name="action_download" msgid="7786338136368564146">"İndir"</string>
+ <string name="action_send" msgid="377635240181672039">"Gönder"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Sil"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Bu ileti silinsin mi?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Bu işlem geri alınamaz."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Sil"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Bu ileti dizileri silinsin mi?</item>
+ <item quantity="one">Bu ileti dizisi silinsin mi?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Sil"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"İptal"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Alıcı:"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Birden fazla resim seç"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Seçimi onayla"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Ses kaydedilemiyor. Tekrar deneyin."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Ses çalınamıyor. Tekrar deneyin."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Ses kaydedilemedi. Tekrar deneyin."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Dokunup basılı tutun"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Resim"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Ses klibi"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kişi kartı"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"İndir"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS ile yanıtla"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS ile yanıtla"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Yanıtla"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> katılımcı</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> katılımcı</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Ben"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kişi engellendi ve arşivlendi"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kişinin engellemesi kaldırıldı ve kişi arşivden geri alındı"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ileti dizisi arşivlendi"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ileti dizisi arşivden kaldırıldı"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Bildirimler kapalı"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Bildirimler açık"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Tümü ayarlandı. Gönder\'e tekrar dokunun."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Mesajlaşma, başarılı bir şekilde varsayılan SMS uygulaması olarak ayarlandı."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Ekleri sil</item>
+ <item quantity="one">Eki sil</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Ses dosyası eki"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Ekli ses dosyasını çal"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Duraklat"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> adlı kişiden ileti: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Şu ileti <xliff:g id="SENDER">%s</xliff:g> tarafından gönderilemedi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Şu ileti <xliff:g id="SENDER">%s</xliff:g> tarafından gönderildi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Şu ileti <xliff:g id="CONTACT">%s</xliff:g> adlı kişiye gönderilmedi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Şu ileti <xliff:g id="CONTACT">%s</xliff:g> adlı kişiye gönderiliyor: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Şu ileti <xliff:g id="CONTACT">%s</xliff:g> adlı kişiye gönderilemedi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Şu ileti <xliff:g id="CONTACT">%s</xliff:g> adlı kişiye gönderildi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Şu ileti <xliff:g id="SENDER">%s</xliff:g> tarafından gönderilemedi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Şu ileti <xliff:g id="SENDER">%s</xliff:g> tarafından gönderildi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Şu ileti <xliff:g id="GROUP">%s</xliff:g> adlı gruba gönderilmedi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Şu ileti <xliff:g id="GROUP">%s</xliff:g> adlı gruba gönderiliyor: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Şu ileti <xliff:g id="GROUP">%s</xliff:g> adlı gruba gönderilemedi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Şu ileti <xliff:g id="GROUP">%s</xliff:g> adlı gruba gönderildi: <xliff:g id="MESSAGE">%s</xliff:g>. Saat: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"İleti başarısız oldu. Yeniden denemek için dokunun."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> ile ileti dizisi"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Konuyu sil"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Video çek"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Fotoğraf çek"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Fotoğraf çek"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Video kaydetmeye başla"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Tam ekran kameraya geç"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Ön ve arka kamera arasında geçiş yap"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Kaydı durdur ve video ekle"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Video kaydını durdur"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Mesajlaşma fotoğrafları"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> fotoğraf, \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albümüne kaydedildi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> fotoğraf, \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albümüne kaydedildi</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video, \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albümüne kaydedildi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video, \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albümüne kaydedildi</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other">Ekli <xliff:g id="QUANTITY_2">%d</xliff:g> dosya, \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" albümüne kaydedildi</item>
+ <item quantity="one">Ekli <xliff:g id="QUANTITY_0">%d</xliff:g> dosya, \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" albümüne kaydedildi</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other">Ekli <xliff:g id="QUANTITY_1">%d</xliff:g> dosya, \"İndirilenler\"e kaydedildi</item>
+ <item quantity="one">Ekli <xliff:g id="QUANTITY_0">%d</xliff:g> dosya, \"İndirilenler\"e kaydedildi</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ek kaydedildi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ek kaydedildi</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ek kaydedilemedi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ek kaydedilemedi</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS eki kaydedildi"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Ayarlar"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arşivlenen"</string>
+ <string name="action_close" msgid="1840519376200478419">"Kapat"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Gelişmiş"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Hata Ayıklama"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Bildirimler"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ses"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Sessiz"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Titreşim"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Engellendi"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS iletim raporları"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Gönderilen her SMS için bir iletim raporu iste"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Otomatik al"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS iletilerini otomatik olarak al"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Dolaşımda otomatik al"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Dolaşımdayken MMS\'leri otomatik olarak al"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Grup mesajları"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Birden fazla alıcıya tek bir ileti göndermek için MMS kullanın"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Varsayılan SMS uygulaması"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Varsayılan SMS uygulaması"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Telefon numaranız"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Bilinmiyor"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Giden ileti sesleri"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS verilerini aktar"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Alınan ham SMS verilerini harici bir depolama dosyasına aktarın"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS verilerini aktar"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Alınan ham MMS verilerini harici bir depolama dosyasına aktarın"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Kablosuz uyarıları"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"İleti seçenekleri"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Metni kopyala"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Ayrıntıları görüntüle"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Sil"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Yönlendir"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"İleti ayrıntıları"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Tür: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Kısa mesaj"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimedya iletisi"</string>
+ <string name="from_label" msgid="1947831848146564875">"Gönderen: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Alıcı: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Gönderilme tarihi: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Alınma tarihi: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Konu: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Boyut: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Öncelik: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Yüksek"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Normal"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Düşük"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Gizli gönderen adresi"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Ekler yüklenirken ileti gönderilemez."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Ek yüklenemiyor. Tekrar deneyin."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Ağ hazır değil. Tekrar deneyin."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Metni sil"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Metin ve sayı girişi arasında geçiş yap"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Daha fazla katılımcı ekle"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Katılımcıları onayla"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Yeni ileti dizisi başlat"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Bu öğeyi seçin"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Videoyu oynat"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Kişiler ve seçenekler"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Hata ayıkla"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Kişiler ve seçenekler"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Genel"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Bu görüşmedeki kişiler"</string>
+ <string name="action_call" msgid="6596167921517350362">"Çağrı yap"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"İleti gönder"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Mesajı şu karttan gönder:&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Fotoğrafları gönder</item>
+ <item quantity="one">Fotoğrafı gönder</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Ses dosyalarını gönder</item>
+ <item quantity="one">Ses dosyasını gönder</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Videoları gönder</item>
+ <item quantity="one">Videoyu gönder</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Kişi kartlarını gönder</item>
+ <item quantity="one">Kişi kartını gönder</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Ekleri gönder</item>
+ <item quantity="one">Eki gönder</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ek gönderilmeye hazır</item>
+ <item quantity="one">Bir ek gönderilmeye hazır</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Geri bildirim gönder"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Google Play Store\'da görüntüle"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Sürüm bilgisi"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Sürüm %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Açık kaynak lisansları"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Bildirimler"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Ek sınırına ulaşıldı"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Ek yüklenemedi."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Kişilere eklensin mi?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Kişi Ekle"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Konu"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Konu: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kişi kartı yükleniyor"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kişi kartı yüklenemedi"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Kişi kartını görüntüle"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kişi</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kişi</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kişi kartları"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Doğum günü"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Notlar"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"İletiyi yönlendir"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Yanıtla"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS devre dışı bırakıldı"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Göndermek için Mesajlaşma\'yı varsayılan SMS uygulamanız olarak ayarlayın"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Mesajlaşma\'yı varsayılan SMS uygulamanız olarak ayarlayın"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Değiştir"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"İletileri almak için Mesajlaşma\'yı varsayılan SMS uygulamanız olarak ayarlayın"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS iletilerini göndermek için SIM tercihi yapılmadı"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Cihaz sahibi bu uygulamaya izin vermiyor."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"Tamam"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Bir görüşmede çok fazla katılımcı var"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Geçersiz kişiler</item>
+ <item quantity="one">Geçersiz kişi</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kamera görüntüsü yüklenemedi"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Siz: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Taslak"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Yeni bir görüşme başlattığınızda burada listelendiğini göreceksiniz"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arşivlenmiş ileti dizileri burada görünür"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"İleti dizileri yükleniyor..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Resim"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Ses klibi"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kişi kartı"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Geri al"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Yeniden dene"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Yeni bir iletiye başlamak için bir kişi adı veya telefon numarası girin"</string>
+ <string name="action_block" msgid="9032076625645190136">"Engelle"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> hedefini engelle"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> adlı kişinin engellemesini kaldır"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> engellensin mi?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Bu numaradan gelen iletileri almaya devam edecek ancak artık bildirim almayacaksınız. Bu ileti dizisi arşivlenecektir."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Engellenen kişiler"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"ENGELLEMEYİ KALDIR"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Engellenen kişiler"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Doküman kitaplığından resim seç"</string>
+ <string name="sending_message" msgid="6363584950085384929">"İleti gönderiliyor"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"İleti gönderildi"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Hücresel veri özelliği kapalı. Ayarlarınızı kontrol edin."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Uçak modunda iletiler gönderilemiyor"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"İleti gönderilemedi"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"İleti indirildi"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Hücresel veri özelliği kapalı. Ayarlarınızı kontrol edin."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Uçak modundayken iletiler indirilemez"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"İleti indirilemedi"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Sıfır"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Bir"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"İki"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Üç"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Dört"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Beş"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Altı"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Yedi"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Sekiz"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Dokuz"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> ile ileti gönderilemiyor. Hata kodu: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Bilinmeyen operatör ile ileti gönderilemiyor. Hata kodu: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Yön: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"İleti gönderilmedi: Hizmet, ağ üzerinde etkin değil"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"İleti gönderilmedi: Geçersiz gönderim adresi"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"İleti gönderilmedi: Geçersiz ileti"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"İleti gönderilmedi: Desteklenmeyen içerik"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"İleti gönderilmedi: Desteklenmeyen ileti"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"İleti gönderilmedi: Çok büyük"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Yeni ileti"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Görüntüle"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Resim"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Uygun bir uygulama bulunamadı"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Alıcıyı kaldır"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Yeni ileti"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"İptal"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Erişim noktasını düzenle"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ayarlanmadı"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Ad"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proxy\'si"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS bağlantı noktası"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN türü"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APN\'yi sil"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Yeni APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Kaydet"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Yoksay"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Ad alanı boş olamaz."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN boş olamaz."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC alanındaki değer 3 rakamdan oluşmalıdır."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC alanındaki değer 2 veya 3 rakamdan oluşmalıdır."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Varsayılan APN ayarları geri yükleniyor."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Varsayılana sıfırla"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Varsayılan APN ayarlarına sıfırlama tamamlandı."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Başlıksız"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Erişim Noktası Adları (APN)"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN\'ler"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Yeni APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Erişim Noktası Adı (APN) ayarları bu kullanıcı için kullanılamıyor"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Panoya kopyalansın mı?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopyala"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"Mesajın alınacağı kart: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Genel"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Gelişmiş"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Genel ayarlar"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Gelişmiş ayarlar"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Tüm alıcılara tek tek SMS iletisi gönder. Sadece size yanıt gönderilir."</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Tüm alıcılara tek bir MMS iletisi gönder"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Bilinmeyen numara"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Yeni ileti"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Yeni ileti."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM seçici"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> seçildi, SIM seçici"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Konuyu düzenle"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM seçin veya konuyu düzenleyin"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Ses kaydetmek için dokunun ve basılı tutun"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Yeni ileti dizisi başlat"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Mesajlaşma"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Mesajlaşma Listesi"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Mesajlaşma"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Yeni ileti"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"İleti dizisi listesi"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"İleti dizileri yükleniyor"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"İletiler yükleniyor"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Diğer ileti dizilerini görüntüle"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Daha fazla ileti görüntüle"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"İleti dizisi silindi"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"İleti dizisi silindi. Farklı bir Mesajlaşma ileti dizisini görüntülemek için dokunun"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Engellendi"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Engellemesi kaldırıldı"</string>
+ <string name="db_full" msgid="8459265782521418031">"Depolama alanı az. Bazı veriler kaybolabilir."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Ekleri seçin"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Seçimi onayla"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> öğe seçildi"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Lütfen bir veya daha fazla eki kaldırın ve tekrar deneyin."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"İletinizi göndermeyi deneyebilirsiniz, ancak bir veya daha fazla eki kaldırmadığınız sürece ileti teslim edilmeyebilir."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Her iletide yalnızca bir tane video gönderebilirsiniz. Lütfen ek videoları kaldırın ve tekrar deneyin."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Mesajlaşma, eki yükleyemedi."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Yine de gönder"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Sohbet başlatılamadı"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> seçildi"</string>
+</resources>
diff --git a/res/values-uk/arrays.xml b/res/values-uk/arrays.xml
new file mode 100644
index 0000000..fc47a64
--- /dev/null
+++ b/res/values-uk/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"без теми"</item>
+ <item msgid="272485471009191934">"без теми"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Так"</item>
+ <item msgid="6049132459802288033">"Ні"</item>
+ <item msgid="3084376867445867895">"ОК"</item>
+ <item msgid="3155097332660174689">"Ха-ха-ха"</item>
+ <item msgid="2611328818571146775">"Дякую"</item>
+ <item msgid="4881335087096496747">"Погоджуюсь"</item>
+ <item msgid="2422296858597420738">"Чудово"</item>
+ <item msgid="4805581752819452687">"У дорозі"</item>
+ <item msgid="4746700499431366214">"Добре, я зателефоную пізніше"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..1c44548
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,571 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Повідомлення"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Повідомлення"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Виберіть ланцюжок повідомлень"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Налаштування"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Надіслати повідомлення"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Вкласти файл"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Довідка"</string>
+ <string name="welcome" msgid="2857560951820802321">"Вітаємо"</string>
+ <string name="skip" msgid="7238879696319945853">"Пропустити"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Далі &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Далі"</string>
+ <string name="exit" msgid="1905187380359981199">"Вийти"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Налаштування &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Налаштування"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Додатку Повідомлення потрібен доступ до SMS, телефона й контактів."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Щоб змінити дозволи, виберіть \"Налаштування\" &gt; \"Додатки\" &gt; \"Повідомлення\" &gt; \"Дозволи\"."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Щоб змінити дозволи, виберіть \"Налаштування\" &gt; \"Додатки\" &gt; \"Повідомлення\" &gt; \"Дозволи\"."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Найчастіше на зв’язку"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Усі контакти"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Надіслати на номер <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Зняти фото або відео"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Вибрати зображення на цьому пристрої"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Записати аудіо"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Вибрати фотографію"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Медіафайл вибрано."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Медіафайл не вибрано."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Вибрано <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"зображення: <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" msgid="8142175029053370662">"зображення"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Записати аудіо"</string>
+ <string name="action_share" msgid="2143483844803153871">"Поділитися"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Щойно"</string>
+ <string name="posted_now" msgid="867560789350406701">"Зараз"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> хвилина</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> хвилини</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> хвилин</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> хвилини</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> година</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> години</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> годин</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> години</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> день</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> дні</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> днів</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> дня</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> тиждень</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> тижні</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> тижнів</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> тижня</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> місяць</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> місяці</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> місяців</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> місяця</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> рік</item>
+ <item quantity="few"><xliff:g id="COUNT">%d</xliff:g> роки</item>
+ <item quantity="many"><xliff:g id="COUNT">%d</xliff:g> років</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> року</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Повідомлення класу 0"</string>
+ <string name="save" msgid="5081141452059463572">"Зберегти"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"На пристрої замало місця. З додатка Повідомлення буде автоматично видалено старіші SMS, щоб звільнити місце."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Закінчується пам’ять"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Додаток Повідомлення не зможе надсилати й отримувати SMS, доки не звільниться місце на пристрої."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Недостатньо місця для SMS. Видаліть старі повідомлення."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Підтвердьте свій номер телефону"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Номер потрібен, щоб надсилати групові повідомлення. Його потрібно ввести лише один раз."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Номер телефону"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Видалити всі повідомлення з медіафайлами"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Видалити повідомлення, отримані понад <xliff:g id="DURATION">%s</xliff:g> тому"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Автоматично видаляти повідомлення, отримані понад <xliff:g id="DURATION">%s</xliff:g> тому"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ігнорувати"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Видалити всі повідомлення з медіафайлами?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Видалити повідомлення, отримані понад <xliff:g id="DURATION">%s</xliff:g> тому?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Видаляти повідомлення, отримані понад <xliff:g id="DURATION">%s</xliff:g> тому й увімкнути автоматичне видалення?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"Повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Ваше повідомлення"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Ви надіслали повідомлення"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Надсилання…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Не надіслано. Торкніться, щоб повторити спробу."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Не надіслано. Повторна спроба…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Надіслати повторно або видалити"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Не вдалося надіслати текстове повідомлення в екстрену службу. Спробуйте зателефонувати."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Помилка"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Доступне нове MMS-повідомлення"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Нове MMS-повідомлення"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Не вдалося завантажити"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Торкніться, щоб повторити спробу"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Торкніться, щоб завантажити"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Завантажити або видалити"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Завантаження…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Повідомлення прострочене або недоступне"</string>
+ <string name="mms_info" msgid="3402311750134118165">"розмір: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, дійсне до: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Не вдається надіслати. Недійсний отримувач."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Послугу не активовано в мережі"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Не вдалося надіслати через проблему з мережею"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Повідомлення прострочене або недоступне"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Без теми)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Невідомий відправник"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Доставлено"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Не вдалося завантажити повідомлення з темою \"<xliff:g id="SUBJECT">%1$s</xliff:g>\" від користувача <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Не вдалося завершити дію з базою даних. Недостатньо пам’яті"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Повідомлення не надіслано"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Деякі повідомлення не надіслано"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлення в ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлення в ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлень у ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлення в ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Повідомлення не завантажено"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Деякі повідомлення не завантажено"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлення в ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="few"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлення в ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="many"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлень у ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> повідомлення в ланцюжках (<xliff:g id="CONVERSATIONS">%d</xliff:g>)</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Повідомлення на номер <xliff:g id="NUMBER">%1$s</xliff:g> не надіслано"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Не вдалося надіслати текстове повідомлення на номер екстреної служби <xliff:g id="NUMBER">%1$s</xliff:g>. Спробуйте зателефонувати."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> нове повідомлення</item>
+ <item quantity="few"><xliff:g id="MESSAGES">%d</xliff:g> нові повідомлення</item>
+ <item quantity="many"><xliff:g id="MESSAGES">%d</xliff:g> нових повідомлень</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> нового повідомлення</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Почати"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Камера недоступна"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Камера недоступна"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Відеозйомка недоступна"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Не вдається зберегти медіафайли"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Не вдається сфотографувати"</string>
+ <string name="back" msgid="1477626055115561645">"Назад"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Архів"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Видалити"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Архів"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Розархівувати"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Вимкнути сповіщення"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Увімкнути сповіщення"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Додати контакт"</string>
+ <string name="action_download" msgid="7786338136368564146">"Завантажити"</string>
+ <string name="action_send" msgid="377635240181672039">"Надіслати"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Видалити"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Видалити це повідомлення?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Цю дію не можна скасувати."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Видалити"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Видалити ці ланцюжки повідомлень?</item>
+ <item quantity="few">Видалити ці ланцюжки повідомлень?</item>
+ <item quantity="many">Видалити ці ланцюжки повідомлень?</item>
+ <item quantity="other">Видалити ці ланцюжки повідомлень?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Видалити"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Скасувати"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Кому:"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Вибрати кілька зображень"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Підтвердити вибір"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Не вдається записати аудіо. Повторіть спробу."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Не вдається відтворити аудіо. Повторіть спробу."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Не вдалося зберегти аудіо. Повторіть спробу."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Натисніть і утримуйте"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Зображення"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Аудіокліп"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Відео"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Картка контакта"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Завантажити"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Відповісти за допомогою SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Надіслати MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Відповісти"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> учасник</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> учасники</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> учасників</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> учасника</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Я"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Користувача заблоковано та заархівовано"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Контакт розблоковано та розархівовано"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Заархівовано: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Розархівовано: <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Сповіщення вимкнено"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Сповіщення ввімкнено"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Готово. Торкніться опції \"Надіслати\" знову."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Повідомлення тепер є додатком для SMS за умовчанням."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Видалити вкладені файли</item>
+ <item quantity="few">Видалити вкладені файли</item>
+ <item quantity="many">Видалити вкладені файли</item>
+ <item quantity="other">Видалити вкладені файли</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Вкладений аудіофайл"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Відтворити аудіовкладення"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Пауза"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Не вдалося надіслати повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Ненадіслане повідомлення групі <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Надсилається повідомлення групі <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Не вдалося надіслати повідомлення групі <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Повідомлення групі <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Не вдалося надіслати повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Повідомлення від користувача <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Ненадіслане повідомлення групі <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Надсилається повідомлення групі <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Не вдалося надіслати повідомлення групі <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Повідомлення групі <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Час: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Не вдалося надіслати повідомлення. Торкніться, щоб повторити спробу."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Учасники бесіди: <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Видалити тему"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Зняти відео"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Зняти статичне зображення"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Зробити знімок"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Почати запис відео"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Показувати зображення з камери на весь екран"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Переключатися між камерами на передній і задній панелях"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Зупинити запис і долучити відео"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Зупинити запис відео"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Фото з повідомлень"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> фотографію збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> фотографії збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> фотографій збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> фотографії збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> відео збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> відео збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> відео збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> відео збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> вкладений файл збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_2">%d</xliff:g> вкладені файли збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_2">%d</xliff:g> вкладених файлів збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> вкладеного файлу збережено в альбом \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> вкладений файл збережено в папку \"Завантаження\"</item>
+ <item quantity="few"><xliff:g id="QUANTITY_1">%d</xliff:g> вкладені файли збережено в папку \"Завантаження\"</item>
+ <item quantity="many"><xliff:g id="QUANTITY_1">%d</xliff:g> вкладених файлів збережено в папку \"Завантаження\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> вкладеного файлу збережено в папку \"Завантаження\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one">Збережено <xliff:g id="QUANTITY_1">%d</xliff:g> вкладений файл</item>
+ <item quantity="few">Збережено <xliff:g id="QUANTITY_1">%d</xliff:g> вкладені файли</item>
+ <item quantity="many">Збережено <xliff:g id="QUANTITY_1">%d</xliff:g> вкладених файлів</item>
+ <item quantity="other">Збережено <xliff:g id="QUANTITY_1">%d</xliff:g> вкладеного файлу</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Не вдалося зберегти <xliff:g id="QUANTITY_1">%d</xliff:g> вкладений файл</item>
+ <item quantity="few">Не вдалося зберегти <xliff:g id="QUANTITY_1">%d</xliff:g> вкладені файли</item>
+ <item quantity="many">Не вдалося зберегти <xliff:g id="QUANTITY_1">%d</xliff:g> вкладених файлів</item>
+ <item quantity="other">Не вдалося зберегти <xliff:g id="QUANTITY_1">%d</xliff:g> вкладеного файлу</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Вкладення з MMS збережено"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Налаштування"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Архів"</string>
+ <string name="action_close" msgid="1840519376200478419">"Закрити"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Додатково"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Налагодження"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Сповіщення"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Сигнал"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Без звуку"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Вібросигнал"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Заблоковано"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Звіти про доставку SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Запитувати звіт про доставку для кожного надісланого SMS"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Автоматичне отримання"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Автоматично отримувати MMS-повідомлення"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Автоотримання в роумінгу"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Автоматично отримувати MMS у роумінгу"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Групові повідомлення"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Використовуйте опцію MMS, щоб надсилати одне повідомлення декільком одержувачам"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Додаток для SMS за умовчанням"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Додаток для SMS за умовчанням"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Ваш номер телефону"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Невідомий"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Сигнали для вихідних повідомлень"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Записати дані SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Записати отримані вихідні дані SMS у файл зовнішньої пам’яті"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Записати дані MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Записати отримані вихідні дані MMS у файл зовнішньої пам’яті"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Сповіщення через бездротові мережі"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Параметри повідомлення"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Копіювати текст"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Переглянути деталі"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Видалити"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Переслати"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Деталі повідомлення"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Тип: "</string>
+ <string name="text_message" msgid="7415419755252205721">"SMS"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Мультимедійне повідомлення"</string>
+ <string name="from_label" msgid="1947831848146564875">"Від: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Кому: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Надіслано: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Отримано: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Тема: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Розмір: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Пріоритет: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-карта: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Високий"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Звичайний"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Низький"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM-карта <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Прихована адреса відправника"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Неможливо надіслати повідомлення під час завантаження вкладених файлів."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Не вдається завантажити вкладений файл. Повторіть спробу."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Мережа не готова. Повторіть спробу."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Видалити текст"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Переключення між введенням тексту та числами"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Додати більше учасників"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Підтвердити учасників"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Почати нову бесіду"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Вибрати цей елемент"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Відтворити відео"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Учасники й опції"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Налагодження"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Учасники й опції"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Загальні"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Учасники цієї бесіди"</string>
+ <string name="action_call" msgid="6596167921517350362">"Телефонувати"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Надіслати повідомлення"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Надіслати повідомлення&lt;br/&gt;&lt;small&gt;із SIM-карти <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Надіслати фото</item>
+ <item quantity="few">Надіслати фото</item>
+ <item quantity="many">Надіслати фото</item>
+ <item quantity="other">Надіслати фото</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Надіслати аудіозаписи</item>
+ <item quantity="few">Надіслати аудіозаписи</item>
+ <item quantity="many">Надіслати аудіозаписи</item>
+ <item quantity="other">Надіслати аудіозаписи</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Надіслати відео</item>
+ <item quantity="few">Надіслати відео</item>
+ <item quantity="many">Надіслати відео</item>
+ <item quantity="other">Надіслати відео</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Надіслати картки контактів</item>
+ <item quantity="few">Надіслати картки контактів</item>
+ <item quantity="many">Надіслати картки контактів</item>
+ <item quantity="other">Надіслати картки контактів</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Надіслати вкладені файли</item>
+ <item quantity="few">Надіслати вкладені файли</item>
+ <item quantity="many">Надіслати вкладені файли</item>
+ <item quantity="other">Надіслати вкладені файли</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one">Можна надсилати <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> вкладений файл</item>
+ <item quantity="few">Можна надсилати <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> вкладені файли</item>
+ <item quantity="many">Можна надсилати <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> вкладених файлів</item>
+ <item quantity="other">Можна надсилати <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> вкладеного файлу</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Надіслати відгук"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Переглянути в магазині Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Інформація про версію"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Версія %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Ліцензії відкритого коду"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Сповіщення"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Ви вклали максимальну кількість файлів"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Не вдалося завантажити вкладений файл."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Додати в контакти?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Додати контакт"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Тема"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Тема: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g>. <xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Завантаження картки контакта"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Не вдалося завантажити картку контакта"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Переглянути картку контакта"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> контакт</item>
+ <item quantity="few"><xliff:g id="COUNT_1">%d</xliff:g> контакти</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> контактів</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> контакта</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Картки контактів"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"День народження"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Нотатки"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Переслати повідомлення"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Відповісти"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS вимкнено"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Щоб надсилати SMS, зробіть Повідомлення додатком для SMS за умовчанням"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Зробіть Повідомлення додатком для SMS за умовчанням"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Змінити"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Щоб отримувати повідомлення, зробіть Повідомлення додатком для SMS за умовчанням"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Не вибрано основної SIM-карти для надсилання SMS-повідомлень"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Власник пристрою не надав дозволів для цього додатка."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ОК"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Забагато учасників у бесіді"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Недійсні контакти</item>
+ <item quantity="few">Недійсні контакти</item>
+ <item quantity="many">Недійсні контакти</item>
+ <item quantity="other">Недійсні контакти</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Не вдалося завантажити зображення з камери"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Ви: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Чернетка"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Коли ви почнете переписку, вона з’явиться тут"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Тут відображаються заархівовані бесіди"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Завантаження бесід…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Зображення"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Аудіокліп"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Відео"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Картка контакта"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Скасувати"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Повторити"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Щоб створити нове повідомлення, введіть ім’я контакта або номер телефону"</string>
+ <string name="action_block" msgid="9032076625645190136">"Заблокувати"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Блокувати користувача <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Розблокувати користувача <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Заблокувати номер <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Ви надалі отримуватимете повідомлення з цього номера, але не отримуватимете сповіщення. Цю бесіду буде заархівовано."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Заблоковані контакти"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"РОЗБЛОКУВАТИ"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Заблоковані контакти"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Вибрати зображення з бібліотеки документів"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Повідомлення надсилається"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Повідомлення надіслано"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Мобільне передавання даних вимкнено. Перевірте налаштування."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Не вдається надіслати повідомлення в режимі польоту"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Не вдалося надіслати повідомлення"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Повідомлення завантажено"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Мобільний Інтернет вимкнено. Перевірте налаштування."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Неможливо завантажити повідомлення в режимі польоту"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Не вдалося завантажити повідомлення"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Нуль"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Один"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Два"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Три"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Чотири"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"П’ять"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Шість"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Сім"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Вісім"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Дев’ять"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Не вдається надіслати повідомлення. Помилка <xliff:g id="ERRORCODE">%2$d</xliff:g>, оператор <xliff:g id="CARRIERNAME">%1$s</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Не вдається надіслати повідомлення. Помилка <xliff:g id="ERRORCODE">%1$d</xliff:g>, невідомий оператор"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Пересилання: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Повідомлення не надіслано: послугу не активовано в мережі"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Повідомлення не надіслано. Недійсна адреса одержувача"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Повідомлення не надіслано. Повідомлення недійсне"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Повідомлення не надіслано. Вміст не підтримується"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Повідомлення не надіслано. Повідомлення не підтримується"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Повідомлення не надіслано: текст задовгий"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Нове повідомлення"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Переглянути"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Зображення"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Не вдалося знайти відповідний додаток"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Видалити отримувача"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Нове повідомлення"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Скасувати"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Редагування точки доступу"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Не вказано"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Назва"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"Назва точки доступу"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Проксі MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Порт MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Тип назви точки доступу"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Видалити назву точки доступу"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Нова назва точки доступу"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Зберегти"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Відхилити"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Поле \"Назва\" не може бути порожнім."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Поле назви точки доступу не може бути порожнім."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Поле MCC має містити 3 цифри."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Поле MNC має містити 2 або 3 цифри."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Відновлення налаштувань назви точки доступу за умовчанням."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Відновити налаштування за умовчанням"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Налаштування назви точки доступу за умовчанням відновлено."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Без назви"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Назви точок доступу"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Назви точок доступу"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Нова назва точки доступу"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Цей користувач не може вказувати назву точки доступу"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Копіювати в буфер обміну?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Копіювати"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"на SIM-карту <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Загальні"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Розширені"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Загальні налаштування"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Додаткові налаштування"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM-карта \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Надсилати окремі SMS усім отримувачам. Відповіді надходитимуть лише вам"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Надсилати одне MMS усім отримувачам"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Невідомий номер"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Нове повідомлення"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Нове повідомлення."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Вибір SIM-карти"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Вибір SIM-карти: вибрано <xliff:g id="SIM_0">%1$s</xliff:g>"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Змінити тему"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Вибрати SIM-карту або змінити тему"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Торкніться й утримуйте, щоб записати аудіо"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Почати нову бесіду"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Повідомлення"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Список у додатку Повідомлення"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Повідомлення"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Нове повідомлення"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Список бесід"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Завантаження бесід"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Завантаження повідомлень"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Переглянути інші бесіди"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Переглянути більше повідомлень"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Бесіду видалено"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Бесіду видалено. Торкніться, щоб переглянути іншу бесіду в додатку Повідомлення"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Заблоковано"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Розблоковано"</string>
+ <string name="db_full" msgid="8459265782521418031">"Замало пам’яті. Деякі дані можуть не зберегтись."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Вибрати вкладені файли"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Підтвердити вибір"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Вибрано <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Видаліть один або декілька вкладених файлів і повторіть спробу."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Спробуйте надіслати повідомлення, але адресат може його не отримати, якщо ви не видалите один або декілька вкладених файлів."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"У повідомленні можна надіслати лише одне відео. Видаліть зайві відео та спробуйте знову."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Не вдалося завантажити вкладенні файли."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Усе одно надіслати"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Не вдалося почати бесіду"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Вибрано <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-ur-rPK/arrays.xml b/res/values-ur-rPK/arrays.xml
new file mode 100644
index 0000000..fa4e5da
--- /dev/null
+++ b/res/values-ur-rPK/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"بلا عنوان"</item>
+ <item msgid="272485471009191934">"بلا عنوان"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"ہاں"</item>
+ <item msgid="6049132459802288033">"نہیں"</item>
+ <item msgid="3084376867445867895">"ٹھیک ہے"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"شکریہ"</item>
+ <item msgid="4881335087096496747">"میں متفق ہوں"</item>
+ <item msgid="2422296858597420738">"اچھا"</item>
+ <item msgid="4805581752819452687">"راستے میں ہوں"</item>
+ <item msgid="4746700499431366214">"ٹھیک ہے، میں بعد میں آپ سے رابطہ کرتا ہوں"</item>
+ <item msgid="4342041517788201970">"‎:)‎"</item>
+ <item msgid="7439437911747961187">"‎:(‎"</item>
+ </string-array>
+</resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
new file mode 100644
index 0000000..1ed22e1
--- /dev/null
+++ b/res/values-ur-rPK/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"پیغام رسانی"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"پیغام رسانی"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"گفتگو منتخب کریں"</string>
+ <string name="action_settings" msgid="1329008122345201684">"ترتیبات"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"پیغام بھیجیں"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"ایک منسلکہ شامل کریں"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"مدد"</string>
+ <string name="welcome" msgid="2857560951820802321">"خوش آمدید"</string>
+ <string name="skip" msgid="7238879696319945853">"نظر انداز کریں"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"اگلا &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"آگے"</string>
+ <string name="exit" msgid="1905187380359981199">"باہر نکلیں"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"ترتیبات &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"ترتیبات"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"‏پیغام رسانی کو SMS، فون اور رابطوں کیلئے اجازت درکار ہے۔"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"آپ ترتیبات &gt; ایپس &gt; پیغام رسانی &gt; اجازتوں میں جا کر اجازتیں تبدیل کر سکتے ہیں۔"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"آپ ترتیبات، ایپس، پیغام رسانی، اجازتوں میں جا کر اجازتیں تبدیل کر سکتے ہیں۔"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"اکثر و بیشتر"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"سبھی رابطے"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"<xliff:g id="DESTINATION">%s</xliff:g> پر بھیجیں"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"تصاویر یا ویڈیو کیپچر کریں"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"اس آلہ سے تصاویر منتخب کریں"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"آڈیو ریکارڈ کریں"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"تصویر منتخب کریں"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"میڈیا منتخب کردہ ہے۔"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"میڈیا غیر منتخب کردہ ہے۔"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> کو منتخب کیا گیا"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"تصویر <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" msgid="8142175029053370662">"تصویر"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"آڈیو ریکارڈ کریں"</string>
+ <string name="action_share" msgid="2143483844803153871">"اشتراک کریں"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"ابھی ابھی"</string>
+ <string name="posted_now" msgid="867560789350406701">"ابھی"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> منٹ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> منٹ</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> گھنٹے</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> گھنٹہ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> دن</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> دن</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> ہفتے</item>
+ <item quantity="one">ایک ہفتہ</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> مہینے</item>
+ <item quantity="one">ایک ماہ</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> سال</item>
+ <item quantity="one">ایک سال</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"کلاس 0 پیغام"</string>
+ <string name="save" msgid="5081141452059463572">"محفوظ کریں"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"آلہ میں جگہ کم ہے۔ پیغام رسانی جگہ خالی کرنے کیلئے خودکار طریقے سے پرانے پیغامات حذف کر دے گی۔"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"اسٹوریج کی جگہ ختم ہو رہی ہے"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"جب تک آپ کے آلہ پر مزید جگہ دستیاب نہ ہو، ممکن ہے اس وقت تک پیغام رسانی نہ پیغامات بھیجے یا نہ موصول کرے۔"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"‏SMS کا کم اسٹوریج۔ آپ کو کچھ پیغامات کو حذف کرنے کی ضرورت پڑ سکتی ہے۔"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"اپنے فون نمبر کی توثیق کریں"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"یہ یک وقتی مرحلہ اس بات کو یقینی بنائے گا کہ پیغام رسانی آپ کے گروپ پیغامات کو مناسب طریقے سے ڈیلیور کرے گی۔"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"فون نمبر"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"میڈیا والے سبھی پیغامات کو حذف کریں"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g> سے پرانے پیغامات کو حذف کر دیں"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g> سے پہلے کے پیغامات کو خودکار طور پر حذف کریں"</string>
+ <string name="ignore" msgid="7063392681130898793">"نظر انداز کریں"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"میڈیا کے ساتھ سبھی پیغامات کو حذف کریں؟"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> سے پہلے کے پیغامات کو حذف کریں؟"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g> سے پہلے کے پیغامات کو حذف کریں اور خودکار طور پر حذف کرنے کو آن کریں؟"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> نے کہا"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"آپ نے کہا"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"پیغام منجانب <xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"آپ نے ایک پیغام بھیجا"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"بھیجا جا رہا ہے…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"بھیجا نہیں گیا۔ دوبارہ کوشش کرنے کیلئے ٹچ کریں۔"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"بھیجا نہیں گیا۔ دوبارہ کوشش کی جا رہی ہے…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"دوبارہ بھیجیں یا حذف کریں"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"براہ کرم ہنگامی سروسز کیلئے ایک صوتی کال کریں۔ آپ کا متنی پیغام اس وقت ڈیلیور نہیں کیا جا سکا۔"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"ناکام"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"‏ڈاؤن لوڈ کرنے کیلئے نیا MMS پیغام"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"‏نیا MMS پیغام"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"ڈاؤن لوڈ نہیں کیا جا سکا"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"دوبارہ کوشش کرنے کیلئے ٹچ کریں"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"ڈاؤن لوڈ کرنے کیلئے ٹچ کریں"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"ڈاؤن لوڈ کریں یا حذف کریں"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"ڈاؤن لوڈ ہو رہا ہے…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"پیغام کی میعاد ختم ہو گئی یا دستیاب نہیں ہے"</string>
+ <string name="mms_info" msgid="3402311750134118165">"سائز: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>، اختتامی میعاد: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"بھیجا نہیں جا سکتا۔ وصول کنندہ درست نہیں ہے۔"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"نیٹ ورک پر سروس فعال نہیں ہے"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"نیٹ ورک کے مسئلہ کی وجہ سے نہیں بھیجا جا سکا"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"پیغام کی میعاد ختم ہو گئی یا دستیاب نہیں ہے"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(بلا عنوان)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"نامعلوم ارسال کنندہ"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"ڈیلیور ہو گیا"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"پیغام <xliff:g id="SUBJECT">%1$s</xliff:g> کو <xliff:g id="FROM">%2$s</xliff:g> سے ڈاؤن لوڈ نہیں کیا جا سکا۔"</string>
+ <string name="low_memory" msgid="5300743415198486619">"کم میموری کی وجہ سے ڈیٹا بیس کی کارروائی مکمل نہیں ہو سکی"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"پیغام نہیں بھیجا گیا"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"پیغام رسانی میں کچھ پیغامات کو نہیں بھیجا گیا"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> گفتگوؤں میں <xliff:g id="MESSAGES_1">%d</xliff:g> پیغامات</item>
+ <item quantity="one">ایک گفتگو میں <xliff:g id="MESSAGES_0">%d</xliff:g> پیغامات</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"پیغام ڈاؤن لوڈ نہیں ہوا"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"پیغام رسانی میں کچھ پیغامات ڈاؤن لوڈ نہیں ہوئے"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"> <xliff:g id="CONVERSATIONS">%d</xliff:g> گفتگوؤں میں <xliff:g id="MESSAGES_1">%d</xliff:g> پیغامات</item>
+ <item quantity="one"> ایک گفتگو میں <xliff:g id="MESSAGES_0">%d</xliff:g> یغامات</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> پر پیغام نہیں بھیجا گیا"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"براہ کرم ہنگامی سروسز کیلئے ایک صوتی کال کریں۔ <xliff:g id="NUMBER">%1$s</xliff:g> پر آپ کا متنی پیغام اس وقت ڈیلیور نہیں کیا جا سکا۔"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> نئے پیغامات</item>
+ <item quantity="one">نیا پیغام</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"شروع کریں"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"کیمرہ دستیاب نہیں ہے"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"کیمرہ دستیاب نہیں ہے"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"ویڈیو کیپچر دستیاب نہیں ہے"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"میڈیا کو محفوظ نہیں کیا جا سکتا ہے"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"تصویر نہیں لی جا سکتی"</string>
+ <string name="back" msgid="1477626055115561645">"واپس جائیں"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"آرکائیو کردہ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"حذف کریں"</string>
+ <string name="action_archive" msgid="5437034800324083170">"آرکائیو کریں"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"آرکائیو سے نکالیں"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"اطلاعات آف کریں"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"اطلاعات آن کریں"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"رابطہ شامل کریں"</string>
+ <string name="action_download" msgid="7786338136368564146">"ڈاؤن لوڈ کریں"</string>
+ <string name="action_send" msgid="377635240181672039">"بھیجیں"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"حذف کریں"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"یہ پیغام حذف کریں؟"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"اس عمل کو کالعدم نہیں کیا جاسکتا۔"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"حذف کریں"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">ان گفتگوؤں کو حذف کریں؟</item>
+ <item quantity="one">اس گفتگو کو حذف کریں؟</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"حذف کریں"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"منسوخ کریں"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"بنام"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"متعدد تصاویر منتخب کریں"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"انتخاب کی توثیق کریں"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"‎+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"آڈیو کو ریکارڈ نہیں کیا جا سکتا ہے۔ دوبارہ کوشش کریں۔"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"آڈیو کو چلایا نہیں جا سکتا ہے۔ دوبارہ کوشش کریں۔"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"آڈیو کو محفوظ نہیں کیا جا سکا۔ دوبارہ کوشش کریں۔"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"ٹچ کریں اور دبائے رکھیں"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"، "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"تصویر"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"آڈیو کلپ"</string>
+ <string name="notification_video" msgid="4331423498662606204">"ویڈیو"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"رابطہ کارڈ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"ڈاؤن لوڈ کریں"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"‏SMS کے ذریعے جواب دیں"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"‏MMS کے ذریعے جواب دیں"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"جواب دیں"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> شرکت کنندگان</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> شرکت کنندہ</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"میں"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"رابطہ کو مسدود اور آرکائیو کر دیا گیا"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"رابطہ کو غیر مسدود کر دیا گیا اور آرکائیو سے نکال دیا گیا"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> کو آرکائیو کر دیا گیا"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> کو آرکائیو سے نکال دیا گیا"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"اطلاعات آف کر دی گئیں"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"اطلاعات آن کر دی گئیں"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"سب ٹھیک ہے۔ بھیجیں کو دوبارہ ٹچ کریں۔"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"‏پیغام رسانی کامیابی سے بطور ڈیفالٹ SMS ایپ سیٹ ہو گئی۔"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">منسلکہ جات کو مسترد کریں</item>
+ <item quantity="one">منسلکہ کو مسترد کریں</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"آڈیو منسلکہ"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"آڈیو منسلکہ چلائیں"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"موقوف کریں"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"پیغام منجانب <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"ناکام پیغام منجانب <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"پیغام منجانب <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"غیر ارسال کردہ پیغام بنام <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"پیغام بھیجا جا رہا ہے بنام <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"ناکام پیغام بنام <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"پیغام بنام <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"ناکام پیغام منجانب <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔ <xliff:g id="GROUPINFO">%s</xliff:g>۔"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"پیغام منجانب <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔ <xliff:g id="GROUPINFO">%s</xliff:g>۔"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"غیر ارسال کردہ پیغام بنام <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"پیغام بھیجا جا رہا ہے بنام <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"ناکام پیغام بنام <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"پیغام بنام <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>۔ وقت: <xliff:g id="TIME">%s</xliff:g>۔"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"ناکام پیغام۔ دوبارہ کوشش کرنے کیلئے ٹچ کریں۔"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> کے ساتھ گفتگو"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"موضوع حذف کریں"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"ویڈیو کیپچر کریں"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"ایک ساکت تصویر کیپچر کریں"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"تصویر لیں"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"ویڈیو ریکارڈ کرنا شروع کریں"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"پوری اسکرین کے کیمرہ پر سوئچ کریں"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"سامنے اور پیچھے کے کیمرے کے درمیان سوئچ کریں"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"ریکارڈنگ بند کریں اور ویڈیو منسلک کریں"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"ویڈیو ریکارڈ کرنے کو روکیں"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"پیغام رسانی تصاویر"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> تصاویر \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" البم میں محفوظ ہو گئیں</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> تصویر \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" البم میں محفوظ ہو گئی</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ویڈیوز \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" البم میں محفوظ ہو گئیں</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ویڈیو \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" البم میں محفوظ ہو گئی</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> منسلکہ جات \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\" البم میں محفوظ ہو گئیں</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> منسلکہ \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\" البم میں محفوظ ہو گئی</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> منسلکہ جات \'ڈاؤن لوڈز\' میں محفوظ ہو گئیں</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> منسلکہ \'ڈاؤن لوڈز\' میں محفوظ ہو گئی</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> منسلکہ جات کو محفوظ کر دیا گیا</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> منسلکہ کو محفوظ کر دیا گیا</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"> <xliff:g id="QUANTITY_1">%d</xliff:g> منسلکہ جات کو محفوظ نہیں کیا جا سکا</item>
+ <item quantity="one"> <xliff:g id="QUANTITY_0">%d</xliff:g> منسلکہ کو محفوظ نہیں کیا جا سکا</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"‏MMS منسلکہ کو محفوظ کر دیا گیا"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"ترتیبات"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"آرکائیو کردہ"</string>
+ <string name="action_close" msgid="1840519376200478419">"بند کریں"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"جدید ترین"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"ڈیبگ کریں"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"اطلاعات"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"آواز"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"خاموش"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"وائبریٹ"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"مسدود"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"‏SMS ڈیلیوری کی رپورٹس"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"‏اپنے ذریعہ بھیجے جانے والے ہر SMS کی ایک ڈیلیوری رپورٹ کی درخواست کریں"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"خودکار طور پر بازیافت کریں"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"‏خود کار طور پر MMS پیغامات کی بازیابی کریں"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"رومنگ کے وقت خود کار بازیابی"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"‏رومنگ میں رہتے ہوئے MMS کی خود کار طور پر بازيابی کریں"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"گروپ پیغام رسانی"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"‏متعدد وصول کنندگان ہونے کی صورت میں ایک پیغام بھیجنے کیلئے MMS استعمال کریں"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"‏ڈیفالٹ SMS ایپ"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"‏ڈیفالٹ SMS ایپ"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"آپ کا فون نمبر"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"نامعلوم"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"آؤٹ گوئنگ پیغامات کی آوازیں"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"‏SMS کو ڈمپ کریں"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"‏وصول کردہ SMS خام ڈیٹا کو خارجی اسٹوریج فائل میں ڈمپ کریں"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"‏MMS کو ڈمپ کریں"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"‏وصول کردہ MMS خام ڈیٹا کو خارجی اسٹوریج فائل میں ڈمپ کریں"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"وائرلیس الرٹس"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"پیغام کے اختیارات"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"متن کاپی کریں"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"تفصیلات دیکھیں"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"حذف کریں"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"فارورڈ کریں"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"پیغام کی تفصیلات"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"قسم: "</string>
+ <string name="text_message" msgid="7415419755252205721">"متنی پیغام"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"ملٹی میڈیا پیغام"</string>
+ <string name="from_label" msgid="1947831848146564875">"منجانب: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"بنام: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"ارسال کردہ: "</string>
+ <string name="received_label" msgid="4442494712757995203">"موصول کردہ: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"موضوع: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"سائز: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"ترجیح: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM:‎ "</string>
+ <string name="priority_high" msgid="728836357310908368">"اعلی"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"عام"</string>
+ <string name="priority_low" msgid="7398724779026801851">"کم"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>‎"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"ارسال کنندہ کا پتہ مخفی کر دیا گیا"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"منسلکات کو لوڈ کرتے وقت پیغام نہیں بھیجا جا سکتا۔"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"منسلکہ کو لوڈ نہیں کیا جا سکتا۔ دوبارہ کوشش کریں۔"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"نیٹ ورک تیار نہیں ہے۔ دوبارہ کوشش کریں۔"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"متن کو حذف کریں"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"متن اور اعداد درج کرنے کے بیچ سوئچ کریں"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"مزید شرکاء کو شامل کریں"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"شرکاء کی توثیق کریں"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"نئی گفتگو شروع کریں"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"اس آئٹم کو منتخب کریں"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"ویڈیو چلائیں"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"لوگ اور اختیارات"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"ڈیبگ کریں"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"لوگ اور اختیارات"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"عام"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"اس گفتگو میں موجود لوگ"</string>
+ <string name="action_call" msgid="6596167921517350362">"ایک کال کریں"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"پیغام بھیجیں"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"‏پیغام بھیجیں‎&lt;br/&gt;&lt;small&gt;‎از <xliff:g id="SIM_NAME">%s</xliff:g>‎&lt;/small&gt;‎"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">تصاویر بھیجیں</item>
+ <item quantity="one">تصویر بھیجیں</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">آڈیوز بھیجیں</item>
+ <item quantity="one">آڈیو بھیجیں</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">ویڈیوز بھیجیں</item>
+ <item quantity="one">ویڈیو بھیجیں</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">رابطہ کارڈز بھیجیں</item>
+ <item quantity="one">رابطہ کارڈ بھیجیں</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">منسلکہ جات بھیجیں</item>
+ <item quantity="one">منسلکہ بھیجیں</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> منسلکہ جات بھیجنے کیلئے تیار</item>
+ <item quantity="one">ایک منسلکہ بھیجنے کیلئے تیار</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"تاثرات بھیجیں"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"‏Google Play اسٹور میں دیکھیں"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"ورژن کی معلومات"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"‏ورژن ‎%1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"اوپن سورس لائسنسز"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"اطلاعات"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"منسلکہ کی حد تک پہنچ گئے"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"منسلکہ لوڈ کرنے میں ناکام۔"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"رابطوں میں شامل کریں؟"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"رابطہ شامل کریں"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"موضوع"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"موضوع: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"‎<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>‎"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"رابطہ کارڈ لوڈ کر رہا ہے"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"رابطہ کارڈ کو لوڈ نہیں کیا جا سکا"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"رابطہ کارڈ دیکھیں"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> رابطے</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> رابطہ</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"رابطہ کارڈز"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"سالگرہ"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"نوٹس"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"پیغام فارورڈ کریں"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"جواب دیں"</string>
+ <string name="plus_one" msgid="9010288417554932581">"‎+1‎"</string>
+ <string name="plus_n" msgid="8961547034116059566">"‎+%d‎"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"‏SMS غیر فعال ہے"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"‏بھیجنے کیلئے پیغام رسانی کو بطور ڈیفالٹ SMS ایپ سیٹ کریں"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"‏پیغام رسانی کو بطور ڈیفالٹ SMS ایپ سیٹ کریں"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"تبدیل کریں"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"‏پیغامات موصول کرنے کیلئے پیغام رسانی کو بطور ڈیفالٹ SMS ایپ سیٹ کریں"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"‏SMS پیغامات بھیجنے کیلئے کوئی ترجیحی SIM منتخب نہیں کیا گیا"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"آلہ کے مالک نے اس ایپ کی اجازت نہیں دی ہے۔"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"ٹھیک ہے"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"ایک گفتگو میں بہت زیادہ شرکاء"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">غلط رابطے</item>
+ <item quantity="one">غلط رابطہ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"کیمرے کی تصویر کو لوڈ نہیں کیا جا سکا"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"آپ: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"‎<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>:‎ "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"مسودہ"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"کوئی نئی گفتگو شروع کرنے پر، آپ اسے یہاں مندرج دیکھیں گے"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"آرکائیو کردہ گفتگوئیں یہاں ظاہر ہوتی ہیں"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"گفتگوئیں لوڈ ہو رہی ہیں…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"تصویر"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"آڈیو کلپ"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"ویڈیو"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"رابطہ کارڈ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"کالعدم کریں"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"دوبارہ کوشش کریں"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"ایک نیا پیغام شروع کرنے کیلئے رابطہ کا ایک نام یا فون نمبر درج کریں"</string>
+ <string name="action_block" msgid="9032076625645190136">"مسدود کریں"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"<xliff:g id="DESTINATION">%s</xliff:g> کو مسدود کریں"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"<xliff:g id="DESTINATION">%s</xliff:g> کو غیر مسدود کریں"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> کو مسدود کریں؟"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"آپ کو اس نمبر سے پیغامات موصول ہوتے رہیں گے لیکن مزید مطلع نہیں کیا جائے گا۔ اس گفتگو کو آرکائیو کر دیا جائے گا۔"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"مسدود رابطے"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"غیر مسدود کریں"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"مسدود رابطے"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"دستاویز لائبریری سے تصویر منتخب کریں"</string>
+ <string name="sending_message" msgid="6363584950085384929">"پیغام بھیجا جا رہا ہے"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"پیغام بھیج دیا گیا"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"سیلولر ڈیٹا آف ہے۔ اپنی ترتیبات چیک کریں۔"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"پیغامات کو ہوائی جہاز وضع میں نہیں بھیجا جا سکتا ہے"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"پیغام بھیجا نہیں جا سکا"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"پیغام ڈاؤن لوڈ ہو گیا"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"سیلولر ڈیٹا آف ہے۔ اپنی ترتیبات چیک کریں۔"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"ہوائی جہاز وضع میں پیغامات ڈاؤن لوڈ نہیں کیے جا سکتے ہیں"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"پیغام ڈاؤن لوڈ نہیں کیا جا سکا"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"صفر"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"ایک"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"دو"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"تین"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"چار"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"پانچ"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"چھ"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"سات"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"آٹھ"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"نو"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> کے ساتھ پیغام نہیں بھیجا جا سکتا، خرابی <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"نامعلوم کیرئیر کے ساتھ پیغام نہیں بھیجا جا سکتا، خرابی <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"آگے منتقل کریں: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"پیغام نہیں بھیجا گیا: سروس نیٹ ورک پر فعال نہیں ہے"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"پیغام نہیں بھیجا گیا: منزل مقصول کا غلط پتہ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"پیغام نہیں بھیجا گیا: غلط پیغام"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"پیغام نہیں بھیجا گیا: غیر تعاون یافتہ مواد"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"پیغام کو نہیں بھیجا گیا: غیر تعاون یافتہ پیغام"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"پیغام نہیں بھیجا گیا: کافی بڑا ہے"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"نیا پیغام"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"دیکھیں"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"تصویر"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"ایک مناسب ایپلیکیشن تلاش نہیں کی جا سکی"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"وصول کنندہ کو ہٹائیں"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"نیا پیغام"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"منسوخ کریں"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"رسائی کی جگہ میں ترمیم کریں"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"سیٹ نہیں ہے"</string>
+ <string name="apn_name" msgid="1572691851070894985">"نام"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"‏MMS پراکسی"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"‏MMS پورٹ"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"‏APN قسم"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"‏APN کو حذف کریں"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"‏نیا APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"محفوظ کریں"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"مسترد کریں"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"نام کی فیلڈ خالی نہیں رہ سکتی-"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"‏APN خالی نہيں رہ سکتا۔"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"‏MCC فیلڈ میں 3 ہندسے ہونا ضروری ہے۔"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"‏MNC فیلڈ میں 2 یا 3 ہندسے ہونا ضروری ہے-"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"‏ڈیفالٹ APN ترتیبات بحال ہو رہی ہیں-"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"ڈیفالٹ پر دوبارہ ترتیب دیں"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"‏ڈیفالٹ APN ترتیبات کو دوبارہ ترتیب دینے کا عمل مکمل ہو گیا-"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"بلا عنوان"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"رسائی کی جگہ کے نام"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APNs"</string>
+ <string name="menu_new" msgid="8286285392706532511">"‏نیا APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"رسائی کی جگہ کے نام کی ترتیبات اس صارف کیلئے دستیاب نہیں ہیں"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"کلپ بورڈ پر کاپی کریں؟"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"کاپی کریں"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"<xliff:g id="SIM_NAME">%s</xliff:g> پر"</string>
+ <string name="general_settings" msgid="5409336577057897710">"عام"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"جدید ترین"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"عام ترتیبات"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"جدید ترین ترتیبات"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"‎\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM‎"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"‏سبھی وصول کنندگان کو انفرادی SMS پیغامات بھیجیں۔ صرف آپ کو کوئی جوابات موصول ہوں گے"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"‏سبھی وصول کنندگان کو ایک MMS بھیجیں"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"نامعلوم نمبر"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"نیا پیغام"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"نیا پیغام۔"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"‏SIM کا انتخاب کنندہ"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"‏منتخب شدہ <xliff:g id="SIM_0">%1$s</xliff:g>، SIM کا انتخاب کنندہ"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"موضوع میں ترمیم کریں"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"‏SIM منتخب کریں یا موضوع میں ترمیم کریں"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"آڈیو ریکارڈ کرنے کیلئے ٹچ کریں اور پکڑ کر رکھیں"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"نئی گفتگو شروع کریں"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"پیغام رسانی"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"پیغام رسانی کی فہرست"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"پیغام رسانی"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"نیا پیغام"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"گفتگو کی فہرست"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"گفتگوئیں لوڈ ہو رہی ہیں"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"پیغامات لوڈ ہو رہے ہیں"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"مزید گفتگوئیں دیکھیں"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"مزید پیغامات دیکھیں"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"گفتگو کو حذف کر دیا گیا"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"گفتگو حذف ہوگئی۔ پیغام رسانی کی ایک دوسری گفتگو دکھانے کیلئے ٹچ کریں"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"مسدود"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"غیر مسدود"</string>
+ <string name="db_full" msgid="8459265782521418031">"اسٹوریج کی جگہ کم ہے۔ کچھ ڈیٹا ضائع ہو سکتا ہے۔"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"منسلکات منتخب کریں"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"انتخاب کی توثیق کریں"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> کو منتخب کیا گیا"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"براہ کرم ایک یا مزید منسلکات ہٹائیں اور دوبارہ کوشش کریں۔"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"آپ اپنا پیغام بھیجنے کی کوشش کر سکتے ہیں، لیکن ہو سکتا ہے وہ تب تک ڈیلیور نہ ہو جب تک کہ آپ ایک یا مزيد منسلکات نہیں ہٹا دیتے۔"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"آپ فی پیغام صرف ایک ویڈیو بھیج سکتے ہیں۔ براہ کرم اضافی ویڈیوز ہٹائیں اور دوبارہ کوشش کریں۔"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"پیغام رسانی منسلکہ کو لوڈ کرنے میں ناکام رہی۔"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"بہر صورت بھیجیں"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"گفتگو شروع نہیں کی جا سکی"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> منتخب کردہ"</string>
+</resources>
diff --git a/res/values-uz-rUZ/arrays.xml b/res/values-uz-rUZ/arrays.xml
new file mode 100644
index 0000000..79b4c47
--- /dev/null
+++ b/res/values-uz-rUZ/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"mavzu yo‘q"</item>
+ <item msgid="272485471009191934">"mavzu yo‘q"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Ha"</item>
+ <item msgid="6049132459802288033">"Yo‘q"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Ha-ha"</item>
+ <item msgid="2611328818571146775">"Rahmat"</item>
+ <item msgid="4881335087096496747">"Yaxshi"</item>
+ <item msgid="2422296858597420738">"Ajoyib"</item>
+ <item msgid="4805581752819452687">"Yo‘ldaman"</item>
+ <item msgid="4746700499431366214">"OK, keyinroq telefon qilaman"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
new file mode 100644
index 0000000..2437398
--- /dev/null
+++ b/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Xabarlar"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Xabarlar"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Suhbatni tanlang"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Sozlamalar"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Xabar yuborish"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Fayl biriktiring"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Yordam"</string>
+ <string name="welcome" msgid="2857560951820802321">"Xush kelibsiz"</string>
+ <string name="skip" msgid="7238879696319945853">"O’tkazib yuborish"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Keyingi &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Keyingi"</string>
+ <string name="exit" msgid="1905187380359981199">"Chiqish"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Sozlamalar &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Sozlamalar"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"SMS/MMS xizmatiga SMS, Telefon va Kontaktlardan foydalanish uchun ruxsat zarur."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Ruxsatnomalarni bu yerdan o‘zgartiring: Sozlamalar &gt; Ilovalar &gt; SMS/MMS &gt; Ruxsatnomalar"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Ruxsatnomalarni bu yerdan o‘zgartiring: Sozlamalar &gt; Ilovalar &gt; SMS/MMS &gt; Ruxsatnomalar"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Tez-tez"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Barcha kontaktlar"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Yuborish: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Rasm yoki video tasvirga olish"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Ushbu qurilmadan rasmlarni tanlang"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Audio yozish"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Rasmni tanlang"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Media tanlandi."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Mediani tanlash bekor qilindi."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> ta tanlandi"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"tasvir <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" msgid="8142175029053370662">"tasvir"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Audio yozish"</string>
+ <string name="action_share" msgid="2143483844803153871">"Ulashish"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Hozir"</string>
+ <string name="posted_now" msgid="867560789350406701">"Hozir"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> daqiqa</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> daqiqa</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> soat</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> soat</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> kun</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> kun</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> hafta</item>
+ <item quantity="one">bir hafta</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> oy</item>
+ <item quantity="one">bir oy</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> yil</item>
+ <item quantity="one">bir yil</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0-toifa xabari"</string>
+ <string name="save" msgid="5081141452059463572">"Saqlash"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Qurilma xotirasidagi bo‘sh joy kam qolmoqda. Joy bo‘shatish uchun eskiroq xabarlar avtomatik o‘chirib tashlanadi."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Xotirada bo‘sh joy tugamoqda"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Qurilmangizda bo‘sh joy kam qolsa, SMS/MMS xizmati xabarlarni yubora yoki qabul qila olmay qolishi mumkin."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"SMS uchun xotirada joy kam. Sizdan ba’zi xabarlarni o‘chirish talab qilinishi mumkin."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Telefon raqamingizni tasdiqlang"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Bu bir martalik bosqich bajarilsa, SMS/MMS xizmati guruh xabarlarini to‘g‘ri yuboradigan bo‘ladi."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Telefon raqami"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Barcha multimediali xabarlarni o‘chirish"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"<xliff:g id="DURATION">%s</xliff:g>dan eski xabarlar o‘chirilsin"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"<xliff:g id="DURATION">%s</xliff:g>dan oldingi xabarlar avtomatik o‘chirilsin"</string>
+ <string name="ignore" msgid="7063392681130898793">"E’tiborsiz qoldirish"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Media faylli barcha xabarlar o‘chirib tashlansinmi?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"<xliff:g id="DURATION">%s</xliff:g> dan oldingi barcha xabarlar o‘chirib tashlansinmi?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"<xliff:g id="DURATION">%s</xliff:g>dan oldingi xabarlar o‘chirilib, avtomatik o‘chirish funksiyasi yoqilsinmi?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> dedi"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Siz dedingiz"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"<xliff:g id="SENDER">%s</xliff:g> yuborgan xabar"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Siz xabar yubordingiz"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Yuborilmoqda…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Yuborilmadi. Qayta urinish uchun bosing."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Yuborilmadi. Qayta urinilmoqda…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Qayta yuboring yoki o‘chirib tashlang"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Favqulodda xizmatlarga ovozli qo‘ng‘iroq qiling. Favqulodda xizmat raqamlariga yuborilgan SMS xabarlarni hozirda yetkazib bo‘lmaydi."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Xabar yuborilmadi"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Yuklab olish uchun yangi MMS xabari"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Yangi MMS xabari"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Yuklab olinmadi"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Qayta urinish uchun bosing"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Yuklab olish uchun bosing"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Yuklab oling yoki o‘chirib tashlang"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Yuklab olinmoqda…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Xabar eskirgan yoki mavjud emas"</string>
+ <string name="mms_info" msgid="3402311750134118165">"hajmi: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, tugash muddati: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Yuborib bo‘lmadi. Qabul qiluvchi noto‘g‘ri ko‘rsatilgan."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Xizmat tarmoqda faollashtirilmagan"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Tarmoqdagi muammo tufayli yuborib bo‘lmadi"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Xabar eskirgan yoki mavjud emas"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Mavzu yo‘q)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Noma’lum yuboruvchi"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Yetkazildi"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"“<xliff:g id="SUBJECT">%1$s</xliff:g>” to‘g‘risidagi xabarni (yuboruvchi: <xliff:g id="FROM">%2$s</xliff:g>) yuklab bo‘lmadi."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Xotirada kam joy qolganligi sababli ma’lumotlar bazasi amalini bajarib bo‘lmaydi"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Xabar yuborilmadi"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"SMS/MMS xizmatidagi ayrim xabarlar yuborilmadi"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other">Suhbatlar: <xliff:g id="CONVERSATIONS">%d</xliff:g>, xabarlar: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="one">Suhbatdagi xabarlar soni: <xliff:g id="MESSAGES_0">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Xabar yuklab olinmadi"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"SMS/MMS xizmatidagi ayrim xabarlar yuklab olinmadi"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other">Suhbatlar: <xliff:g id="CONVERSATIONS">%d</xliff:g>, xabarlar: <xliff:g id="MESSAGES_1">%d</xliff:g></item>
+ <item quantity="one">Suhbatdagi xabarlar soni: <xliff:g id="MESSAGES_0">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"<xliff:g id="NUMBER">%1$s</xliff:g> raqamiga xabar yuborilmadi"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Favqulodda xizmatlarga ovozli qo‘ng‘iroq qiling. <xliff:g id="NUMBER">%1$s</xliff:g> raqamiga yuborgan SMS xabaringizni hozir yetkazib bo‘lmaydi."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> ta yangi xabar</item>
+ <item quantity="one">Yangi xabar</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Boshlash"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Kamera tayyor emas"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Kamera tayyor emas"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Videoga olib bo‘lmaydi"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Media faylni saqlab bo‘lmadi"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Rasmga olib bo‘lmadi"</string>
+ <string name="back" msgid="1477626055115561645">"Orqaga"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Arxivlangan"</string>
+ <string name="action_delete" msgid="4076795795307486019">"O‘chirish"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Arxivlash"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Arxivdan chiqarish"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Bildirishnomalarni o‘chirib qo‘yish"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Bildirishnomalarni yoqish"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Kontakt qo‘shish"</string>
+ <string name="action_download" msgid="7786338136368564146">"Yuklab olish"</string>
+ <string name="action_send" msgid="377635240181672039">"Yuborish"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"O‘chirish"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Ushbu xabar o‘chirilsinmi?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Ushbu amalni bekor qilib bo‘lmaydi."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"O‘chirish"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Bu suhbatlar o’chirib tashlansinmi?</item>
+ <item quantity="one">Bu suhbat o’chirib tashlansinmi?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"O‘chirish"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Bekor qilish"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Kimga:"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Bir nechta rasm tanlash"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Tanlovni tasdiqlash"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g> ta"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Audioni yozib bo‘lmadi. Qaytadan urining."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Audioni ijro qilib bo‘lmadi. Qaytadan urining."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Audioni saqlab bo‘lmadi. Qaytadan urining."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Bosib turing"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Rasm"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Audio-klip"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Kontaktlar kartasi"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Yuklab olish"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"SMS orqali javob berish"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"MMS orqali javob"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Javob berish"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ta ishtirokchi</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ta ishtirokchi</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Men"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Kontakt bloklandi va arxivlandi"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Kontakt blokdan va arxivdan chiqarildi"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> ta qayd arxivlandi"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> ta qayd arxivdan chiqarildi"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Bildirishnomalar o‘chirib qo‘yildi"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Bildirishnomalar yoqildi"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Hammasi tayyor. Endi “Yuborish” tugmasini bosing."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"SMS/MMS xizmati birlamchi SMS ilovasi sifatida o‘rnatildi."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Biriktirmalarni olib tashlash</item>
+ <item quantity="one">Biriktirmani olib tashlash</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Ovozli biriktirma"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Audio biriktirmani ijro qilish"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"To‘xtatib turish"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g> yuborgan xabar: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g> yuborgan muvaffaqiyatsiz xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g> yuborgan xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"<xliff:g id="CONTACT">%s</xliff:g>ga yuborilmay qolgan xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"<xliff:g id="CONTACT">%s</xliff:g>ga xabar yuborilmoqda: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"<xliff:g id="CONTACT">%s</xliff:g>ga yuborilgan muvaffaqiyatsiz xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"<xliff:g id="CONTACT">%s</xliff:g>ga xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g> yuborgan muvaffaqiyatsiz xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g> yuborgan xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"<xliff:g id="GROUP">%s</xliff:g> guruhiga yuborilmay qolgan xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"<xliff:g id="GROUP">%s</xliff:g> guruhiga xabar yuborilmoqda: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"<xliff:g id="GROUP">%s</xliff:g> guruhiga yuborilgan muvaffaqiyatsiz xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"<xliff:g id="GROUP">%s</xliff:g> guruhiga xabar: <xliff:g id="MESSAGE">%s</xliff:g>. Vaqt: <xliff:g id="TIME">%s</xliff:g>"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Muvaffaqiyatsiz xabar. Qayta urinish uchun bosing."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"<xliff:g id="PARTICIPANTS">%s</xliff:g> bilan suhbat"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Mavzuni o‘chirish"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Video tasvirga olish"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Harakatsiz rasmni suratga olish"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Rasmga olish"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Video tasvirga olishni boshlash"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"To‘liq ekranli kameraga o‘tish"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Old va orqa kamera o‘rtasida almashish"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Tasvirga olishni to‘xtatish va faylni biriktirish"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Videoga olishni to‘xtatish"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"SMS/MMS rasmlari"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ta rasm “<xliff:g id="ALBUMNAME_3">%s</xliff:g>” albomiga saqlandi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ta rasm “<xliff:g id="ALBUMNAME_1">%s</xliff:g>” albomiga saqlandi</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ta video “<xliff:g id="ALBUMNAME_3">%s</xliff:g>” albomiga saqlandi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ta video “<xliff:g id="ALBUMNAME_1">%s</xliff:g>” albomiga saqlandi</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ta biriktirma “<xliff:g id="ALBUMNAME_3">%s</xliff:g>” albomiga saqlandi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ta biriktirma “<xliff:g id="ALBUMNAME_1">%s</xliff:g>” albomiga saqlandi</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ta biriktirma “Yuklanishlar” jildiga saqlandi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ta biriktirma “Yuklanishlar” jildiga saqlandi</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ta biriktirma saqlandi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ta biriktirma saqlandi</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> ta biriktirmani saqlab bo’lmadi</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ta biriktirmani saqlab bo’lmadi</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"MMS fayli saqlandi"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Sozlamalar"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Arxivlangan"</string>
+ <string name="action_close" msgid="1840519376200478419">"Yopish"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Qo‘shimcha"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Tuzatish"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Bildirishnomalar"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Ovoz"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Ovozsiz"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Tebranish"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Bloklandi"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"SMS xabarlarning yetkazilganlik hisobotlari"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Yuborilgan har bir SMS uchun yetkazilganlik hisobotini talab qilish"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Avtomatik yuklanish"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"MMS xabarlarni avtomatik yuklab olish"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Rouming vaqtida avtomatik yuklab olish"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Rouming vaqtida MMS xabarlarni avtomatik yuklab olish"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Guruh xabarlari"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Bitta xabarni bir nechta qabul qiluvchiga yuborish uchun MMS xabardan foydalanilsin"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Birlamchi SMS ilovasi"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Birlamchi SMS ilovasi"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Telefon raqamingiz"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Noma’lum"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Chiquvchi xabar ovozlari"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"SMS xabarni o‘tkazish"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"SMS orqali olingan qayta ishlanmagan ma’lumotlarni tashqi xotiraga o‘tkazish"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"MMS xabarni o‘tkazish"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"MMS orqali olingan qayta ishlanmagan ma’lumotlarni tashqi xotiraga o‘tkazish"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Aholini ogohlantirish"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Xabar sozlamalari"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Matndan nusxa olish"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Tafsilotlarni ko‘rish"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"O‘chirish"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Qayta yo‘naltirish"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Xabar tafsilotlari"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Turi: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Matnli xabar"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Multimediali xabar"</string>
+ <string name="from_label" msgid="1947831848146564875">"Kimdan: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Kimga: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Yuborildi: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Qabul qilindi: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Mavzu: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Hajmi: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Ustivorlik: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM-karta: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Yuqori"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"O‘rtacha"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Past"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"<xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>-SIM karta"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Yuboruvchining manzili yashirilgan"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Biriktirilgan fayllarni yuklab olayotganda xabar yuborib bo‘lmaydi."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Biriktirma faylini yuklab bo‘lmadi. Qaytadan urining."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Tarmoq hali tayyor emas. Qaytadan urining."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Matnni o‘chirish"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Harf va raqam kiritish o‘rtasida almashish"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Yana ishtirokchi qo‘shish"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Ishtirokchilarni tasdiqlang"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Yangi suhbat boshlash"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Ushbu variantni tanlash"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Videoni ijro qilish"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Odamlar va tanlamalar"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Tuzatish"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Odamlar va tanlamalar"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Umumiy"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Ushbu suhbat ishtirokchilari"</string>
+ <string name="action_call" msgid="6596167921517350362">"Qo‘ng‘iroq qilish"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Xabar yuborish"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;<xliff:g id="SIM_NAME">%s</xliff:g> orqali&lt;/small&gt; xabar yuboring"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Rasmlarni yuborish</item>
+ <item quantity="one">Rasmni yuborish</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Audio faylni yuborish</item>
+ <item quantity="one">Audio faylni yuborish</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Videolarni yuborish</item>
+ <item quantity="one">Videoni yuborish</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Kontaktlar kartasini yuborish</item>
+ <item quantity="one">Kontaktlar kartasini yuborish</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Biriktirmani yuborish</item>
+ <item quantity="one">Biriktirmani yuborish</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> ta biriktirma yuborishga tayyor</item>
+ <item quantity="one">Biriktirma yuborishga tayyor</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Fikr va mulohaza yuborish"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Play Marketdan qidirish"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Versiyasi"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"%1$s versiya"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Ochiq kodli DT litsenziyalari"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Bildirishnomalar"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Biriktirma chekloviga yetdi"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Biriktirmani yuklab bo‘lmadi."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Kontaktlarga qo‘shilsinmi?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Kontakt qo‘shish"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Mavzu"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Mavzu: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Kontaktlar kartasi yuklanmoqda"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Kontaktlar kartasini yuklab bo‘lmadi"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Kontaktlar kartasini ko‘rish"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ta kontakt</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ta kontakt</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Kontakt kartalari"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Tug‘ilgan kun"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Qaydlar"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Xabarni qayta yo‘naltirish"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Javob berish"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"SMS o‘chirib qo‘yilgan"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Yuborish uchun SMS/MMS xizmatini birlamchi SMS ilovasi sifatida o‘rnating"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"SMS/MMS xizmatini birlamchi SMS ilovasi sifatida o‘rnating"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"O‘zgartirish"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Xabarlarni qabul qilish uchun SMS/MMS xizmatini birlamchi SMS ilovasi sifatida o‘rnating"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"SMS xabarlarni yuborish uchun ma’qul SIM-karta tanlanmagan"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Qurilma egasi ushbu ilovadan foydalanishga ruxsat bermagan."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Suhbatdagi ishtirokchilar soni juda ko‘p"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Kontaktlar topilmadi</item>
+ <item quantity="one">Kontakt topilmadi</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Kameradagi rasmni yuklab bo‘lmadi"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Siz: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Qoralama"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Yangi suhbat boshlasangiz, u bu yerda ko‘rinadi"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Arxivlangan suhbatlar bu yerda ko‘rinadi"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Suhbatlar yuklanmoqda…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Rasm"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Audio-klip"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Kontaktlar kartasi"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Bekor qilish"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Qayta urinish"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Yangi xabar yozish uchun kontaktning ismi yoki telefon raqamini kiriting"</string>
+ <string name="action_block" msgid="9032076625645190136">"Bloklash"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Bloklash:<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Blokdan chiqarish: <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"<xliff:g id="DESTINATION">%s</xliff:g> bloklansinmi?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Sizga ushbu raqamdan xabarlar kelishi mumkin, biroq bu haqda siz xabardor qilinmaysiz. Ushbu suhbat esa arxivlanadi."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Bloklangan kontaktlar"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"BLOKDAN CHIQARISH"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Bloklangan kontaktlar"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Hujjatlar kutubxonasidan rasm tanlang"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Xabar yuborilmoqda"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Xabar yuborildi"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Mobil internet o‘chirilgan. Sozlamalarni tekshiring."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Parvoz rejimida xabarlarni yuborib bo‘lmaydi"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Xabar yuborilmadi"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Xabar yuklab olindi"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Mobil internet o‘chirilgan. Sozlamalarni tekshiring."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Parvoz rejimida xabarlarni yuklab olib bo‘lmaydi"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Xabarni yuklab olib bo‘lmadi"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Nol"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Bir"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Ikki"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Uch"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"To‘rt"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Besh"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Olti"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Yetti"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Sakkiz"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"To‘qqiz"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"<xliff:g id="CARRIERNAME">%1$s</xliff:g> orqali xabar yuborib bo‘lmadi, xatolik kodi: <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Noma’lum aloqa operatori orqali xabar yuborib bo‘lmadi, xatolik kodi: <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Fwd: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Xabar yuborilmadi: xizmat tarmoqda faollashtirilmagan"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Xabar yuborilmadi: qabul qiluvchining manzili noto‘g‘ri"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Xabar yuborilmadi: xabar noto‘g‘ri"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Xabar yuborilmadi: kontent turi qo‘llab-quvvatlanmaydi"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Xabar yuborilmadi: xabar turi qo‘llab-quvvatlanmaydi"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Xabar yuborilmadi: juda katta"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Yangi xabar"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Ko‘rish"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Rasm"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Mos ilovani topib bo‘lmadi"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Qabul qiluvchini o‘chirish"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Yangi xabar"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Bekor qilish"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Ulanish nuqtasi sozlamalari"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Sozlanmagan"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Nomi"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS proksi-serveri"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS porti"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN turi"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"APNni o‘chirish"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"Yangi APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Saqlash"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Bekor qilish"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"APN nomi maydonchasini to‘ldirish shart."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN maydonchasini to‘ldirish shart."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC 3 ta raqamdan iborat bo‘lishi shart."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC 2 yoki 3 ta raqamdan iborat bo‘lishi shart."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Standart APN sozlamalari qayta tiklanmoqda."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Standart sozlamalarni tiklash"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Standart sozlamalar tiklandi."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Nomsiz"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Ulanish nuqtalari"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"Yangi APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Ushbu foydalanuvchi uchun Ulanish nuqtasi nomi (APN) sozlamalari mavjud emas"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Vaqtinchalik xotiraga nusxa olinsinmi?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Nusxa olish"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ushbu SIM-kartaga: <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Umumiy"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Qo‘shimcha"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Umumiy sozlamalar"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Qo‘shimcha sozlamalar"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"“<xliff:g id="SIM_NAME">%s</xliff:g>” SIM kartasi"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Har bir qabul qiluvchiga alohida SMS xabar yuboring. Faqat siz barcha javoblarni olishingiz mumkin"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Barcha qabul qiluvchilarga bir xil MMS xabar yuboring"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Noma’lum raqam"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Yangi xabar"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Yangi xabar."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM kartani tanlash"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> SIM-kartasi tanlangan"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Mavzuni tahrirlash"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"SIM kartani tanlang yoki mavzuni tahrirlang"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Audio yozib olish uchun bosib turing"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Yangi suhbat boshlash"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"SMS/MMS"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"SMS/MMS chatlari"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"SMS/MMS"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Yangi xabar"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Suhbatlar ro‘yxati"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Suhbatlar yuklanmoqda"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Xabarlar yuklanmoqda"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Ko‘proq suhbatlarni ko‘rish"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Boshqa xabarlarni ko‘rish"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Suhbat o‘chirib tashlandi"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Suhbat o‘chirib tashlandi. SMS/MMS xizmatidagi boshqa suhbatni ko‘rish uchun bosing."</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Bloklandi"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Blokdan chiqarildi"</string>
+ <string name="db_full" msgid="8459265782521418031">"Xotirada joy kam qoldi. Ba’zi ma’lumotlar saqlanmasligi mumkin."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Biriktirmalarni tanlash"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Tanlovni tasdiqlash"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> ta tanlandi"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Bir yoki bir nechta biriktirmani olib tashlab, keyin qayta urinib ko‘ring."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Xabarni yuborib ko‘rishingiz mumkin, lekin undagi bir yoki bir nechta biriktirma olib tashlanmaguncha qabul qiluvchiga yetib bormasligi mumkin."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Siz bitta xabarda faqat bitta video jo‘nata olasiz. Ortiqcha videolarni olib tashlang va qaytadan urinib ko‘ring."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Biriktirmani yuklab bo‘lmadi."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Baribir yuborilsin"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Suhbatni boshlab bo‘lmadi"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"<xliff:g id="SELECTED_SIM">%s</xliff:g> tanlandi"</string>
+</resources>
diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml
new file mode 100644
index 0000000..01cec58
--- /dev/null
+++ b/res/values-v17/styles.xml
@@ -0,0 +1,77 @@
+<?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>
+ <style name="GalleryGridItemViewCheckBoxStyle">
+ <item name="android:paddingEnd">4dp</item>
+ </style>
+
+ <style name="AttachmentGridItemViewCheckBoxStyle">
+ <item name="android:paddingEnd">4dp</item>
+ </style>
+
+ <style name="PeopleAndOptionsItemStyle">
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ </style>
+
+ <style name="AudioAttachmentViewStyle">
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ </style>
+
+ <style name="VcardAttachmentSingleStyle">
+ <item name="android:paddingStart">@dimen/message_text_left_right_padding</item>
+ <item name="android:paddingEnd">@dimen/message_text_left_right_padding</item>
+ </style>
+
+ <style name="PeopleListItemViewStyle">
+ <item name="android:paddingStart">16dp</item>
+ </style>
+
+ <style name="MessageVcardAttachmentStyle">
+ <item name="android:paddingStart">@dimen/message_text_left_right_padding</item>
+ <item name="android:paddingEnd">@dimen/message_text_left_right_padding</item>
+ </style>
+
+ <style name="CameraChooserFrameStyle">
+ <item name="android:paddingStart">16dp</item>
+ </style>
+
+ <style name="AddContactConfirmationTextStyle">
+ <item name="android:paddingStart">12dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ </style>
+
+ <style name="ContactListItemDetailType">
+ <item name="android:paddingStart">12dp</item>
+ </style>
+
+ <style name="DebugMmsConfigItemStyle">
+ <item name="android:paddingStart">16dp</item>
+ <item name="android:paddingEnd">16dp</item>
+ </style>
+
+ <style name="ApnPreferenceLayoutStyle">
+ <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ </style>
+
+ <style name="CopyContactDialogTextStyle">
+ <item name="android:paddingStart">@dimen/copy_contact_dialog_left_padding</item>
+ <item name="android:paddingEnd">@dimen/copy_contact_dialog_right_padding</item>
+ </style>
+</resources>
diff --git a/res/values-v21/colors.xml b/res/values-v21/colors.xml
new file mode 100644
index 0000000..d51ad04
--- /dev/null
+++ b/res/values-v21/colors.xml
@@ -0,0 +1,22 @@
+<?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>
+ <color name="notification_sender_text">#DE000000</color>
+ <color name="notification_secondary_text">#8A000000</color>
+ <color name="notification_tertiary_text">#8A000000</color>
+ <color name="notification_subject_color">#DE000000</color>
+</resources>
diff --git a/res/values-v21/dimens.xml b/res/values-v21/dimens.xml
new file mode 100644
index 0000000..466d2c8
--- /dev/null
+++ b/res/values-v21/dimens.xml
@@ -0,0 +1,22 @@
+<?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>
+ <dimen name="fab_bottom_margin">16dp</dimen>
+ <dimen name="fab_left_right_margin">16dp</dimen>
+ <dimen name="fab_size">56dp</dimen>
+ <dimen name="fab_padding_bottom">0dp</dimen>
+</resources>
diff --git a/res/values-v21/styles.xml b/res/values-v21/styles.xml
new file mode 100644
index 0000000..aef5d1c
--- /dev/null
+++ b/res/values-v21/styles.xml
@@ -0,0 +1,41 @@
+<?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>
+ <style name="BugleTheme.ConversationActivity"
+ parent="@style/BugleTheme.ConversationActivityBase">
+ <item name="android:colorEdgeEffect">@color/conversation_edge_effect</item>
+ </style>
+
+ <style name="BugleTheme.DialogActivity" parent="@style/Theme.AppCompat.Light.Dialog">
+ <item name="android:windowNoTitle">false</item>
+ <item name="android:background">@android:color/white</item>
+ </style>
+
+ <style name="ContactListItemDrawableIndicator">
+ <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+ </style>
+
+ <style name="ConversationActionBarTitleTextStyle" parent="BugleActionBarTitleTextStyle">
+ <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+ </style>
+
+ <style name="WidgetConversationListItemStyle">
+ <item name="android:background">@drawable/widget_item_background</item>
+ <item name="android:layout_height">72dp</item>
+ </style>
+
+</resources>
diff --git a/res/values-vi/arrays.xml b/res/values-vi/arrays.xml
new file mode 100644
index 0000000..b16f18b
--- /dev/null
+++ b/res/values-vi/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"không có chủ đề"</item>
+ <item msgid="272485471009191934">"không có chủ đề"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Có"</item>
+ <item msgid="6049132459802288033">"Không"</item>
+ <item msgid="3084376867445867895">"OK"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Xin cảm ơn"</item>
+ <item msgid="4881335087096496747">"Tôi đồng ý"</item>
+ <item msgid="2422296858597420738">"Tuyệt vời"</item>
+ <item msgid="4805581752819452687">"Đang trên đường"</item>
+ <item msgid="4746700499431366214">"OK, tôi sẽ liên hệ lại với bạn sau"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..ac23771
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Nhắn tin"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Nhắn tin"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Chọn cuộc hội thoại"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Cài đặt"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Gửi tin nhắn"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Thêm tệp đính kèm"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Trợ giúp"</string>
+ <string name="welcome" msgid="2857560951820802321">"Chào mừng"</string>
+ <string name="skip" msgid="7238879696319945853">"Bỏ qua"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Tiếp theo &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Tiếp theo"</string>
+ <string name="exit" msgid="1905187380359981199">"Thoát"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Cài đặt &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Cài đặt"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Nhắn tin cần quyền truy cập vào SMS, Điện thoại và Danh bạ."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Bạn có thể thay đổi các quyền trong Cài đặt &gt; Ứng dụng &gt; Nhắn tin &gt; Quyền."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Bạn có thể thay đổi các quyền trong Cài đặt, Ứng dụng, Nhắn tin, Quyền."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Liên hệ thường xuyên"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Tất cả liên hệ"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Gửi đến <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Chụp ảnh hoặc quay video"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Chọn hình ảnh từ thiết bị này"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Ghi âm"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Chọn ảnh"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Phương tiện được chọn."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Phương tiện không được chọn."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"Đã chọn <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"hình ảnh <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" msgid="8142175029053370662">"hình ảnh"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Ghi âm"</string>
+ <string name="action_share" msgid="2143483844803153871">"Chia sẻ"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Vừa đăng"</string>
+ <string name="posted_now" msgid="867560789350406701">"Hiện tại"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> phút</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> phút</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> giờ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> giờ</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ngày</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> ngày</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tuần</item>
+ <item quantity="one">một tuần</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> tháng</item>
+ <item quantity="one">một tháng</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> năm</item>
+ <item quantity="one">một năm</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Thông báo hạng 0"</string>
+ <string name="save" msgid="5081141452059463572">"Lưu"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Thiết bị sắp hết bộ nhớ. Nhắn tin sẽ tự động xóa các tin nhắn cũ để giải phóng dung lượng."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Sắp hết dung lượng lưu trữ"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Nhắn tin không thể gửi hoặc nhận tin nhắn cho tới khi có thêm bộ nhớ trên thiết bị của bạn."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Sắp hết dung lượng SMS. Bạn có thể cần xóa tin nhắn."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Xác nhận số điện thoại của bạn"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Bước thực hiện một lần này sẽ đảm bảo ứng dụng Nhắn tin gửi tin nhắn nhóm của bạn đúng cách."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Số điện thoại"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Xóa tất cả tin nhắn có phương tiện"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Xóa tin nhắn cũ hơn <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Tự động xóa tin nhắn cũ hơn <xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Bỏ qua"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Xóa tất cả tin nhắn có phương tiện?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Xóa tin nhắn cũ hơn <xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Xóa tin nhắn cũ hơn <xliff:g id="DURATION">%s</xliff:g> và bật tự động xóa?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> đã nói"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Bạn đã nói"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Tin nhắn từ <xliff:g id="SENDER">%s</xliff:g>:"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Bạn đã gửi tin nhắn"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Đang gửi…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Không gửi được. Hãy chạm để thử lại."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Không gửi được. Đang thử lại…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Gửi lại hoặc xóa"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Vui lòng thực hiện cuộc gọi thoại tới các dịch vụ khẩn cấp. Chúng tôi hiện không thể gửi tin nhắn văn bản của bạn."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Không thành công"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Tin nhắn MMS mới cần tải xuống"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Tin nhắn MMS mới"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Không thể tải xuống"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Chạm để thử lại"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Chạm để tải xuống"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Tải xuống hoặc xóa"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Đang tải xuống..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Tin nhắn đã hết hạn hoặc không khả dụng"</string>
+ <string name="mms_info" msgid="3402311750134118165">"kích thước: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, hết hạn: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Không thể gửi. Người nhận không hợp lệ."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Dịch vụ không được kích hoạt trên mạng"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Không thể gửi do sự cố mạng"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Tin nhắn đã hết hạn hoặc không khả dụng"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Không có chủ đề)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Người gửi không xác định"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Đã gửi"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Không thể tải xuống tin nhắn <xliff:g id="SUBJECT">%1$s</xliff:g> từ <xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Không thể hoàn tất thao tác liên quan đến cơ sở dữ liệu do sắp hết bộ nhớ"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Không gửi được tin nhắn"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Không gửi được một số tin nhắn trong Nhắn tin"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> tin nhắn trong <xliff:g id="CONVERSATIONS">%d</xliff:g> cuộc trò chuyện</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> tin nhắn trong một cuộc trò chuyện</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Không tải xuống được tin nhắn"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Không tải xuống được một số tin nhắn trong Nhắn tin"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> tin nhắn trong <xliff:g id="CONVERSATIONS">%d</xliff:g> cuộc trò chuyện</item>
+ <item quantity="one"><xliff:g id="MESSAGES_0">%d</xliff:g> tin nhắn trong một cuộc trò chuyện</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Tin nhắn tới <xliff:g id="NUMBER">%1$s</xliff:g> chưa được gửi"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Vui lòng thực hiện cuộc gọi thoại tới các dịch vụ khẩn cấp. Chúng tôi hiện không thể gửi tin nhắn văn bản của bạn tới <xliff:g id="NUMBER">%1$s</xliff:g>."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> tin nhắn mới</item>
+ <item quantity="one">Tin nhắn mới</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Bắt đầu"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Máy ảnh không khả dụng"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Máy ảnh không khả dụng"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Quay video không khả dụng"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Không thể lưu phương tiện"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Không thể chụp ảnh"</string>
+ <string name="back" msgid="1477626055115561645">"Quay lại"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Đã lưu trữ"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Xóa"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Lưu trữ"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Hủy lưu trữ"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Tắt thông báo"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Bật thông báo"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Thêm liên hệ"</string>
+ <string name="action_download" msgid="7786338136368564146">"Tải xuống"</string>
+ <string name="action_send" msgid="377635240181672039">"Gửi"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Xóa"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Xóa tin nhắn này?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Không thể hoàn tác tác vụ này."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Xóa"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">Xóa những cuộc trò chuyện này?</item>
+ <item quantity="one">Xóa cuộc trò chuyện này?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Xóa"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Hủy"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Đến"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Chọn nhiều hình ảnh"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Xác nhận lựa chọn"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Không thể ghi âm thanh. Hãy thử lại."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Không thể phát âm thanh. Hãy thử lại."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Không thể lưu âm thanh. Hãy thử lại."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Chạm và giữ"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Hình ảnh"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Clip âm thanh"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Video"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Thẻ liên hệ"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Tải xuống"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Trả lời qua SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Trả lời qua MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Trả lời"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> người tham gia</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> người tham gia</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Tôi"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Đã chặn và lưu trữ địa chỉ liên hệ"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Đã bỏ chặn và hủy lưu trữ liên hệ"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"Đã lưu trữ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"Đã hủy lưu trữ <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Đã tắt thông báo"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Đã bật thông báo"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Đã hoàn tất. Hãy chạm vào Gửi lần nữa."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Đã đặt thành công Nhắn tin là ứng dụng SMS mặc định."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">Hủy tệp đính kèm</item>
+ <item quantity="one">Hủy tệp đính kèm</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Tệp âm thanh đính kèm"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Phát tệp âm thanh đính kèm"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Tạm dừng"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Tin nhắn từ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Không gửi được tin nhắn từ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Tin nhắn từ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Chưa gửi được tin nhắn tới <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Đang gửi tin nhắn tới <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Không gửi được tin nhắn tới <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Tin nhắn tới <xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Không gửi được tin nhắn từ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Tin nhắn từ <xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Chưa gửi được tin nhắn tới <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Đang gửi tin nhắn tới <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Không gửi được tin nhắn tới <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Tin nhắn tới <xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Thời gian: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Không gửi được tin nhắn. Hãy chạm để thử lại."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Cuộc trò chuyện với <xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Xóa chủ đề"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Quay video"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Chụp ảnh tĩnh"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Chụp ảnh"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Bắt đầu quay video"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Chuyển sang máy ảnh toàn màn hình"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Chuyển đổi giữa máy ảnh trước và sau"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Dừng quay và đính kèm video"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Dừng ghi video"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Ảnh trong Nhắn tin"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> ảnh đã được lưu vào album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> ảnh đã được lưu vào album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> video đã được lưu vào album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> video đã được lưu vào album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> tệp đính kèm đã được lưu vào album \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> tệp đính kèm đã được lưu vào album \"<xliff:g id="ALBUMNAME_1">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> tệp đính kèm đã được lưu vào \"Nội dung tải xuống\"</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> tệp đính kèm đã được lưu vào \"Nội dung tải xuống\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">Đã lưu <xliff:g id="QUANTITY_1">%d</xliff:g> tệp đính kèm</item>
+ <item quantity="one">Đã lưu <xliff:g id="QUANTITY_0">%d</xliff:g> tệp đính kèm</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">Không thể lưu <xliff:g id="QUANTITY_1">%d</xliff:g> tệp đính kèm</item>
+ <item quantity="one">Không thể lưu <xliff:g id="QUANTITY_0">%d</xliff:g> tệp đính kèm</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Đã lưu tệp đính kèm MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Cài đặt"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Đã lưu trữ"</string>
+ <string name="action_close" msgid="1840519376200478419">"Đóng"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Nâng cao"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Gỡ lỗi"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Thông báo"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Âm thanh"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Im lặng"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Rung"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Đã chặn"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Báo cáo gửi SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Yêu cầu báo cáo gửi cho mỗi SMS bạn gửi"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Tự động truy xuất"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Tự động truy xuất tin nhắn MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Tự động truy xuất khi chuyển vùng"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Tự động truy xuất MMS khi chuyển vùng"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Nhắn tin theo nhóm"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Sử dụng MMS để gửi một tin nhắn duy nhất khi có nhiều người nhận"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Ứng dụng SMS mặc định"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Ứng dụng SMS mặc định"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Số điện thoại của bạn"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Không xác định"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Âm thanh tin nhắn đi"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Kết xuất SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Kết xuất dữ liệu thô SMS đã nhận vào tệp bộ nhớ ngoài"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Kết xuất MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Kết xuất dữ liệu thô MMS đã nhận vào tệp bộ nhớ ngoài"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Cảnh báo không dây"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Tùy chọn tin nhắn"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Sao chép văn bản"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Xem chi tiết"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Xóa"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Chuyển tiếp"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Chi tiết tin nhắn"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Loại: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Tin nhắn văn bản"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Tin nhắn đa phương tiện"</string>
+ <string name="from_label" msgid="1947831848146564875">"Người gửi: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Người nhận: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Đã gửi: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Đã nhận: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Chủ đề: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Kích cỡ: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Mức độ ưu tiên: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Cao"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Bình thường"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Thấp"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Địa chỉ người gửi bị ẩn"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Không thể gửi tin nhắn trong khi tải tệp đính kèm."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Không thể tải tệp đính kèm. Hãy thử lại."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Mạng chưa sẵn sàng. Hãy thử lại."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Xóa văn bản"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Chuyển đổi giữa nhập văn bản và số"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Thêm người tham gia khác"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Xác nhận người tham gia"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Bắt đầu cuộc trò chuyện mới"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Chọn mục này"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Phát video"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Danh bạ và tùy chọn"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Gỡ lỗi"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Danh bạ và tùy chọn"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Chung"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Mọi người trong cuộc hội thoại này"</string>
+ <string name="action_call" msgid="6596167921517350362">"Gọi điện"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Gửi tin nhắn"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Gửi tin nhắn&lt;br/&gt;&lt;nhỏ&gt;từ <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/nhỏ&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">Gửi ảnh</item>
+ <item quantity="one">Gửi ảnh</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">Gửi âm thanh</item>
+ <item quantity="one">Gửi âm thanh</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">Gửi video</item>
+ <item quantity="one">Gửi video</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">Gửi danh thiếp</item>
+ <item quantity="one">Gửi danh thiếp</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">Gửi tệp đính kèm</item>
+ <item quantity="one">Gửi tệp đính kèm</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> tệp đính kèm đã sẵn sàng gửi</item>
+ <item quantity="one">Một tệp đính kèm đã sẵn sàng gửi</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Gửi phản hồi"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Xem trong Cửa hàng Google Play"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Thông tin phiên bản"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Phiên bản %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Giấy phép nguồn mở"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Thông báo"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Đã đạt đến giới hạn tệp đính kèm"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Không tải được tệp đính kèm."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Thêm vào Danh bạ?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Thêm liên hệ"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Chủ đề"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Chủ đề: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Đang tải thẻ liên hệ"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Không thể tải danh thiếp"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Xem thẻ liên hệ"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> liên hệ</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> liên hệ</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Danh thiếp"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Ngày sinh"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Ghi chú"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Chuyển tiếp thư"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Trả lời"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"Đã tắt tính năng nhắn tin SMS"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Để gửi, hãy đặt Nhắn tin là ứng dụng SMS mặc định"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Đặt Nhắn tin là ứng dụng SMS mặc định"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Thay đổi"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Để nhận tin nhắn, hãy đặt Nhắn tin là ứng dụng SMS mặc định"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Không có SIM ưu tiên nào được chọn để gửi tin nhắn SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Ứng dụng này không được chủ sở hữu thiết bị cho phép."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"OK"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Có quá nhiều người tham gia trong một cuộc trò chuyện"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">Liên hệ không hợp lệ</item>
+ <item quantity="one">Liên hệ không hợp lệ</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Không thể tải hình ảnh trong máy ảnh"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Bạn: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Thư nháp"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Sau khi bạn bắt đầu một cuộc trò chuyện mới, bạn sẽ thấy cuộc trò chuyện đó được liệt kê ở đây"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Các cuộc trò chuyện đã lưu trữ xuất hiện ở đây"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Đang tải cuộc trò chuyện..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Hình ảnh"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Clip âm thanh"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Video"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Thẻ liên hệ"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Hoàn tác"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Thử lại"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Nhập tên liên hệ hoặc số điện thoại để bắt đầu tin nhắn mới"</string>
+ <string name="action_block" msgid="9032076625645190136">"Chặn"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Chặn <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Bỏ chặn <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Chặn <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Bạn sẽ tiếp tục nhận tin nhắn từ số này nhưng bạn sẽ không được thông báo nữa. Cuộc trò chuyện này sẽ được lưu trữ."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Địa chỉ liên hệ bị chặn"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"BỎ CHẶN"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Địa chỉ liên hệ bị chặn"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Chọn hình ảnh từ thư viện tài liệu"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Đang gửi tin nhắn"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Đã gửi tin nhắn"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Dữ liệu di động đã bị tắt. Hãy kiểm tra cài đặt của bạn."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Không thể gửi tin nhắn ở Chế độ trên máy bay"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Không thể gửi thư"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Đã tải tin nhắn xuống"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Dữ liệu di động đã bị tắt. Hãy kiểm tra cài đặt của bạn."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Không thể tải xuống tin nhắn ở Chế độ trên máy bay"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Không thể tải xuống tin nhắn"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Không"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Một"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Hai"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Ba"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Bốn"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Năm"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Sáu"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Bảy"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Tám"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Chín"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Không thể gửi tin nhắn bằng <xliff:g id="CARRIERNAME">%1$s</xliff:g>, lỗi <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Không thể gửi tin nhắn bằng nhà cung cấp dịch vụ không xác định, lỗi <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Chuyển tiếp: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Không gửi được tin nhắn: dịch vụ chưa được kích hoạt trên mạng"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Không gửi được tin nhắn: địa chỉ đích không hợp lệ"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Không gửi được tin nhắn: tin nhắn không hợp lệ"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Không gửi được tin nhắn: nội dung không được hỗ trợ"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Không gửi được tin nhắn: tin nhắn không được hỗ trợ"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Không gửi được tin nhắn: quá lớn"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Tin nhắn mới"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Xem"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Hình ảnh"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Không thể tìm thấy ứng dụng thích hợp"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Xóa người nhận"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Tin nhắn mới"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Hủy"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Chỉnh sửa điểm truy cập"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Chưa đặt"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Tên"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Proxy của MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Cổng MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Loại APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Xóa APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"APN mới"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Lưu"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Hủy"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Không được để trống trường Tên."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"Không được để trống APN."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Trường MCC phải có 3 chữ số."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Trường MNC phải có 2 hoặc 3 chữ số."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Đang khôi phục cài đặt APN mặc định."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Đặt lại về mặc định"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Đặt lại cài đặt APN mặc định đã hoàn tất."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Không có tiêu đề"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Tên điểm truy cập"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"APN mới"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Cài đặt Tên điểm truy cập không khả dụng cho người dùng này"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Sao chép vào khay nhớ tạm?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Sao chép"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"tới <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Chung"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Nâng cao"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Cài đặt chung"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Cài đặt nâng cao"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM \"<xliff:g id="SIM_NAME">%s</xliff:g>\""</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Gửi từng tin nhắn SMS cho tất cả người nhận. Chỉ bạn mới nhận được mọi tin nhắn trả lời"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Gửi một MMS cho tất cả người nhận"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Số điện thoại không xác định"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Tin nhắn mới"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Tin nhắn mới."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Bộ chọn SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"Đã chọn <xliff:g id="SIM_0">%1$s</xliff:g>, bộ chọn SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Chỉnh sửa chủ đề"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Chọn SIM hoặc chỉnh sửa chủ đề"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Chạm và giữ để ghi âm"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Bắt đầu cuộc trò chuyện mới"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Nhắn tin"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Danh sách Nhắn tin"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Nhắn tin"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Tin nhắn mới"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Danh sách cuộc hội thoại"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Đang tải cuộc trò chuyện..."</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Đang tải thư"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Xem các cuộc hội thoại khác"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Xem thêm tin nhắn"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Đã xóa cuộc hội thoại"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Đã xóa cuộc trò chuyện. Chạm để hiển thị cuộc trò chuyện khác trong Nhắn tin"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Đã chặn"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Đã bỏ chặn"</string>
+ <string name="db_full" msgid="8459265782521418031">"Dung lượng bộ nhớ thấp. Một số dữ liệu có thể bị mất."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Chọn tài liệu đính kèm"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Xác nhận lựa chọn"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"Đã chọn <xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Vui lòng xóa một hoặc nhiều tệp đính kèm và thử lại."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Bạn có thể thử gửi tin nhắn của mình nhưng tin nhắn có thể không gửi được trừ khi bạn xóa một hoặc nhiều tệp đính kèm."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Bạn chỉ có thể gửi một video cho mỗi tin nhắn. Hãy xóa các video khác rồi thử lại."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Ứng dụng Nhắn tin không tải được tệp đính kèm."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Vẫn gửi"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Không thể bắt đầu cuộc trò chuyện"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"Đã chọn <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml
new file mode 100644
index 0000000..9512884
--- /dev/null
+++ b/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"无主题"</item>
+ <item msgid="272485471009191934">"无主题"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"是的"</item>
+ <item msgid="6049132459802288033">"不"</item>
+ <item msgid="3084376867445867895">"好"</item>
+ <item msgid="3155097332660174689">"呵呵"</item>
+ <item msgid="2611328818571146775">"谢谢"</item>
+ <item msgid="4881335087096496747">"同意"</item>
+ <item msgid="2422296858597420738">"不错"</item>
+ <item msgid="4805581752819452687">"在路上"</item>
+ <item msgid="4746700499431366214">"稍后再回复你"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..1a128fc
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"短信"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"短信"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"选择对话"</string>
+ <string name="action_settings" msgid="1329008122345201684">"设置"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"发送信息"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"添加附件"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"帮助"</string>
+ <string name="welcome" msgid="2857560951820802321">"欢迎使用"</string>
+ <string name="skip" msgid="7238879696319945853">"跳过"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"下一步 &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"下一步"</string>
+ <string name="exit" msgid="1905187380359981199">"退出"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"设置 &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"设置"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"“短信”应用需要获得“短信”、“电话”和“通讯录”的访问权限。"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"您可以在“设置”&gt;“应用”&gt;“短信”&gt;“权限”中更改权限。"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"您可以依次触摸“设置”、“应用”、“短信”和“权限”来更改权限。"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"常用联系人"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"所有联系人"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"发送到<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"拍照或录像"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"选择此设备上的图片"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"录制语音"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"选择照片"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"已选择此媒体。"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"已取消选择此媒体。"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"已选择<xliff:g id="COUNT">%d</xliff:g>项"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"图片(<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" msgid="8142175029053370662">"图片"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"录制语音"</string>
+ <string name="action_share" msgid="2143483844803153871">"分享"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"刚刚"</string>
+ <string name="posted_now" msgid="867560789350406701">"刚刚"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 分钟</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 分钟</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 小时</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 小时</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 天</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 天</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 周</item>
+ <item quantity="one">1 周</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 个月</item>
+ <item quantity="one">1 个月</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 年</item>
+ <item quantity="one">1 年</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"0级信息"</string>
+ <string name="save" msgid="5081141452059463572">"保存"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"设备存储空间不足。“短信”将自动删除较早的信息,以便腾出存储空间。"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"存储空间不足"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"除非您的设备上腾出更多可用空间,否则“短信”可能无法收发信息。"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"短信存储空间不足,建议您删除部分信息。"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"确认您的电话号码"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"执行这个一次性步骤可确保“短信”正确地传送您的群发信息。"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"电话号码"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"删除所有含媒体内容的信息"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"删除<xliff:g id="DURATION">%s</xliff:g>以前的信息"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"自动删除超过 <xliff:g id="DURATION">%s</xliff:g>以前的信息"</string>
+ <string name="ignore" msgid="7063392681130898793">"忽略"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"要删除所有含媒体内容的信息吗?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"要删除超过 <xliff:g id="DURATION">%s</xliff:g>以前的信息吗?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"要删除超过 <xliff:g id="DURATION">%s</xliff:g>以前的信息并开启自动删除功能吗?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g>说"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"您说"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"来自<xliff:g id="SENDER">%s</xliff:g>的信息"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"您发送了一条信息"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"正在发送…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"发送失败。触摸即可重试。"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"发送失败。正在重试…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"重新发送或删除"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"请拨打语音电话寻求紧急服务。目前您的信息无法成功发送。"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"失败"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"待下载的新彩信"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"新彩信"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"无法下载"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"触摸即可重试"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"触摸即可下载"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"下载或删除"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"正在下载…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"信息已过期或无法查看"</string>
+ <string name="mms_info" msgid="3402311750134118165">"信息大小:<xliff:g id="MESSAGESIZE">%1$s</xliff:g>,过期时间:<xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"无法发送。收件人无效。"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"您尚未在网络上启用该服务。"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"遇到网络问题,发送失败"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"信息已过期或无法查看"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(无主题)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"未知发件人"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"已送达"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"无法下载来自<xliff:g id="FROM">%2$s</xliff:g>的信息“<xliff:g id="SUBJECT">%1$s</xliff:g>”。"</string>
+ <string name="low_memory" msgid="5300743415198486619">"内存不足,无法完成数据库操作"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"信息发送失败"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"“短信”未发送部分信息"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> 个对话中的 <xliff:g id="MESSAGES_1">%d</xliff:g> 条信息</item>
+ <item quantity="one">1 个对话中的 <xliff:g id="MESSAGES_0">%d</xliff:g> 条信息</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"信息下载失败"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"“短信”未下载部分信息"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> 个对话中的 <xliff:g id="MESSAGES_1">%d</xliff:g> 条信息</item>
+ <item quantity="one">1 个对话中的 <xliff:g id="MESSAGES_0">%d</xliff:g> 条信息</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"未能将信息发送至 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"请拨打语音电话寻求紧急服务。目前您的信息无法发送至 <xliff:g id="NUMBER">%1$s</xliff:g>。"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> 条新信息</item>
+ <item quantity="one">新信息</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"发起"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"无法使用相机"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"无法使用相机"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"无法使用视频拍摄功能"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"无法保存媒体"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"无法拍照"</string>
+ <string name="back" msgid="1477626055115561645">"返回"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"已归档的对话"</string>
+ <string name="action_delete" msgid="4076795795307486019">"删除"</string>
+ <string name="action_archive" msgid="5437034800324083170">"归档"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"取消归档"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"关闭通知"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"开启通知"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"添加到通讯录"</string>
+ <string name="action_download" msgid="7786338136368564146">"下载"</string>
+ <string name="action_send" msgid="377635240181672039">"发送"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"删除"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"要删除此信息吗?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"该操作无法撤消。"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"删除"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">要删除这些对话吗?</item>
+ <item quantity="one">要删除此对话吗?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"删除"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"取消"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"收件人"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"选择多张图片"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"确认选择"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"无法录制语音,请重试。"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"无法播放语音,请重试。"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"无法保存语音,请重试。"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"触摸并按住"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"、 "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"图片"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"语音"</string>
+ <string name="notification_video" msgid="4331423498662606204">"视频"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"名片"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"下载"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"通过短信回复"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"通过彩信回复"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"回复"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 位参与者</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 位参与者</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"我"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"联系人已被屏蔽且已归档"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"已取消屏蔽并取消归档联系人"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"已归档 <xliff:g id="COUNT">%d</xliff:g> 个"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"已取消归档 <xliff:g id="COUNT">%d</xliff:g> 个"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"通知已关闭"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"通知已开启"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"设置已完成。请再次触摸“发送”。"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"已成功地将“短信”设为默认短信应用。"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">舍弃附件</item>
+ <item quantity="one">舍弃附件</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"语音附件"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"播放语音附件"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"暂停"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"<xliff:g id="SENDER">%s</xliff:g>发来了消息:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>未能成功发送的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>发来的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"要发送给<xliff:g id="CONTACT">%s</xliff:g>的草稿信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"正在向<xliff:g id="CONTACT">%s</xliff:g>发送的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"未能成功发送给<xliff:g id="CONTACT">%s</xliff:g>的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"发送给<xliff:g id="CONTACT">%s</xliff:g>的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>未能成功发送的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。<xliff:g id="GROUPINFO">%s</xliff:g>。"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>发来的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。<xliff:g id="GROUPINFO">%s</xliff:g>。"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"要发送给<xliff:g id="GROUP">%s</xliff:g>的草稿信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"正在向<xliff:g id="GROUP">%s</xliff:g>发送的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"未能成功发送给<xliff:g id="GROUP">%s</xliff:g>的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"发送给<xliff:g id="GROUP">%s</xliff:g>的信息,内容为:<xliff:g id="MESSAGE">%s</xliff:g>。时间为:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"信息发送失败。触摸即可重试。"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"与<xliff:g id="PARTICIPANTS">%s</xliff:g>的对话"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"删除主题"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"拍摄视频"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"拍摄静态照片"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"拍照"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"开始录制视频"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"切换到全屏相机模式"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"在前置和后置相机之间切换"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"停止拍摄并添加视频附件"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"停止录制视频"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"“短信”中的照片"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 张照片已保存至“<xliff:g id="ALBUMNAME_3">%s</xliff:g>”相册</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 张照片已保存至“<xliff:g id="ALBUMNAME_1">%s</xliff:g>”相册</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 个视频已保存至“<xliff:g id="ALBUMNAME_3">%s</xliff:g>”相册</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 个视频已保存至“<xliff:g id="ALBUMNAME_1">%s</xliff:g>”相册</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 个附件已保存至“<xliff:g id="ALBUMNAME_3">%s</xliff:g>”相册</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 个附件已保存至“<xliff:g id="ALBUMNAME_1">%s</xliff:g>”相册</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> 个附件已保存至“下载内容”</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 个附件已保存至“下载内容”</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">已保存 <xliff:g id="QUANTITY_1">%d</xliff:g> 个附件</item>
+ <item quantity="one">已保存 <xliff:g id="QUANTITY_0">%d</xliff:g> 个附件</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">无法保存 <xliff:g id="QUANTITY_1">%d</xliff:g> 个附件</item>
+ <item quantity="one">无法保存 <xliff:g id="QUANTITY_0">%d</xliff:g> 个附件</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"保存的彩信附件"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"设置"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"已归档的对话"</string>
+ <string name="action_close" msgid="1840519376200478419">"关闭"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"彩信"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"高级"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"调试"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"通知"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"提示音"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"静音"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"振动"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"已屏蔽"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"短信送达情况报告"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"了解您发的每条短信的送达情况"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"自动检索"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"自动检索彩信"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"漫游时自动检索"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"漫游时自动检索彩信"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"群发彩信"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"有多个收件人时,也使用彩信发送信息"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"默认短信应用"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"默认短信应用"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"您的手机号码"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"未知"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"信息发送提示音"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"转储短信"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"将收到的短信原始数据转储到外部存储文件中"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"转储彩信"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"将收到的彩信原始数据转储到外部存储文件中"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"无线警报"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"信息选项"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"复制文字"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"查看详情"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"删除"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"转发"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"信息详情"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"类型: "</string>
+ <string name="text_message" msgid="7415419755252205721">"文字信息"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"多媒体信息"</string>
+ <string name="from_label" msgid="1947831848146564875">"发件人: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"收件人: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"发送时间: "</string>
+ <string name="received_label" msgid="4442494712757995203">"接收时间: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"主题: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"大小: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"优先级: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM 卡: "</string>
+ <string name="priority_high" msgid="728836357310908368">"高"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"中"</string>
+ <string name="priority_low" msgid="7398724779026801851">"低"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM 卡 <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"隐藏的发件人地址"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"附件尚未加载完毕,无法发送信息。"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"无法加载附件,请重试。"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"网络尚未就绪,请重试。"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"删除文字"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"在文字和数字输入模式间切换"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"添加更多参与者"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"确认参与者"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"发起新的对话"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"选择此项内容"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"播放视频"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"参与者名单和选项"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"调试"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"参与者名单和选项"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"常规"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"此对话的参与者"</string>
+ <string name="action_call" msgid="6596167921517350362">"拨打电话"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"发送信息"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"发送信息&lt;br/&gt;&lt;small&gt;(通过<xliff:g id="SIM_NAME">%s</xliff:g>)&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">发送照片</item>
+ <item quantity="one">发送照片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">发送语音</item>
+ <item quantity="one">发送语音</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">发送视频</item>
+ <item quantity="one">发送视频</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">发送名片</item>
+ <item quantity="one">发送名片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">发送附件</item>
+ <item quantity="one">发送附件</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> 个附件待发送</item>
+ <item quantity="one">1 个附件待发送</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"发送反馈"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"在 Google Play 商店中查看"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"版本信息"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"版本%1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"开放源代码许可"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"通知"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"已达到附件上限"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"加载附件失败。"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"要添加到通讯录吗?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"添加到通讯录"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"主题"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"主题: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"正在加载名片"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"无法加载名片"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"查看名片"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 位联系人</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 位联系人</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"名片"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"生日"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"备注"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"转发信息"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"回复"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"短信功能已停用"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"要发送信息,请将“短信”设为默认短信应用"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"将“短信”设为默认短信应用"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"更改"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"要接收信息,请将“短信”设为默认短信应用"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"未选择用于发送短信的首选 SIM 卡"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"此应用未获得设备机主授权。"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"确定"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"对话中的参与者过多"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">联系人无效</item>
+ <item quantity="one">联系人无效</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"无法加载相机图像"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"你: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"草稿"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"您的短信对话将列在此处"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"已归档的对话会显示在此处"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"正在加载对话…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"照片"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"语音"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"视频"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"名片"</string>
+ <string name="mms_text" msgid="1528791558806015806">"彩信"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"撤消"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"重试"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"要发送新信息,请输入联系人姓名或电话号码"</string>
+ <string name="action_block" msgid="9032076625645190136">"屏蔽"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"屏蔽<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"取消屏蔽<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"要屏蔽<xliff:g id="DESTINATION">%s</xliff:g>吗?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"您将继续接收来自此号码的信息,但不会再收到相关通知。此对话将被归档。"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"屏蔽的联系人"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"取消屏蔽"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"屏蔽的联系人"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"从文档库中选择图片"</string>
+ <string name="sending_message" msgid="6363584950085384929">"正在发送消息"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"消息已发送"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"移动数据网络已关闭。请检查您的设置。"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"在飞行模式下无法发送信息"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"无法发送信息"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"彩信已下载"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"移动数据网络已关闭。请检查您的设置。"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"在飞行模式下无法下载信息"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"无法下载信息"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"0"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"1"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"2"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"3"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"4"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"5"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"6"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"7"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"8"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"9"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"无法发送消息(运营商:<xliff:g id="CARRIERNAME">%1$s</xliff:g>,错误代码:<xliff:g id="ERRORCODE">%2$d</xliff:g>)"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"无法发送消息(运营商:未知,错误代码:<xliff:g id="ERRORCODE">%1$d</xliff:g>)"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"转发:<xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"信息发送失败:服务未在网络上激活"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"信息发送失败:目标地址无效"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"信息发送失败:信息无效"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"信息发送失败:内容不受支持"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"信息发送失败:信息不受支持"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"信息发送失败:信息过长"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"新信息"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"查看"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"图片"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"找不到合适的应用"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"移除收件人"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"新消息"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"取消"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"修改接入点"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"未设置"</string>
+ <string name="apn_name" msgid="1572691851070894985">"名称"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"彩信代理"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"彩信端口"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN 类型"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"删除 APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"新建 APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"保存"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"舍弃"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"“名称”字段不能为空。"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN 不能为空。"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC 字段必须为 3 位数。"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC 字段必须为 2 位数或 3 位数。"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"正在恢复默认 APN 设置。"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"重置为默认值"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"已重置默认 APN 设置。"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"未命名"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"接入点名称"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"新建 APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"此用户无权修改接入点名称设置"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"要复制到剪贴板吗?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"复制"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"发至<xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"常规"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"高级"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"常规设置"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"高级设置"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"SIM 卡 “<xliff:g id="SIM_NAME">%s</xliff:g>”"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"将单条短信发送给所有收件人。只有您能收到相应回复"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"将单条彩信发送给所有收件人"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"未知号码"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"新信息"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"新信息。"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM 卡选择器"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"已选择<xliff:g id="SIM_0">%1$s</xliff:g>,SIM 卡选择器"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"修改主题"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"选择 SIM 卡或修改主题"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"触摸并按住即可录制语音"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"发起新的对话"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"短信"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"短信列表"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"短信"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"新信息"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"对话列表"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"正在加载对话"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"正在加载信息"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"查看更多对话"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"查看更多信息"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"对话已删除"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"对话已删除。触摸可显示其他“短信”对话"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"已屏蔽"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"已取消屏蔽"</string>
+ <string name="db_full" msgid="8459265782521418031">"存储空间不足,部分数据可能会丢失。"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"选择附件"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"确认选择"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"已选择 <xliff:g id="COUNT">%d</xliff:g> 个附件"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"请移除一个或多个附件,然后重试。"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"您可以尝试发送信息,但您可能需要移除一个或多个附件才能成功发送。"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"您在每条消息中只能发送一个视频。请移除多余的视频,然后重试。"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"“短信”无法加载附件。"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"仍然发送"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"无法发起对话"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"已选择<xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-zh-rHK/arrays.xml b/res/values-zh-rHK/arrays.xml
new file mode 100644
index 0000000..ab1cad4
--- /dev/null
+++ b/res/values-zh-rHK/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"沒有主旨"</item>
+ <item msgid="272485471009191934">"無主旨"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"是"</item>
+ <item msgid="6049132459802288033">"否"</item>
+ <item msgid="3084376867445867895">"確定"</item>
+ <item msgid="3155097332660174689">"呵呵"</item>
+ <item msgid="2611328818571146775">"謝謝"</item>
+ <item msgid="4881335087096496747">"我同意"</item>
+ <item msgid="2422296858597420738">"很好"</item>
+ <item msgid="4805581752819452687">"我正在路上"</item>
+ <item msgid="4746700499431366214">"好,我稍後再回覆您"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..a93ba92
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"訊息"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"訊息"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"選取對話群組"</string>
+ <string name="action_settings" msgid="1329008122345201684">"設定"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"傳送訊息"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"新增附件"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"說明"</string>
+ <string name="welcome" msgid="2857560951820802321">"歡迎"</string>
+ <string name="skip" msgid="7238879696319945853">"略過"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"繼續 &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"下一步"</string>
+ <string name="exit" msgid="1905187380359981199">"結束"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"設定 &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"設定"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"「短訊」需要存取短訊、手機和聯絡人的權限。"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"您可以前往 [設定] &gt; [應用程式] &gt; [短訊] &gt; [權限] 變更權限。"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"您可以前往 [設定]、[應用程式]、[短訊]、[權限] 變更權限。"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"常用聯絡人"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"所有聯絡人"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"傳送至 <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"拍照或錄影"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"從這部裝置選擇圖片"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"錄音"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"選擇相片"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"已選取媒體。"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"已取消選取媒體。"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"已選取 <xliff:g id="COUNT">%d</xliff:g> 個項目"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"<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" msgid="8142175029053370662">"圖片"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"錄音"</string>
+ <string name="action_share" msgid="2143483844803153871">"分享"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"剛剛"</string>
+ <string name="posted_now" msgid="867560789350406701">"現在"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>分鐘</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>分鐘</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>小時</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>小時</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>天</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>天</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>個星期</item>
+ <item quantity="one">一個星期</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>個月</item>
+ <item quantity="one">一個月</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>年</item>
+ <item quantity="one">一年</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"等級 0 訊息"</string>
+ <string name="save" msgid="5081141452059463572">"儲存"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"裝置儲存空間不足。「短訊」會自動刪除較舊的訊息以騰出空間。"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"儲存空間即將用盡"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"「短訊」需要更多裝置空間方可發送或接收訊息。"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"短訊儲存空間即將用完,建議您刪除部分訊息。"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"確認您的電話號碼"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"這個單一步驟能確保「短訊」正確發送群組訊息。"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"電話號碼"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"刪除所有附有媒體的訊息"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"自動刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息"</string>
+ <string name="ignore" msgid="7063392681130898793">"略過"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"要刪除所有附有媒體檔案的訊息嗎?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"要刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息嗎?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"要刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息並開啟自動刪除嗎?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"傳送者:<xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"您說"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"來自<xliff:g id="SENDER">%s</xliff:g>的訊息"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"您傳送了訊息"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"正在傳送…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"訊息傳送失敗,輕觸即可再試一次。"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"訊息傳送失敗,正在重試…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"重新傳送或刪除"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"請致電緊急服務。目前,您的短訊無法送達。"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"失敗"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"下載新的多媒體訊息"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"新的多媒體訊息"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"無法下載"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"輕觸即可再試一次"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"輕觸即可下載"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"下載或刪除"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"正在下載…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"訊息已過期或無法取得"</string>
+ <string name="mms_info" msgid="3402311750134118165">"大小:<xliff:g id="MESSAGESIZE">%1$s</xliff:g>,期限:<xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"收件者無效,因此無法傳送訊息。"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"您還未在網絡上啟用這項服務"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"網絡發生問題,因此無法傳送"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"訊息已過期或無法取得"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(沒有主旨)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"未知的寄件者"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"已傳送"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"無法下載 <xliff:g id="FROM">%2$s</xliff:g> 傳送的這則訊息「<xliff:g id="SUBJECT">%1$s</xliff:g>」。"</string>
+ <string name="low_memory" msgid="5300743415198486619">"記憶體不足,因此無法完成資料庫操作"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"無法傳送訊息"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"部分訊息未能在「短訊」中發送"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g>個對話中的<xliff:g id="MESSAGES_1">%d</xliff:g>則訊息</item>
+ <item quantity="one">一個對話中的<xliff:g id="MESSAGES_0">%d</xliff:g>則訊息</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"仍未下載訊息"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"部分訊息未能在「短訊」中下載"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g>個對話中的<xliff:g id="MESSAGES_1">%d</xliff:g>則訊息</item>
+ <item quantity="one">一個對話中的<xliff:g id="MESSAGES_0">%d</xliff:g>則訊息</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"未能傳送訊息至 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"請致電緊急服務。目前,您的短訊無法送達至 <xliff:g id="NUMBER">%1$s</xliff:g>。"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> 則新訊息</item>
+ <item quantity="one">新訊息</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"開始"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"無法使用相機"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"無法使用相機"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"無法錄製影片"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"無法儲存媒體檔案"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"無法拍照"</string>
+ <string name="back" msgid="1477626055115561645">"返回"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"已封存"</string>
+ <string name="action_delete" msgid="4076795795307486019">"刪除"</string>
+ <string name="action_archive" msgid="5437034800324083170">"封存"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"取消封存"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"關閉通知"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"開啟通知"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"新增聯絡人"</string>
+ <string name="action_download" msgid="7786338136368564146">"下載"</string>
+ <string name="action_send" msgid="377635240181672039">"傳送"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"刪除"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"要刪除這個訊息嗎?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"此操作將無法復原。"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"刪除"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">要刪除這些對話嗎?</item>
+ <item quantity="one">要刪除這個對話嗎?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"刪除"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"取消"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"收件者︰"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"選取多個圖片"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"確認選取"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"超過 <xliff:g id="COUNT">%d</xliff:g> 個項目"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"無法錄製語音,請再試一次。"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"無法播放語音,請再試一次。"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"無法儲存語音,請再試一次。"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"輕觸並按住"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"、 "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"圖片"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"錄音片段"</string>
+ <string name="notification_video" msgid="4331423498662606204">"影片"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"聯絡人卡"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"下載"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"透過短訊回覆"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"透過 MMS 回覆"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"回覆"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g>位參加者</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g>位參加者</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"我"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"已封鎖並封存聯絡人"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"已解除封鎖並取消封存聯絡人"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"已封存 <xliff:g id="COUNT">%d</xliff:g> 個對話記錄"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"已取消封存 <xliff:g id="COUNT">%d</xliff:g> 個項目"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"已關閉通知"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"已開啟通知"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"準備就緒,輕觸 [重新傳送]。"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"已成功將「短訊」設定為預設短訊應用程式。"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">捨棄附件</item>
+ <item quantity="one">捨棄附件</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"音效檔案附件"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"播放音效檔案附件"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"暫停"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"來自<xliff:g id="SENDER">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>未能成功傳送的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>傳送的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"尚未傳送給<xliff:g id="CONTACT">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"正在傳送給<xliff:g id="CONTACT">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"未能成功傳送給<xliff:g id="CONTACT">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"傳送給<xliff:g id="CONTACT">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>未能成功傳送的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。<xliff:g id="GROUPINFO">%s</xliff:g>。"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>傳送的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。<xliff:g id="GROUPINFO">%s</xliff:g>。"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"尚未傳送給<xliff:g id="GROUP">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"正在傳送給<xliff:g id="GROUP">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"未能成功傳送給<xliff:g id="GROUP">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"傳送給<xliff:g id="GROUP">%s</xliff:g>的訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"未能成功傳送的訊息。輕觸即可重試。"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"與<xliff:g id="PARTICIPANTS">%s</xliff:g>的對話"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"刪除主旨"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"拍攝影片"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"拍攝靜態圖像"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"拍照"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"開始錄製影片"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"切換至全屏攝像"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"切換前置和後置鏡頭"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"停止錄製並附加影片"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"停止錄製影片"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"「短訊」相片"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 張相片已儲存至「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」相簿</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 張相片已儲存至「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」相簿</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 部影片已儲存至「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」相簿</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 部影片已儲存至「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」相簿</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 個附件已儲存至「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」相簿</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 個附件已儲存至「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」相簿</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> 個附件已儲存至「下載」</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 個附件已儲存至「下載」</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">已儲存<xliff:g id="QUANTITY_1">%d</xliff:g>個附件</item>
+ <item quantity="one">已儲存<xliff:g id="QUANTITY_0">%d</xliff:g>個附件</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">無法儲存<xliff:g id="QUANTITY_1">%d</xliff:g>個附件</item>
+ <item quantity="one">無法儲存<xliff:g id="QUANTITY_0">%d</xliff:g>個附件</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"已儲存的 MMS 附件"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"設定"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"已封存"</string>
+ <string name="action_close" msgid="1840519376200478419">"關閉"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"進階"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"除錯"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"通知"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"音效"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"靜音"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"震動"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"已封鎖"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"短訊傳送報告"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"每次發送短訊後要求發送報告"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"自動擷取"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"自動擷取 MMS 訊息"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"漫遊時自動擷取"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"漫遊時自動擷取 MMS 訊息"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"群組短訊"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"有多位收件者時只發送一則 MMS 訊息"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"預設短訊應用程式"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"預設短訊應用程式"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"您的電話號碼"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"不明"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"傳送訊息音效"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"傾印短訊"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"將收到的短訊原始資料傾印到外部儲存檔案"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"傾印 MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"將收到的 MMS 原始資料傾印到外部儲存檔案"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"無線警示"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"訊息選項"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"複製文字"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"查看詳情"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"刪除"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"轉寄"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"訊息詳情"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"類型: "</string>
+ <string name="text_message" msgid="7415419755252205721">"短訊"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"多媒體訊息"</string>
+ <string name="from_label" msgid="1947831848146564875">"寄件者: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"收件者: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"傳送時間: "</string>
+ <string name="received_label" msgid="4442494712757995203">"接收時間: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"主旨: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"大小: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"重要: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM 卡: "</string>
+ <string name="priority_high" msgid="728836357310908368">"高"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"正常"</string>
+ <string name="priority_low" msgid="7398724779026801851">"低"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM 卡 <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"隱藏的寄件者地址"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"載入附件時無法傳送訊息。"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"無法載入附件,請再試一次。"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"網絡尚未準備好,請再試一次。"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"刪除文本"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"切換文字和數字輸入"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"新增更多參與者"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"確認參與者"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"開始新的對話"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"選取這個項目"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"播放影片"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"參與者和選項"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"除錯"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"參與者和選項"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"一般"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"這個對話的參與者"</string>
+ <string name="action_call" msgid="6596167921517350362">"進行通話"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"傳送訊息"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;br/&gt;&lt;small&gt;從 <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt; 發送短訊"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">傳送相片</item>
+ <item quantity="one">傳送相片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">傳送音效檔案</item>
+ <item quantity="one">傳送音效檔案</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">傳送影片</item>
+ <item quantity="one">傳送影片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">傳送聯絡資料</item>
+ <item quantity="one">傳送聯絡資料</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">傳送附件</item>
+ <item quantity="one">傳送附件</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g>個附件正等待傳送</item>
+ <item quantity="one">一個附件正等待傳送</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"傳送意見"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"前往 Google Play 商店查看"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"版本資料"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"%1$s 版"</string>
+ <string name="menu_license" msgid="1157954180701135202">"開放原始碼授權"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"通知"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"已達到附件限制"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"未能成功載入附件。"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"新增至聯絡人?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"新增聯絡人"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"主旨"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"主旨: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g>:<xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"正在載入聯絡人卡片"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"無法載入聯絡人資訊卡"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"查看聯絡人卡片"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 個聯絡人</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 個聯絡人</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"聯絡人資訊卡"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"生日"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"附註"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"轉寄郵件"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"回覆"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"短訊功能已停用"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"將「短訊」設定為預設短訊應用程式,即可發送訊息"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"將「短訊」設定為預設短訊應用程式"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"變更"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"將「短訊」設定為預設短訊應用程式,即可接收訊息"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"尚未選取傳送短訊的首選 SIM"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"裝置擁有者不允許使用該應用程式。"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"確定"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"參與對話的人過多"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">無效的聯絡人</item>
+ <item quantity="one">無效的聯絡人</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"無法載入相機圖片"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"您: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"草稿"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"只要有新對話,便會在此列出"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"封存的對話顯示在這裏"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"正在載入對話…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"圖片"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"錄音片段"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"影片"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"聯絡人卡"</string>
+ <string name="mms_text" msgid="1528791558806015806">"MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"復原"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"重試"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"如要傳送新訊息,請輸入聯絡人姓名或電話號碼"</string>
+ <string name="action_block" msgid="9032076625645190136">"封鎖"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"封鎖<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"解除封鎖<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"要封鎖 <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"您將繼續接收這個電話號碼傳來的訊息,但不會再收到通知。這個對話將被封存。"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"已封鎖的聯絡人"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"解除封鎖"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"已封鎖的聯絡人"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"從文件庫選擇圖片"</string>
+ <string name="sending_message" msgid="6363584950085384929">"正在傳送訊息"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"已傳送訊息"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"流動數據已關閉,請檢查您的設定。"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"無法在飛行模式下傳送訊息"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"無法傳送訊息"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"已下載訊息"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"流動數據已關閉,請檢查您的設定。"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"在飛行模式下無法下載訊息"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"無法下載訊息"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"0"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"1"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"2"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"3"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"4"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"5"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"6"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"7"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"8"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"9"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"無法經<xliff:g id="CARRIERNAME">%1$s</xliff:g>傳送訊息,錯誤:<xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"無法經不明流動網絡供應商傳送訊息,錯誤:<xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"轉寄:<xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"無法傳送訊息:未在網絡上啟用服務"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"無法傳送訊息:目的地位址無效"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"無法傳送訊息:訊息無效"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"無法傳送訊息:不支援的內容"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"無法傳送訊息:不支援的訊息"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"無法傳送訊息:太大"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"新訊息"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"檢視"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"圖片"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"找不到合適的應用程式"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"移除收件者"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"新訊息"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"取消"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"編輯接入點"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"未設定"</string>
+ <string name="apn_name" msgid="1572691851070894985">"名稱"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"MMS 代理"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"MMS 通訊埠"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN 類型"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"刪除 APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"新增 APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"儲存"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"捨棄"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"[名稱] 欄位不可留空。"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN 不可留空。"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC 欄位必須為 3 位數。"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC 欄位必須要有 2 或 3 位數。"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"正在還原預設 APN 設定。"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"重設回預設值"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"重設預設 APN 設定已完成。"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"未命名"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"接入點名稱"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"新增 APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"這位使用者無法修改存取點名稱設定"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"要複製到剪貼簿嗎?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"複製"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"至 <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"一般"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"進階"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"一般設定"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"進階設定"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"「<xliff:g id="SIM_NAME">%s</xliff:g>」SIM 卡"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"向所有收件者逐一傳送短訊。只有您會收到回覆"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"向所有收件者傳送一封 MMS"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"電話號碼不明"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"新訊息"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"新訊息。"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM 選取器"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"已選擇<xliff:g id="SIM_0">%1$s</xliff:g>,SIM 選取器"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"編輯主旨"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"選取 SIM 或編輯主旨"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"輕觸並按住即可錄製音效檔案"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"開始新的對話"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"短訊"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"「短訊」清單"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"短訊"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"新訊息"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"對話群組清單"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"正在載入對話"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"正在載入訊息"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"查看更多對話"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"查看更多訊息"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"對話群組已刪除"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"已刪除對話,輕觸即可查看其他「短訊」對話"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"已封鎖"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"已解除封鎖"</string>
+ <string name="db_full" msgid="8459265782521418031">"儲存空間不足。部分資料可能會遺失。"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"選取附件"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"確認選取"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"已選取 <xliff:g id="COUNT">%d</xliff:g> 個附件"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"請移除一個或多個附件,然後再試一次。"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"您可以嘗試傳送訊息,但可能要移除一個或多個附件才可送達。"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"每封郵件只可傳送一部影片。請移除額外的影片,然後再試一次。"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"「短訊」無法載入附件。"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"直接傳送"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"無法開始對話"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"已選取 <xliff:g id="SELECTED_SIM">%s</xliff:g>"</string>
+</resources>
diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml
new file mode 100644
index 0000000..3226b70
--- /dev/null
+++ b/res/values-zh-rTW/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"無主旨"</item>
+ <item msgid="272485471009191934">"無主旨"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"是"</item>
+ <item msgid="6049132459802288033">"否"</item>
+ <item msgid="3084376867445867895">"確定"</item>
+ <item msgid="3155097332660174689">"呵呵"</item>
+ <item msgid="2611328818571146775">"謝謝"</item>
+ <item msgid="4881335087096496747">"我同意"</item>
+ <item msgid="2422296858597420738">"很好"</item>
+ <item msgid="4805581752819452687">"我正在路上"</item>
+ <item msgid="4746700499431366214">"好,我稍後再回覆您"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..957d943
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"簡訊"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"簡訊"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"選取對話"</string>
+ <string name="action_settings" msgid="1329008122345201684">"設定"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"傳送訊息"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"新增附件"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"説明"</string>
+ <string name="welcome" msgid="2857560951820802321">"歡迎使用"</string>
+ <string name="skip" msgid="7238879696319945853">"略過"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"繼續 &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"繼續"</string>
+ <string name="exit" msgid="1905187380359981199">"結束"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"設定 &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"設定"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"「簡訊」需要取得簡訊、電話和聯絡人應用程式的存取權限。"</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"如要變更權限,請依序前往 [設定] &gt; [應用程式] &gt; [簡訊] &gt; [權限]。"</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"如要變更權限,請依序前往 [設定]、[應用程式]、[簡訊] 及 [權限]。"</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"常用聯絡人"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"所有聯絡人"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"傳送至 <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"拍照或錄影"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"選擇這個裝置上的圖片"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"錄音"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"選擇相片"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"已選取媒體。"</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"已取消選取媒體。"</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"已選取 <xliff:g id="COUNT">%d</xliff:g> 個項目"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"圖片 (<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" msgid="8142175029053370662">"圖片"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"錄音"</string>
+ <string name="action_share" msgid="2143483844803153871">"分享"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"剛剛"</string>
+ <string name="posted_now" msgid="867560789350406701">"最新"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 分鐘</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 分鐘</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 小時</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 小時</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 天</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 天</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 週</item>
+ <item quantity="one">1 週</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 個月</item>
+ <item quantity="one">1 個月</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> 年</item>
+ <item quantity="one">1 年</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"等級 0 訊息"</string>
+ <string name="save" msgid="5081141452059463572">"儲存"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"裝置儲存空間不足,「簡訊」會自動刪除較舊簡訊以釋出空間。"</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"儲存空間不足"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"在裝置可用空間增加前,「簡訊」可能無法收發訊息。"</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"簡訊儲存空間即將用完,建議您刪除部分訊息。"</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"驗證您的手機號碼"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"這個一次性步驟可確保「簡訊」正確傳送群組訊息。"</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"手機號碼"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"刪除所有內含媒體的訊息"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"自動刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息"</string>
+ <string name="ignore" msgid="7063392681130898793">"忽略"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"刪除所有內含媒體的訊息?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"刪除超過 <xliff:g id="DURATION">%s</xliff:g>的訊息並開啟自動刪除設定?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g>說了"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"您說了"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"來自<xliff:g id="SENDER">%s</xliff:g>的訊息"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"您傳送了訊息"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"傳送中…"</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"傳送失敗,輕觸即可再試一次。"</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"傳送失敗,正在嘗試重新傳送…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"重新傳送或刪除"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"目前無法傳送您的簡訊,請撥打語音電話聯絡緊急服務。"</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"失敗"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"可下載新的多媒體訊息"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"新的多媒體訊息"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"無法下載"</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"輕觸即可再試一次"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"輕觸即可下載"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"下載或刪除"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"下載中…"</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"訊息已過期或無法存取"</string>
+ <string name="mms_info" msgid="3402311750134118165">"大小:<xliff:g id="MESSAGESIZE">%1$s</xliff:g>,期限:<xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"收件者無效,因此無法傳送訊息。"</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"尚未透過網路啟用服務"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"網路發生問題,因此無法傳送"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"訊息已過期或無法存取"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(無主旨)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"寄件者不明"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"已傳送"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"無法下載來自 <xliff:g id="FROM">%2$s</xliff:g> 的這則訊息:<xliff:g id="SUBJECT">%1$s</xliff:g>。"</string>
+ <string name="low_memory" msgid="5300743415198486619">"記憶體不足,因此無法完成資料庫作業"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"訊息未傳送"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"「簡訊」未傳送部分訊息"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> 個對話中的 <xliff:g id="MESSAGES_1">%d</xliff:g> 則訊息</item>
+ <item quantity="one">1 個對話中的 <xliff:g id="MESSAGES_0">%d</xliff:g> 則訊息</item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"訊息未順利下載"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"「簡訊」未下載部分訊息"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="other"><xliff:g id="CONVERSATIONS">%d</xliff:g> 個對話中的 <xliff:g id="MESSAGES_1">%d</xliff:g> 則訊息</item>
+ <item quantity="one">1 個對話中的 <xliff:g id="MESSAGES_0">%d</xliff:g> 則訊息</item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"無法傳送訊息給 <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"目前無法將您的簡訊傳送至 <xliff:g id="NUMBER">%1$s</xliff:g>,請撥打語音電話聯絡緊急服務。"</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> 則新訊息</item>
+ <item quantity="one">新訊息</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"發起"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"無法使用相機"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"無法使用相機"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"無法錄影"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"無法儲存媒體"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"無法拍照"</string>
+ <string name="back" msgid="1477626055115561645">"返回"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"已封存"</string>
+ <string name="action_delete" msgid="4076795795307486019">"刪除"</string>
+ <string name="action_archive" msgid="5437034800324083170">"封存"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"取消封存"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"關閉通知"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"開啟通知"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"新增聯絡人"</string>
+ <string name="action_download" msgid="7786338136368564146">"下載"</string>
+ <string name="action_send" msgid="377635240181672039">"傳送"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"刪除"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"刪除這則訊息?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"這項操作無法復原。"</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"刪除"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="other">要刪除這些對話嗎?</item>
+ <item quantity="one">要刪除這個對話嗎?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"刪除"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"取消"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"收件者"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"選取多張圖片"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"確認選取"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"超過 <xliff:g id="COUNT">%d</xliff:g> 個項目"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"無法錄製音訊,請再試一次。"</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"無法播放音訊,請再試一次。"</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"無法儲存音訊,請再試一次。"</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"輕觸並按住"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">"、 "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"圖片"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"音訊剪輯"</string>
+ <string name="notification_video" msgid="4331423498662606204">"影片"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"聯絡人卡片"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"下載"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"透過簡訊回覆"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"透過多媒體訊息回覆"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"回覆"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 位參與者</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 位參與者</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"我"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"已封鎖並封存聯絡人"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"已解除封鎖及解除封存聯絡人"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"已封存 <xliff:g id="COUNT">%d</xliff:g> 個"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"已解除封存 <xliff:g id="COUNT">%d</xliff:g> 個"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"已關閉通知功能"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"已開啟通知功能"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"設定程序已完成,請再次輕觸 [傳送]。"</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"已成功將「簡訊」設為預設的簡訊應用程式。"</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="other">捨棄附件</item>
+ <item quantity="one">捨棄附件</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"音訊附件"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"播放音訊附件"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"暫停"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"收到<xliff:g id="SENDER">%s</xliff:g>傳送的訊息,內容如下:<xliff:g id="MESSAGE">%s</xliff:g>。"</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"<xliff:g id="SENDER">%s</xliff:g>嘗試傳送訊息但未成功:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"<xliff:g id="SENDER">%s</xliff:g>傳送了訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"給<xliff:g id="CONTACT">%s</xliff:g>的訊息尚未送出:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"正在傳送訊息給<xliff:g id="CONTACT">%s</xliff:g>:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"無法傳送訊息給<xliff:g id="CONTACT">%s</xliff:g>:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"已傳送訊息給<xliff:g id="CONTACT">%s</xliff:g>:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"<xliff:g id="SENDER">%s</xliff:g>嘗試傳送訊息但未成功:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。<xliff:g id="GROUPINFO">%s</xliff:g>。"</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"<xliff:g id="SENDER">%s</xliff:g>傳送了訊息:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。<xliff:g id="GROUPINFO">%s</xliff:g>。"</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"給<xliff:g id="GROUP">%s</xliff:g>的訊息尚未送出:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"正在傳送訊息給<xliff:g id="GROUP">%s</xliff:g>:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"無法傳送訊息給<xliff:g id="GROUP">%s</xliff:g>:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"已傳送訊息給<xliff:g id="GROUP">%s</xliff:g>:<xliff:g id="MESSAGE">%s</xliff:g>。時間:<xliff:g id="TIME">%s</xliff:g>。"</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"訊息傳送失敗,輕觸即可重試。"</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"與<xliff:g id="PARTICIPANTS">%s</xliff:g>的對話"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"刪除主旨"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"錄影"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"拍攝靜態影像"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"拍照"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"開始錄影"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"切換至全螢幕相機"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"切換使用前置與後置鏡頭"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"停止錄影並附加影片"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"停止錄影"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"簡訊相片"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 張相片已儲存到「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」相簿</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 張相片已儲存到「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」相簿</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 段影片已儲存到「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」相簿</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 段影片已儲存到「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」相簿</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> 份附件已儲存到「<xliff:g id="ALBUMNAME_3">%s</xliff:g>」相簿</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 份附件已儲存到「<xliff:g id="ALBUMNAME_1">%s</xliff:g>」相簿</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> 份附件已儲存到「下載內容」</item>
+ <item quantity="one"><xliff:g id="QUANTITY_0">%d</xliff:g> 份附件已儲存到「下載內容」</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="other">已儲存 <xliff:g id="QUANTITY_1">%d</xliff:g> 個附件</item>
+ <item quantity="one">已儲存 <xliff:g id="QUANTITY_0">%d</xliff:g> 個附件</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="other">無法儲存 <xliff:g id="QUANTITY_1">%d</xliff:g> 個附件</item>
+ <item quantity="one">無法儲存 <xliff:g id="QUANTITY_0">%d</xliff:g> 個附件</item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"已儲存的多媒體訊息附件"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"設定"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"已封存"</string>
+ <string name="action_close" msgid="1840519376200478419">"關閉"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"多媒體訊息"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"進階"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"偵錯"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"通知"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"音效"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"靜音"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"震動"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"已封鎖"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"簡訊傳送報告"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"傳送簡訊後要求傳送回條"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"自動擷取"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"自動擷取多媒體訊息"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"漫遊時自動擷取"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"漫遊時自動擷取多媒體訊息"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"群組簡訊"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"有多位收件者時,也使用多媒體訊息傳送訊息"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"預設簡訊應用程式"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"預設簡訊應用程式"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"您的電話號碼"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"不明"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"訊息傳送音效"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"傾印簡訊"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"將收到的簡訊原始資料傾印到外部儲存空間檔案"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"傾印多媒體訊息"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"將收到的多媒體訊息原始資料傾印到外部儲存空間檔案"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"無線災害示警"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"訊息選項"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"複製文字"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"查看詳細資料"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"刪除"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"轉寄"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"訊息詳細資料"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"類型: "</string>
+ <string name="text_message" msgid="7415419755252205721">"簡訊"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"多媒體訊息"</string>
+ <string name="from_label" msgid="1947831848146564875">"寄件者: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"收件者: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"傳送時間: "</string>
+ <string name="received_label" msgid="4442494712757995203">"接收時間: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"主旨: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"大小: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"優先順序: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM 卡: "</string>
+ <string name="priority_high" msgid="728836357310908368">"高"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"一般"</string>
+ <string name="priority_low" msgid="7398724779026801851">"低"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM 卡 <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"隱藏的寄件者地址"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"載入附件時無法傳送訊息。"</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"無法載入附件,請再試一次。"</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"網路尚未就緒,請再試一次。"</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"刪除文字"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"切換輸入的文字和數字"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"新增更多參與者"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"確認參與者"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"發起新對話"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"選取此項目"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"播放影片"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"參與者和選項"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"偵錯"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"參與者和選項"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"一般"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"這個對話的參與者"</string>
+ <string name="action_call" msgid="6596167921517350362">"進行通話"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"傳送簡訊"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"&lt;small&gt;透過 <xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;&lt;br&gt; 傳送訊息"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="other">傳送相片</item>
+ <item quantity="one">傳送相片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="other">傳送音訊</item>
+ <item quantity="one">傳送音訊</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="other">傳送影片</item>
+ <item quantity="one">傳送影片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="other">傳送聯絡人卡片</item>
+ <item quantity="one">傳送聯絡人卡片</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="other">傳送附件</item>
+ <item quantity="one">傳送附件</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="other">有 <xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> 個附件可隨郵件傳送</item>
+ <item quantity="one">有 1 個附件可隨郵件傳送</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"提供意見"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"前往 Google Play 商店查看"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"版本資訊"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"版本 %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"開放原始碼授權"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"通知"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"已達附件數量上限"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"無法載入附件。"</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"加到聯絡人?"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"新增聯絡人"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"主旨"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"主旨: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g>:<xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"正在載入聯絡人卡片"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"無法載入聯絡人卡片"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"查看聯絡人卡片"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> 位聯絡人</item>
+ <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> 位聯絡人</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"聯絡人卡片"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"生日"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"附註"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"轉寄郵件"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"回覆"</string>
+ <string name="plus_one" msgid="9010288417554932581">"還有 1 位參與者"</string>
+ <string name="plus_n" msgid="8961547034116059566">"還有 %d 位參與者"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"簡訊功能已停用"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"如要傳送訊息,請將「簡訊」設為預設的簡訊應用程式"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"將「簡訊」設為預設的簡訊應用程式"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"變更"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"如要接收訊息,請將「簡訊」設為預設的簡訊應用程式"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"尚未選取傳送簡訊時慣用的 SIM 卡"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"裝置擁有者不允許這個應用程式的要求。"</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"確定"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"對話參與者人數過多"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="other">聯絡人無效</item>
+ <item quantity="one">聯絡人無效</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"無法載入相機圖片"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"您: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"草稿"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"這裡會列出您發起的新對話"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"封存的對話會顯示在這裡"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"正在載入對話…"</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"圖片"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"音訊剪輯"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"影片"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"聯絡人卡片"</string>
+ <string name="mms_text" msgid="1528791558806015806">"多媒體訊息"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"復原"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"重試"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"輸入聯絡人名稱或電話號碼,即可開始傳送新訊息"</string>
+ <string name="action_block" msgid="9032076625645190136">"封鎖"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"封鎖 <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"解除封鎖 <xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"封鎖 <xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"您將持續接收來自這個號碼的訊息,但不會再收到通知。此對話將由系統封存。"</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"封鎖的聯絡人"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"解除封鎖"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"封鎖的聯絡人"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"選擇文件庫中的圖片"</string>
+ <string name="sending_message" msgid="6363584950085384929">"正在傳送訊息"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"訊息成功送出"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"行動數據已關閉。請檢查您的設定。"</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"飛航模式下無法傳送訊息"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"無法傳送訊息"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"訊息下載完成"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"行動數據已關閉。請檢查您的設定。"</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"在飛航模式下無法下載訊息"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"無法下載訊息"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"0"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"1"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"2"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"3"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"4"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"5"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"6"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"7"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"8"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"9"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"無法透過<xliff:g id="CARRIERNAME">%1$s</xliff:g>傳送訊息 (錯誤代碼:<xliff:g id="ERRORCODE">%2$d</xliff:g>)"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"無法透過不明行動通訊業者傳送訊息 (錯誤代碼:<xliff:g id="ERRORCODE">%1$d</xliff:g>)"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"轉寄:<xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"訊息未傳送:未在網路上啟用服務"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"無法傳送訊息:目的地位址無效"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"無法傳送訊息:訊息無效"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"無法傳送訊息:不支援的內容"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"無法傳送訊息:不支援的訊息"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"訊息未傳送:內容過大"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"新訊息"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"查看"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"圖片"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"找不到適用的應用程式"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"移除收件者"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"新訊息"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"取消"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"編輯存取點"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"未設定"</string>
+ <string name="apn_name" msgid="1572691851070894985">"名稱"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"多媒體訊息 Proxy"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"多媒體訊息通訊埠"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"APN 類型"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"刪除 APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"新增 APN"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"儲存"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"捨棄"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"名稱欄位不可留空。"</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"APN 不可留空。"</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"MCC 欄位的值必須為 3 碼。"</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"MNC 欄位的值必須為 2 或 3 碼。"</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"正在還原預設 APN 設定。"</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"重設為預設值"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"預設 APN 設定的重設程序已完成。"</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"未命名"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"存取點名稱"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"新增 APN"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"這位使用者無法修改存取點名稱設定"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"複製到剪貼簿?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"複製"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"傳送至 <xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"一般"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"進階"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"一般設定"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"進階設定"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"「<xliff:g id="SIM_NAME">%s</xliff:g>」SIM 卡"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"分別傳送簡訊給所有收件者 (如有任何回覆,只有您會收到)"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"傳送一則多媒體訊息給所有收件者"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"不明號碼"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"新訊息"</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"新訊息。"</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"SIM 卡選取器"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"已選取「<xliff:g id="SIM_0">%1$s</xliff:g>」,SIM 卡選取器"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"編輯主旨"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"選取 SIM 卡或編輯主旨"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"輕觸並按住即可錄製音訊"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"發起新對話"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"簡訊"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"訊息清單"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"簡訊"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"新訊息"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"會話群組清單"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"正在載入對話"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"正在載入訊息"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"查看更多對話"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"查看更多訊息"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"對話已刪除"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"已刪除對話。輕觸即可顯示其他「簡訊」對話"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"已封鎖"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"已解除封鎖"</string>
+ <string name="db_full" msgid="8459265782521418031">"儲存空間即將用盡,部分資料可能會遺失。"</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"選取附件"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"確認選取項目"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"選取了 <xliff:g id="COUNT">%d</xliff:g> 個附件"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"請移除一或多個附件,然後再試一次。"</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"您可以嘗試傳送訊息,但可能必須移除一或多個附件才能順利傳送。"</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"每則訊息只能傳送一部影片。請移除額外的影片,然後再試一次。"</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"「簡訊」無法載入附件。"</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"確定傳送"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"無法發起對話"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"已選取「<xliff:g id="SELECTED_SIM">%s</xliff:g>」"</string>
+</resources>
diff --git a/res/values-zu/arrays.xml b/res/values-zu/arrays.xml
new file mode 100644
index 0000000..50eafe1
--- /dev/null
+++ b/res/values-zu/arrays.xml
@@ -0,0 +1,37 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="empty_subject_strings">
+ <item msgid="8900116186849071724">"asikho isihloko"</item>
+ <item msgid="272485471009191934">"asikho isihloko"</item>
+ </string-array>
+ <string-array name="notification_reply_choices">
+ <item msgid="1875991048685328264">"Yebo"</item>
+ <item msgid="6049132459802288033">"Cha"</item>
+ <item msgid="3084376867445867895">"KULUNGILE"</item>
+ <item msgid="3155097332660174689">"Hehe"</item>
+ <item msgid="2611328818571146775">"Siyabonga"</item>
+ <item msgid="4881335087096496747">"Ngiyavuma"</item>
+ <item msgid="2422296858597420738">"I-Nice"</item>
+ <item msgid="4805581752819452687">"Ngisendleleni"</item>
+ <item msgid="4746700499431366214">"KULUNGILE, mangibuye kuwe emuva kwesikhathi"</item>
+ <item msgid="4342041517788201970">":)"</item>
+ <item msgid="7439437911747961187">":("</item>
+ </string-array>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..02e21dd
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,519 @@
+<?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:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="3062916848713424329">"Imilayezo"</string>
+ <string name="share_intent_label" msgid="6920483598866144717">"Imilayezo"</string>
+ <string name="share_intent_activity_label" msgid="2939454246983196756">"Khetha ingxoxo"</string>
+ <string name="action_settings" msgid="1329008122345201684">"Izilungiselelo"</string>
+ <string name="sendButtonContentDescription" msgid="8422114979691093956">"Thumela umlayezo"</string>
+ <string name="attachMediaButtonContentDescription" msgid="2758624209216276682">"Engeza okunamathiselwe kwi-imeyili"</string>
+ <string name="help_and_feedback_activity_label" msgid="8070710971014835182">"Usizo"</string>
+ <string name="welcome" msgid="2857560951820802321">"Siyakwamukela"</string>
+ <string name="skip" msgid="7238879696319945853">"Yeqa"</string>
+ <string name="next_with_arrow" msgid="2840729397885031655">"Okulandelayo &gt;"</string>
+ <string name="next" msgid="4496962051601713843">"Okulandelayo"</string>
+ <string name="exit" msgid="1905187380359981199">"Phuma"</string>
+ <string name="settings_with_arrow" msgid="8534633224579301342">"Izilungiselelo &gt;"</string>
+ <string name="settings" msgid="1045695083932447399">"Izilungiselelo"</string>
+ <string name="required_permissions_promo" msgid="3898014894175251590">"Ukulayeza kudinga imvume ku-SMS, ifoni noxhumana nabo."</string>
+ <string name="enable_permission_procedure" msgid="2778471831376319587">"Ungaguqula izimvume ku-Izilungiselelo &gt; Izinhlelo zokusebenza &gt; Isilayezi &gt; Izimvume."</string>
+ <string name="enable_permission_procedure_description" msgid="8672121020793945630">"Ungaguqula izimvume ku-Izilungiselelo, Izinhlelo zokusebenza, Isilayezi, Izimvume."</string>
+ <string name="contact_picker_frequents_tab_title" msgid="7157326165824706883">"Imvamisa"</string>
+ <string name="contact_picker_all_contacts_tab_title" msgid="1424746082040243161">"Bonke oxhumana nabo"</string>
+ <string name="contact_list_send_to_text" msgid="3167455944684758290">"Thumela ku-<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="mediapicker_cameraChooserDescription" msgid="8498255650058981250">"Thwebula izithombe noma ividiyo"</string>
+ <string name="mediapicker_galleryChooserDescription" msgid="1227741581156455777">"Khetha izithombe kusuka kule divayisi"</string>
+ <string name="mediapicker_audioChooserDescription" msgid="3660616501040372452">"Rekhoda umsindo"</string>
+ <string name="mediapicker_gallery_title" msgid="3169528536727885763">"Khetha isithombe"</string>
+ <string name="mediapicker_gallery_item_selected_content_description" msgid="2569545441879514283">"Imidiya ikhethiwe."</string>
+ <string name="mediapicker_gallery_item_unselected_content_description" msgid="40021674722525910">"Imidiya isuswe ukukhetha."</string>
+ <string name="mediapicker_gallery_title_selection" msgid="3621616141966436510">"<xliff:g id="COUNT">%d</xliff:g> okukhethiwe"</string>
+ <string name="mediapicker_gallery_image_item_description" msgid="8812237405495409901">"isithombe <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" msgid="8142175029053370662">"Isithombe"</string>
+ <string name="mediapicker_audio_title" msgid="5455016591560739789">"Rekhoda umsindo"</string>
+ <string name="action_share" msgid="2143483844803153871">"Yaba"</string>
+ <string name="posted_just_now" msgid="6632467048088811568">"Khona manje"</string>
+ <string name="posted_now" msgid="867560789350406701">"Manje"</string>
+ <plurals name="num_minutes_ago" formatted="false" msgid="4085627474543076735">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> amaminithi</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> amaminithi</item>
+ </plurals>
+ <plurals name="num_hours_ago" formatted="false" msgid="8010868301590914325">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> amahora</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> amahora</item>
+ </plurals>
+ <plurals name="num_days_ago" formatted="false" msgid="8753803762044567843">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> izinsuku</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> izinsuku</item>
+ </plurals>
+ <plurals name="week_count" formatted="false" msgid="1301973226125551953">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> amaviki</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> amaviki</item>
+ </plurals>
+ <plurals name="month_count" formatted="false" msgid="4858515363324110232">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g>izinyanga</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g>izinyanga</item>
+ </plurals>
+ <plurals name="year_count" formatted="false" msgid="4522546496183798317">
+ <item quantity="one"><xliff:g id="COUNT">%d</xliff:g> iminyaka</item>
+ <item quantity="other"><xliff:g id="COUNT">%d</xliff:g> iminyaka</item>
+ </plurals>
+ <string name="class_0_message_activity" msgid="4603850264073169854">"Umlayezo wesigaba esingu-0"</string>
+ <string name="save" msgid="5081141452059463572">"Londoloza"</string>
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text" msgid="8836401937872068406">"Idivayisi manje inesikhala esincane. Imilayezo izosusa ngokuzenzakalelayo imilayezo emidala ukukhulula isikhala."</string>
+ <string name="sms_storage_low_title" msgid="7985379565293259177">"Isikhala sesitoreji siyaphela"</string>
+ <string name="sms_storage_low_text" msgid="7036247475855447830">"Ukulayeza kungenzeka kungathumeli noma kungamukeli imilayezo kuze kube ukuthi isikhala esiningi sitholakala kudivayisi yakho."</string>
+ <string name="sms_storage_low_notification_ticker" msgid="2393739822170029830">"Isitoreji se-SMS esiphansi. Kungahle kudingeke ukuba ususe imilayezo."</string>
+ <string name="enter_phone_number_title" msgid="7263355879949248448">"Qinisekisa inombolo yakho yefoni"</string>
+ <string name="enter_phone_number_text" msgid="4539767473801195966">"Lesi sinyathelo sesikhathi esisodwa sizoqinisekisa ukuthi Ukulayeza kuzothumela imilayezo yakho yeqembu ngokufanelekile."</string>
+ <string name="enter_phone_number_hint" msgid="6242965213882272822">"Inombolo yefoni"</string>
+ <string name="delete_all_media" msgid="5549693176734564386">"Susa yonke imilayezo ngemidiya"</string>
+ <string name="delete_oldest_messages" msgid="9204277306804390707">"Susa imilayezo emidala kuno-<xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="auto_delete_oldest_messages" msgid="7766679943833404968">"Susa ngokuzenzakalelayo imilayezo emidala kuno-<xliff:g id="DURATION">%s</xliff:g>"</string>
+ <string name="ignore" msgid="7063392681130898793">"Ziba"</string>
+ <string name="delete_all_media_confirmation" msgid="4601960420953525559">"Susa yonke imilayezo ngemidiya?"</string>
+ <string name="delete_oldest_messages_confirmation" msgid="6172297718425265784">"Sula imilayezo emidala kuno-<xliff:g id="DURATION">%s</xliff:g>?"</string>
+ <string name="auto_delete_oldest_messages_confirmation" msgid="9071499976729145269">"Susa imilayezo emidala kuno-<xliff:g id="DURATION">%s</xliff:g> bese uvule ukususa ngokuzenzakalelayo?"</string>
+ <string name="incoming_text_sender_content_description" msgid="2481078288502342745">"<xliff:g id="SENDER">%s</xliff:g> uthe"</string>
+ <string name="outgoing_text_sender_content_description" msgid="3492116976256510272">"Wena uthe"</string>
+ <string name="incoming_sender_content_description" msgid="7512757244515303701">"Umlayezo ovela ku-<xliff:g id="SENDER">%s</xliff:g>"</string>
+ <string name="outgoing_sender_content_description" msgid="5983163141994434778">"Uthumele umlayezo"</string>
+ <string name="message_status_sending" msgid="3306260780518886107">"Iyathumela..."</string>
+ <string name="message_status_send_failed" msgid="5443627648028194631">"Akuthunyelwe. Thinta ukuze uzame futhi."</string>
+ <string name="message_status_send_retrying" msgid="650840088043267981">"Akuthunyelwe. Iyazama futhi…"</string>
+ <string name="message_status_resend" msgid="7718458416521355708">"Thumela kabusha noma susa"</string>
+ <string name="message_status_send_failed_emergency_number" msgid="1738854184297159948">"Sicela wenze ikholi yezwi kumasevisi wesimo esiphuthumayo. Umlayezo wakho wombhalo awukwazanga ukulethwa ngalesi sikhathi."</string>
+ <string name="message_status_failed" msgid="8009695010381848311">"Yehlulekile"</string>
+ <string name="message_title_manual_download" msgid="2761223322277801061">"Umlayezo we-MMS omusha ongalandwa"</string>
+ <string name="message_title_downloading" msgid="6418481644146460124">"Umlayezo we-MMS omusha"</string>
+ <string name="message_title_download_failed" msgid="3867223594671090459">"Ayikwazanga ukulanda."</string>
+ <string name="message_status_download_failed" msgid="7298035414448355842">"Thinta ukuze uzame futhi"</string>
+ <string name="message_status_download" msgid="6527275493954932656">"Thinta ukuze ulande"</string>
+ <string name="message_status_download_action" msgid="285219832844160">"Landa noma susa"</string>
+ <string name="message_status_downloading" msgid="7159807805159805428">"Iyalanda..."</string>
+ <string name="message_status_download_error" msgid="6957738686287397519">"Umlayezo uphelelwe yisikhathi noma awutholakali"</string>
+ <string name="mms_info" msgid="3402311750134118165">"usayizi: <xliff:g id="MESSAGESIZE">%1$s</xliff:g>, ukuphela kwesikhathi: <xliff:g id="MESSAGEEXPIRE">%2$s</xliff:g>"</string>
+ <string name="invalid_destination" msgid="8214889988397524630">"Ayikwazi ukuthumela. Umamukeli awuvumelekile."</string>
+ <string name="service_not_activated" msgid="5512558652052977817">"Isevisi ayisebenzi kunethiwekhi"</string>
+ <string name="service_network_problem" msgid="3765738916704013318">"Ayikwazanga ukuthumela ngenxa yenkinga yenethiwekhi"</string>
+ <string name="service_message_not_found" msgid="8350935323904134751">"Umlayezo uphelelwe yisikhathi noma awutholakali"</string>
+ <string name="no_subject" msgid="5587715902648568767">"(Asikho isihloko)"</string>
+ <string name="unknown_sender" msgid="504272434917395677">"Umthumeli ongaziwa"</string>
+ <string name="delivered_status_content_description" msgid="3433965196058436991">"Kuthunyelwe"</string>
+ <string name="dl_failure_notification" msgid="8744588243554006189">"Ayikwazanga ukulanda umlayezo we-<xliff:g id="SUBJECT">%1$s</xliff:g> kusuka ku-<xliff:g id="FROM">%2$s</xliff:g>."</string>
+ <string name="low_memory" msgid="5300743415198486619">"Ayikwazanga ukuqedela umsebenzi wemininingo egciniwe ngenxa yememori ephansi"</string>
+ <string name="notification_send_failures_line1_singular" msgid="6783835440207655217">"Umlayezo awuthunyelwanga"</string>
+ <string name="notification_send_failures_line1_plural" msgid="7228944748028969272">"Eminye imilayezo ayithunyelwe Ekulayezeni"</string>
+ <plurals name="notification_send_failures" formatted="false" msgid="6938919932879650691">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> imilayezo kwizingxoxo ezingu-<xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> imilayezo kwizingxoxo ezingu-<xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_download_failures_line1_singular" msgid="4424772073720626885">"Umlayezo awuzange ulandwe"</string>
+ <string name="notification_download_failures_line1_plural" msgid="1697982467160426345">"Eminye imilayezo ayilandiwe Ekulayezeni"</string>
+ <plurals name="notification_download_failures" formatted="false" msgid="5341490525457911398">
+ <item quantity="one"><xliff:g id="MESSAGES_1">%d</xliff:g> imilayezo kwizingxoxo ezingu-<xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ <item quantity="other"><xliff:g id="MESSAGES_1">%d</xliff:g> imilayezo kwizingxoxo ezingu-<xliff:g id="CONVERSATIONS">%d</xliff:g></item>
+ </plurals>
+ <string name="notification_emergency_send_failure_line1" msgid="6311715277789488996">"Umlayezo oya ku-<xliff:g id="NUMBER">%1$s</xliff:g> awuthunyelwanga"</string>
+ <string name="notification_emergency_send_failure_line2" msgid="6461481033351036996">"Sicela uwenze ikholi yezwi kumasevisi wesimo esiphuthumayo. Umlayezo wakho wobhalo ku-<xliff:g id="NUMBER">%1$s</xliff:g> awukwazanga ukulethwa ngalesi sikhathi."</string>
+ <plurals name="notification_new_messages" formatted="false" msgid="610594317958128842">
+ <item quantity="one"><xliff:g id="MESSAGES">%d</xliff:g> imilayezo emisha</item>
+ <item quantity="other"><xliff:g id="MESSAGES">%d</xliff:g> imilayezo emisha</item>
+ </plurals>
+ <string name="start_conversation" msgid="7984539515326392793">"Qala"</string>
+ <string name="camera_error_opening" msgid="2739332294400420426">"Ikhamera ayitholakali"</string>
+ <string name="camera_error_unknown" msgid="4283956364220459608">"Ikhamera ayitholakali"</string>
+ <string name="camera_error_video_init_fail" msgid="994632067015578879">"Ukuthwebula kwevidiyo akutholakali"</string>
+ <string name="camera_error_storage_fail" msgid="5536300789483863989">"Ayikwazi ukulondoloza imidiya"</string>
+ <string name="camera_error_failure_taking_picture" msgid="4894133709734862250">"Ayikwazi ukuthatha isithombe"</string>
+ <string name="back" msgid="1477626055115561645">"Phindela emuva"</string>
+ <string name="action_menu_show_archived" msgid="1628518043533374868">"Okufakwe kungobo yomlando"</string>
+ <string name="action_delete" msgid="4076795795307486019">"Susa"</string>
+ <string name="action_archive" msgid="5437034800324083170">"Faka kungobo yomlando"</string>
+ <string name="action_unarchive" msgid="139681611159869493">"Ungafaki kungobo yomlando"</string>
+ <string name="action_notification_off" msgid="4823658797441716246">"Vala izaziso"</string>
+ <string name="action_notification_on" msgid="8244389452685364211">"Vula isaziso"</string>
+ <string name="action_add_contact" msgid="8248615862739848672">"Engeza oxhumana naye"</string>
+ <string name="action_download" msgid="7786338136368564146">"Landa"</string>
+ <string name="action_send" msgid="377635240181672039">"Thumela"</string>
+ <string name="action_delete_message" msgid="2728883749416365507">"Susa"</string>
+ <string name="delete_message_confirmation_dialog_title" msgid="4723834210275712001">"Susa lo mlayezo?"</string>
+ <string name="delete_message_confirmation_dialog_text" msgid="3952533622691031375">"Lesi senzo asikwazi ukuhlehliswa."</string>
+ <string name="delete_message_confirmation_button" msgid="3888584614972573382">"Susa"</string>
+ <plurals name="delete_conversations_confirmation_dialog_title" formatted="false" msgid="2285567882353326800">
+ <item quantity="one">Susa lezi zingxoxo?</item>
+ <item quantity="other">Susa lezi zingxoxo?</item>
+ </plurals>
+ <string name="delete_conversation_confirmation_button" msgid="744574085809600863">"Susa"</string>
+ <string name="delete_conversation_decline_button" msgid="5470021965641900456">"Khansela"</string>
+ <string name="recipient_hint" msgid="1819710673158265515">"Ku-"</string>
+ <string name="action_multiselect" msgid="1219683118692013380">"Khetha izithombe eziningi"</string>
+ <string name="action_confirm_multiselect" msgid="8283370294120913166">"Qinisekisa ukukhetha"</string>
+ <string name="attachment_more_items" msgid="2861573099241369958">"+<xliff:g id="COUNT">%d</xliff:g>"</string>
+ <string name="audio_recording_start_failed" msgid="4442450764642560851">"Ayikwazi ukurekhoda umsindo. Zama futhi."</string>
+ <string name="audio_recording_replay_failed" msgid="3585760856463273828">"Ayikwazi ukudlala umsindo. Zama futhi."</string>
+ <string name="audio_recording_error" msgid="7762310055059024872">"Ayikwazanga ukulondoloza umsindo. Zama futhi."</string>
+ <string name="audio_picker_hint_text" msgid="6986250080467111491">"Thinta bese ubambe"</string>
+ <string name="enumeration_comma" msgid="7032804261692931808">", "</string>
+ <string name="notification_separator" msgid="3472521786709813414">" "</string>
+ <string name="notification_ticker_separator" msgid="2027929074008933906">": "</string>
+ <string name="notification_space_separator" msgid="9007689386066977972">" "</string>
+ <string name="notification_picture" msgid="1176665337098921822">"Isithombe"</string>
+ <string name="notification_audio" msgid="5926581589398218150">"Isiqeshana somsindo"</string>
+ <string name="notification_video" msgid="4331423498662606204">"Ividiyo"</string>
+ <string name="notification_vcard" msgid="7658061674440552878">"Ikhadi loxhumana naye"</string>
+ <string name="notification_download_mms" msgid="6206807985355359528">"Landa"</string>
+ <string name="notification_reply_via_sms" msgid="6990127304684161722">"Phendula nge-SMS"</string>
+ <string name="notification_reply_via_mms" msgid="6706012871186064035">"Phendula nge-MMS"</string>
+ <string name="notification_reply_prompt" msgid="1836023392294480241">"Phendula"</string>
+ <plurals name="wearable_participant_count" formatted="false" msgid="3037889420270036143">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> ababambiqhaza</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> ababambiqhaza</item>
+ </plurals>
+ <string name="unknown_self_participant" msgid="3186565052350548852">"Mina"</string>
+ <string name="blocked_toast_message" msgid="7903364256385612569">"Oxhumana naye uvinjiwe futhi wafakwa kungobo yomlando"</string>
+ <string name="unblocked_toast_message" msgid="4927617874263586622">"Oxhumana naye uvulelwe futhi wakhishwa kungobo yomlando"</string>
+ <string name="archived_toast_message" msgid="8109348891515322512">"<xliff:g id="COUNT">%d</xliff:g> kufakwe kungobo yomlando"</string>
+ <string name="unarchived_toast_message" msgid="5010777423084203833">"<xliff:g id="COUNT">%d</xliff:g> kukhishwe kungobo yomlando"</string>
+ <string name="notification_off_toast_message" msgid="5203458459139408265">"Izaziso zivaliwe"</string>
+ <string name="notification_on_toast_message" msgid="1240626023754746310">"Izaziso zivuliwe"</string>
+ <string name="toast_after_setting_default_sms_app_for_message_send" msgid="2767997591700713113">"Konke kusethiwe. Thinta u-Thumela futhi."</string>
+ <string name="toast_after_setting_default_sms_app" msgid="2923745971897322476">"Ukulayeza kusethwe ngempumelelo njengohlelo lokusebenza oluzenzakalelayo lwe-SMS."</string>
+ <plurals name="attachment_preview_close_content_description" formatted="false" msgid="5631719319690969981">
+ <item quantity="one">Lahla okunamathiselwe</item>
+ <item quantity="other">Lahla okunamathiselwe</item>
+ </plurals>
+ <string name="audio_attachment_content_description" msgid="499334244765270108">"Ukunamathiselwa komsindo"</string>
+ <string name="audio_play_content_description" msgid="4932509227281251607">"Dala okunamathiselwe komsindo"</string>
+ <string name="audio_pause_content_description" msgid="7578169887065513701">"Misa isikhashana"</string>
+ <string name="incoming_message_announcement" msgid="6369259405539452011">"Umlayezo kusukela ku-<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_failed_message_prefix" msgid="6985644411445605747">"Umlayezo ohlulekile ophuma ku-<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_incoming_successful_message_prefix" msgid="6190814597935995703">"Umlayezo ophuma ku-<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_draft_message_prefix" msgid="8721053088385740646">"Umlayezo ongathunyelwe ku-<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_sending_message_prefix" msgid="3929653530203574220">"Ithumela umlayezo ku-<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_failed_message_prefix" msgid="7194065651879454314">"Umlayezo ohlulekile oya ku-<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="one_on_one_outgoing_successful_message_prefix" msgid="7128492863867327814">"Umlayezo oya ku-<xliff:g id="CONTACT">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_incoming_failed_message_prefix" msgid="7085805237707481779">"Umlayezo ohlulekile kusukela ku-<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_incoming_successful_message_prefix" msgid="7248506967059447054">"Umlayezo ophuma ku-<xliff:g id="SENDER">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>. <xliff:g id="GROUPINFO">%s</xliff:g>."</string>
+ <string name="group_outgoing_draft_message_prefix" msgid="1655013812805454648">"Umlayezo ongathunyelwe ku-<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_sending_message_prefix" msgid="7636874745414695556">"Ithumela umlayezo ku-<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_failed_message_prefix" msgid="5059983340377901592">"Umlayezo ohlulekile oya ku-<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="group_outgoing_successful_message_prefix" msgid="1593702642852987920">"Umlayezo oya ku-<xliff:g id="GROUP">%s</xliff:g>: <xliff:g id="MESSAGE">%s</xliff:g>. Isikhathi: <xliff:g id="TIME">%s</xliff:g>."</string>
+ <string name="failed_message_content_description" msgid="7047161648867054936">"Umlayezo ohlulekile. Thinta ukuze uphinde uzame."</string>
+ <string name="group_conversation_description" msgid="2339270924401184182">"Ingxoxo no-<xliff:g id="PARTICIPANTS">%s</xliff:g>"</string>
+ <string name="delete_subject_content_description" msgid="8910749398836396698">"Sula isihloko"</string>
+ <string name="camera_switch_to_video_mode" msgid="2926868205952641428">"Thwebula ividiyo"</string>
+ <string name="camera_switch_to_still_mode" msgid="4419617715199157958">"Thwebula isithombe esimile"</string>
+ <string name="camera_take_picture" msgid="2573317952200799489">"Thatha isithombe"</string>
+ <string name="camera_start_recording" msgid="1736478301191221786">"Qala ukuqopha ividiyo"</string>
+ <string name="camera_switch_full_screen" msgid="6156039742035097276">"Shintshela kukhamela yesikrini esigcwele"</string>
+ <string name="camera_switch_camera_facing" msgid="1566112129296559159">"Shintsha phakathi kwekhamera yaphambilini neyasemuva"</string>
+ <string name="camera_stop_recording" msgid="5331592576107271152">"Misa ukurekhoda uphinde ushumeke ividiyo"</string>
+ <string name="camera_cancel_recording" msgid="6431544304743145818">"Misa ukurekhoda amavidiyo"</string>
+ <string name="photo_view_activity_title" msgid="6556006393051918135">"Ilayeza izithombe"</string>
+ <plurals name="photos_saved_to_album" formatted="false" msgid="7529478299745446838">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> izithombe zilondolozwe ku-albhamu \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> izithombe zilondolozwe ku-albhamu \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="videos_saved_to_album" formatted="false" msgid="6821121808257576788">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> amavidiyo alondolozwe ku-albhamu \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> amavidiyo alondolozwe ku-albhamu \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album" formatted="false" msgid="8407480699515308929">
+ <item quantity="one"><xliff:g id="QUANTITY_2">%d</xliff:g> okunamathiselwe kulondolozwe ku-albhamu \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_2">%d</xliff:g> okunamathiselwe kulondolozwe ku-albhamu \"<xliff:g id="ALBUMNAME_3">%s</xliff:g>\"</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads" formatted="false" msgid="1886864019411801995">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> Okunamathiselwe kulondolozwe kokuthi \"Okulandiwe\"</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> Okunamathiselwe kulondolozwe kokuthi \"Okulandiwe\"</item>
+ </plurals>
+ <plurals name="attachments_saved" formatted="false" msgid="6925000482183379648">
+ <item quantity="one"><xliff:g id="QUANTITY_1">%d</xliff:g> okunamathiselwe kulondoloziwe</item>
+ <item quantity="other"><xliff:g id="QUANTITY_1">%d</xliff:g> okunamathiselwe kulondoloziwe</item>
+ </plurals>
+ <plurals name="attachment_save_error" formatted="false" msgid="2068837227090109833">
+ <item quantity="one">Ayikwazanga ukulondoloza okunamathiselwe okungu-<xliff:g id="QUANTITY_1">%d</xliff:g></item>
+ <item quantity="other">Ayikwazanga ukulondoloza okunamathiselwe okungu-<xliff:g id="QUANTITY_1">%d</xliff:g></item>
+ </plurals>
+ <string name="attachment_file_description" msgid="3056972820372291694">"Ilondoloze okunamathiselwa kwe-MMS"</string>
+ <string name="settings_activity_title" msgid="3380813834835334258">"Izilungiselelo"</string>
+ <string name="archived_activity_title" msgid="4738212324460451188">"Okufakwe kungobo yomlando"</string>
+ <string name="action_close" msgid="1840519376200478419">"Vala"</string>
+ <string name="mms_messaging_category_pref_title" msgid="4816815152658525660">"I-MMS"</string>
+ <string name="advanced_category_pref_title" msgid="6411454224069259687">"Okuthuthukisiwe"</string>
+ <string name="debug_category_pref_title" msgid="8765138968242505061">"Lungisa iphutha"</string>
+ <string name="notifications_enabled_pref_title" msgid="4127288731844373795">"Izaziso"</string>
+ <string name="notification_sound_pref_title" msgid="3685506528957337849">"Umsindo"</string>
+ <string name="silent_ringtone" msgid="8073534180322059814">"Thulile"</string>
+ <string name="notification_vibrate_pref_title" msgid="6668564570045187390">"Dlidlizela"</string>
+ <string name="blocked_pref_title" msgid="2560554234438548817">"Ivinjelwe"</string>
+ <string name="delivery_reports_pref_title" msgid="5115727259825309087">"Imibiko yokulethwa kwe-SMS"</string>
+ <string name="delivery_reports_pref_summary" msgid="4272502420621500421">"Cela umbiko wokuthumela we-SMS ngayinye oyithumelayo"</string>
+ <string name="auto_retrieve_mms_pref_title" msgid="1316094876978218980">"Thola ngokuzenzakalekayo"</string>
+ <string name="auto_retrieve_mms_pref_summary" msgid="2253902455786205335">"Thola ngokuzenzakalela imilayezo ye-MMS"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_title" msgid="2918775628609759349">"Ukuthola okuzenzakalelayo kokuzula"</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary" msgid="7347177867673486983">"Thola ngokuzenzakalela i-MMS uma uzula"</string>
+ <string name="group_mms_pref_title" msgid="6553612309209383106">"Imilayezo yeqembu"</string>
+ <string name="group_mms_pref_summary" msgid="3758710015912690629">"Sebenzisa i-MMS ukuthumela umlayezo owodwa uma kunabamukeli abaningi"</string>
+ <string name="sms_disabled_pref_title" msgid="6352764741524717132">"Uhlelo lokusebenza oluzenzakalelayo le-SMS"</string>
+ <string name="sms_enabled_pref_title" msgid="2961457081888153323">"Uhlelo lokusebenza oluzenzakalelayo le-SMS"</string>
+ <!-- no translation found for default_sms_app (3907546126124760465) -->
+ <skip />
+ <string name="mms_phone_number_pref_title" msgid="5445275222817760638">"Inombolo yakho yefoni"</string>
+ <string name="unknown_phone_number_pref_display_value" msgid="5191326096424554297">"Akwaziwa"</string>
+ <string name="send_sound_pref_title" msgid="6284747469983942370">"Imisindo yemilayezo ephumayo"</string>
+ <string name="dump_sms_pref_title" msgid="4057657151746557281">"Lahla i-SMS"</string>
+ <string name="dump_sms_pref_summary" msgid="3694039770323225329">"Lahla idatha engahluziwe ye-SMS etholiwe kufayela lesitoreji sangaphandle"</string>
+ <string name="dump_mms_pref_title" msgid="6699074152055891680">"Lahla i-MMS"</string>
+ <string name="dump_mms_pref_summary" msgid="7901698352188687659">"Lahla idatha engahluziwe ye-MMS etholiwe kufayela lesitoreji sangaphandle"</string>
+ <string name="wireless_alerts_title" msgid="8218925605166939654">"Izexwayiso ezingenantambo"</string>
+ <string name="message_context_menu_title" msgid="5036023289586457642">"Izinketho zomlayezo"</string>
+ <string name="message_context_menu_copy_text" msgid="8241684826917957666">"Kopisha umbhalo"</string>
+ <string name="message_context_menu_view_details" msgid="2077089491219912840">"Buka imininingwane"</string>
+ <string name="message_context_menu_delete_message" msgid="4924354182554857475">"Susa"</string>
+ <string name="message_context_menu_forward_message" msgid="4848326950037554575">"Dlulisela phambili"</string>
+ <string name="message_details_title" msgid="8451487656255395372">"Imininingwane yomlayezo"</string>
+ <string name="message_type_label" msgid="6442873901113487978">"Uhlobo: "</string>
+ <string name="text_message" msgid="7415419755252205721">"Umlayezo wombhalo"</string>
+ <string name="multimedia_message" msgid="2199989099980111684">"Umlayezo wokuxhumana okuxubile"</string>
+ <string name="from_label" msgid="1947831848146564875">"Kusuka ku: "</string>
+ <string name="to_address_label" msgid="1816631887533235762">"Ku: "</string>
+ <string name="sent_label" msgid="5186286057597137301">"Okuthunyelwe: "</string>
+ <string name="received_label" msgid="4442494712757995203">"Okutholiwe: "</string>
+ <string name="subject_label" msgid="1887378451808609152">"Isihloko: "</string>
+ <string name="message_size_label" msgid="8840394477993741203">"Usayizi: "</string>
+ <string name="priority_label" msgid="5029073794896222902">"Okubalulekile: "</string>
+ <string name="sim_label" msgid="2706003016582772108">"SIM: "</string>
+ <string name="priority_high" msgid="728836357310908368">"Okuphezulu"</string>
+ <string name="priority_normal" msgid="8918221917628753075">"Okuvamile"</string>
+ <string name="priority_low" msgid="7398724779026801851">"Okuphansi"</string>
+ <string name="sim_slot_identifier" msgid="5934005977415016295">"SIM <xliff:g id="SIM_SLOT_NUMBER">%s</xliff:g>"</string>
+ <string name="hidden_sender_address" msgid="5054789216911282696">"Ikheli lomthumeli lifihliwe"</string>
+ <string name="cant_send_message_while_loading_attachments" msgid="4301887223941009907">"Ayikwazi ukuthumela umlayezo ngenkathi ilayisha izinamathiseli."</string>
+ <string name="fail_to_load_attachment" msgid="412233184776827353">"Ayikwazi ukulayisha okunamathiselwe. Zama futhi."</string>
+ <string name="cant_send_message_without_active_subscription" msgid="7130532514190813716">"Inethiwekhi ayilungile. Zama futhi."</string>
+ <string name="chips_text_delete_button_content_description" msgid="123760854728616068">"Susa umbhalo"</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description" msgid="1403198418131594073">"Shintsha phakathi kokufaka umbhalo nezinombolo"</string>
+ <string name="add_more_participants_button_content_description" msgid="12460574490350327">"Engeza ababambiqhaza abaningi"</string>
+ <string name="confrim_participants_button_content_description" msgid="7473740533133798616">"Qinisekisa ababambiqhazi"</string>
+ <string name="start_new_conversation" msgid="3924471215595992758">"Qala ingxoxo entsha"</string>
+ <string name="gallery_checkbox_content_description" msgid="2503122727700475428">"Khetha into"</string>
+ <string name="video_thumbnail_view_play_button_content_description" msgid="3506938388391260811">"Dlala ividiyo"</string>
+ <string name="action_people_and_options" msgid="2748020184252106661">"Abantu nezinketho"</string>
+ <string name="action_debug_options" msgid="2440658410677323714">"Lungisa iphutha"</string>
+ <string name="people_and_options_activity_title" msgid="5443749025829291736">"Abantu nezinketho"</string>
+ <string name="general_settings_title" msgid="4465312111301728995">"Okuvamile"</string>
+ <string name="participant_list_title" msgid="8624855187574757110">"Abantu kule ngxoxo"</string>
+ <string name="action_call" msgid="6596167921517350362">"Yenza ikholi"</string>
+ <string name="compose_message_view_hint_text" msgid="5214836453231753054">"Thumela umlayezo"</string>
+ <string name="compose_message_view_hint_text_multi_sim" msgid="2829154383848677160">"Thumela umlayezo&lt;br/&gt;&lt;small&gt;kusukela ku-<xliff:g id="SIM_NAME">%s</xliff:g>&lt;/small&gt;"</string>
+ <plurals name="compose_message_view_hint_text_photo" formatted="false" msgid="8937589900456604618">
+ <item quantity="one">Thumela izithombe</item>
+ <item quantity="other">Thumela izithombe</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_audio" formatted="false" msgid="5934090345811195170">
+ <item quantity="one">Thumela imisindo</item>
+ <item quantity="other">Thumela imisindo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_video" formatted="false" msgid="9188048653766960507">
+ <item quantity="one">Thumela amavidiyo</item>
+ <item quantity="other">Thumela amavidiyo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_vcard" formatted="false" msgid="933064969858631242">
+ <item quantity="one">Thumela amakhadi oxhumana nabo</item>
+ <item quantity="other">Thumela amakhadi oxhumana nabo</item>
+ </plurals>
+ <plurals name="compose_message_view_hint_text_attachments" formatted="false" msgid="1778518334602418000">
+ <item quantity="one">Thumela okunamathiselwe</item>
+ <item quantity="other">Thumela okunamathiselwe</item>
+ </plurals>
+ <plurals name="attachment_changed_accessibility_announcement" formatted="false" msgid="5592871135747616108">
+ <item quantity="one"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> okunamathiselwe kukulungele ukuthunyelwa</item>
+ <item quantity="other"><xliff:g id="ATTACHMENT_COUNT">%d</xliff:g> okunamathiselwe kukulungele ukuthunyelwa</item>
+ </plurals>
+ <string name="menu_send_feedback" msgid="479412726781330278">"Thumela impendulo"</string>
+ <string name="menu_view_in_store" msgid="3792488955421628684">"Buka ku-Google Play Isitolo"</string>
+ <string name="menu_version_info" msgid="6122616816253404821">"Ulwazi lwenguqulo"</string>
+ <string name="subtitle_format_for_version_number" msgid="446082111129170749">"Inguqulo %1$s"</string>
+ <string name="menu_license" msgid="1157954180701135202">"Amalayisense womthombo ovulekile"</string>
+ <string name="notifications_enabled_conversation_pref_title" msgid="1492686092760478206">"Izaziso"</string>
+ <string name="mms_attachment_limit_reached" msgid="8303890455085643301">"Kufinyelelwe kumkhawulo wokunamathiselwe kwi-imeyili"</string>
+ <string name="mms_attachment_load_failed" msgid="5697191348996648727">"Ihlulekile ukulayisha okunamathiselwe kwi-imeyili."</string>
+ <string name="add_contact_confirmation_dialog_title" msgid="1898307408816625598">"Engeza oxhumana nabo"</string>
+ <string name="add_contact_confirmation" msgid="1479380805406328264">"Engeza oxhumana naye"</string>
+ <string name="compose_message_view_subject_hint_text" msgid="6076616675845705660">"Isihloko"</string>
+ <string name="conversation_message_view_subject_text" msgid="5634274498769555505">"Isihloko: "</string>
+ <string name="notification_subject" msgid="5229975483160300625">"<xliff:g id="SUBJECT_LABEL">%s</xliff:g><xliff:g id="MESSAGETEXT">%s</xliff:g>"</string>
+ <string name="loading_vcard" msgid="3961520941271265083">"Ilayisha ikhadi loxhumana naye"</string>
+ <string name="failed_loading_vcard" msgid="7711070643740368402">"Ayikwazi ukulayisha ikhadi loxhumana naye"</string>
+ <string name="vcard_tap_hint" msgid="4940284329175200952">"Buka ikhadi lokuxhumana"</string>
+ <plurals name="vcard_multiple_display_name" formatted="false" msgid="2884224435488996028">
+ <item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> oxhumana nabo</item>
+ <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> oxhumana nabo</item>
+ </plurals>
+ <string name="vcard_detail_activity_title" msgid="591540798943754991">"Amakhadi wokuxhumana"</string>
+ <string name="vcard_detail_birthday_label" msgid="7196021537629120021">"Usuku lokuzalwa"</string>
+ <string name="vcard_detail_notes_label" msgid="6104179009509800126">"Amanothi"</string>
+ <string name="forward_message_activity_title" msgid="4689730643900226699">"Dlulisa umlayezo"</string>
+ <string name="reply_activity_title" msgid="2967630094609648609">"Phendula"</string>
+ <string name="plus_one" msgid="9010288417554932581">"+1"</string>
+ <string name="plus_n" msgid="8961547034116059566">"+%d"</string>
+ <string name="sms_disabled" msgid="4988773371061820432">"I-SMS ikhutshaziwe"</string>
+ <string name="requires_default_sms_app_to_send" msgid="171048611973157166">"Ukuze uthumele, setha Ukulayeza njengohlelo lwakho lokusebenza oluzenzakalelayo lwe-SMS"</string>
+ <string name="requires_default_sms_app" msgid="7477365167876194810">"Setha Ukulayeza njengohlelo lokusebenza oluzenzakalelayo lwe-SMS"</string>
+ <string name="requires_default_sms_change_button" msgid="6717443481161057941">"Guqula"</string>
+ <string name="recommend_set_default_sms_app" msgid="7769636305626277844">"Ukuze uthole imilayezo, setha Ukulayeza njengohlelo lokusebenza oluzenzakalelayo lwe-SMS"</string>
+ <string name="no_preferred_sim_selected" msgid="8583927728936521140">"Ayikho i-SIM ethandwayo ekhethiwe yokuthumela imilayezo ye-SMS"</string>
+ <string name="requires_sms_permissions_message" msgid="2179684358095980506">"Lolu hlelo lokusebenza aluvumelekile umnikazi wedivayisi."</string>
+ <string name="requires_sms_permissions_close_button" msgid="8601569832171759435">"KULUNGILE"</string>
+ <string name="too_many_participants" msgid="5857516461210932810">"Ababambiqhaza abaningi kakhulu engxoxweni"</string>
+ <plurals name="add_invalid_contact_error" formatted="false" msgid="782438833843363189">
+ <item quantity="one">Oxhumana nabo abangavumelekile</item>
+ <item quantity="other">Oxhumana nabo abangavumelekile</item>
+ </plurals>
+ <string name="camera_media_failure" msgid="6532763214546593687">"Ayikwazanga ukulayisha isithombe sekhamera"</string>
+ <string name="conversation_list_item_view_sent_from_you_prefix" msgid="1735457801737604211">"Wena: "</string>
+ <string name="conversation_list_item_view_sent_from_other_prefix" msgid="9202622757034736175">"<xliff:g id="FIRSTNAMEOFSENDER">%s</xliff:g>: "</string>
+ <string name="conversation_list_item_view_draft_message" msgid="3592923997082845035">"Okusalungiswa"</string>
+ <string name="conversation_list_empty_text" msgid="7505706551294769667">"Uma uqala ingxoxo entsha, uzoyibona kuhlu lapha"</string>
+ <string name="archived_conversation_list_empty_text" msgid="6109126963298874571">"Izingxoxo ezifakwe kungobo yomlando zivela lapha"</string>
+ <string name="conversation_list_first_sync_text" msgid="3749751291444609993">"Ilayisha izingxoxo..."</string>
+ <string name="conversation_list_snippet_picture" msgid="5326960910088541826">"Isithombe"</string>
+ <string name="conversation_list_snippet_audio_clip" msgid="7894183429890407387">"Isiqeshana somsindo"</string>
+ <string name="conversation_list_snippet_video" msgid="7101059507173671233">"Ividiyo"</string>
+ <string name="conversation_list_snippet_vcard" msgid="5004837959112452394">"Ikhadi loxhumana naye"</string>
+ <string name="mms_text" msgid="1528791558806015806">"i-MMS"</string>
+ <string name="snack_bar_undo" msgid="4523395751563700308">"Hlehlisa"</string>
+ <string name="snack_bar_retry" msgid="7140970704902570020">"Zama futhi"</string>
+ <string name="contact_list_empty_text" msgid="1036353827229041942">"Faka igama loxhumana naye noma inombolo eyefoni ukuze uqale umlayezo omusha"</string>
+ <string name="action_block" msgid="9032076625645190136">"Vimba"</string>
+ <string name="block_contact_title" msgid="6968382557194643329">"Vimbela u-<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="unblock_contact_title" msgid="4719854710980066596">"Susa ukuvimbela u-<xliff:g id="DESTINATION">%s</xliff:g>"</string>
+ <string name="block_confirmation_title" msgid="8288283455512985682">"Vumba e-<xliff:g id="DESTINATION">%s</xliff:g>?"</string>
+ <string name="block_confirmation_message" msgid="185429978461824228">"Uzoqhubeka nokuthola imilayezo kusuka kule nombolo kodwa ngeke usaziswa. Le ngxoxo izofakwa kungobo yomlando."</string>
+ <string name="blocked_contacts_title" msgid="7709164518967312281">"Oxhumana nabo abavinjiwe"</string>
+ <string name="tap_to_unblock_message" msgid="1284284144816901647">"VULELA"</string>
+ <string name="view_blocked_contacts_title" msgid="7417596291306885403">"Oxhumana nabo abavinjiwe"</string>
+ <string name="pick_image_from_document_library_content_description" msgid="132845956503874378">"Khetha isithombe kusuka kulabhulali yedokhumenti"</string>
+ <string name="sending_message" msgid="6363584950085384929">"Ithumela umlayezo"</string>
+ <string name="send_message_success" msgid="4088006261869323324">"Umyalezo uthunyelwe"</string>
+ <string name="send_message_failure_no_data" msgid="7404080465234559802">"Idatha yeselula ivaliwe. Hlola izilungiselelo zakho."</string>
+ <string name="send_message_failure_airplane_mode" msgid="3966519541237053840">"Ayikwazi ukuthumela imilayezo kumodi yendiza"</string>
+ <string name="send_message_failure" msgid="5273892629851390023">"Umlayezo awukwazanga ukuthunyelwa"</string>
+ <string name="download_message_success" msgid="3514921076616367225">"Umlayezo ulandiwe"</string>
+ <string name="download_message_failure_no_data" msgid="6830055949399088532">"Idatha yeselula ivaliwe. Hlola izilungiselelo zakho."</string>
+ <string name="download_message_failure_airplane_mode" msgid="6752478540984118382">"Ayikwazi ukulanda imilayezo kumodi ye-Ndiza"</string>
+ <string name="download_message_failure" msgid="635370887537738004">"Umlayezo awukwazanga ukulandwa"</string>
+ <string name="content_description_for_number_zero" msgid="7992301592202897868">"Iqanda"</string>
+ <string name="content_description_for_number_one" msgid="3886554185135150473">"Kunye"</string>
+ <string name="content_description_for_number_two" msgid="1562486479385891146">"Kubili"</string>
+ <string name="content_description_for_number_three" msgid="8471442614073144385">"Kuthathu"</string>
+ <string name="content_description_for_number_four" msgid="7515917522043407213">"Kune"</string>
+ <string name="content_description_for_number_five" msgid="8078774905490022978">"Kuhlanu"</string>
+ <string name="content_description_for_number_six" msgid="9192012772645396654">"Isithupha"</string>
+ <string name="content_description_for_number_seven" msgid="5870011338757220060">"Yisikhombisa"</string>
+ <string name="content_description_for_number_eight" msgid="7078061834874737466">"Isishiyagalombili"</string>
+ <string name="content_description_for_number_nine" msgid="4276787959475295232">"Isishiyagalolunye"</string>
+ <string name="carrier_send_error" msgid="3796492439123251453">"Ayikwazi ukuthumela umlayezo nge-<xliff:g id="CARRIERNAME">%1$s</xliff:g>, iphutha <xliff:g id="ERRORCODE">%2$d</xliff:g>"</string>
+ <string name="carrier_send_error_unknown_carrier" msgid="1210665032244425938">"Ayikwazi ukuthumela umlayezo ngenkampani yenethiwekhi engaziwa, iphutha <xliff:g id="ERRORCODE">%1$d</xliff:g>"</string>
+ <string name="message_fwd" msgid="2144370964507743673">"Dlulisa: <xliff:g id="SUBJECT">%s</xliff:g>"</string>
+ <string name="mms_failure_outgoing_service" msgid="4662217039143124592">"Umlayezo awuthunyelwe: isevisi ayisebenzi kunethiwekhi"</string>
+ <string name="mms_failure_outgoing_address" msgid="6212773250215580849">"Umlayezo awuthunyelwe: ikheli lendawo okuya kulo alivumelekile"</string>
+ <string name="mms_failure_outgoing_corrupt" msgid="6132734324976690436">"Umlayezo awuthunyelwe: umlayezo ongavumelekile"</string>
+ <string name="mms_failure_outgoing_content" msgid="919220273446626565">"Umlayezo awuthunyelwe: okuqukethwe okungasekelwe"</string>
+ <string name="mms_failure_outgoing_unsupported" msgid="2614377585077448979">"Umlayezo awuthunyelwe: umlayezo ongasekelwe"</string>
+ <string name="mms_failure_outgoing_too_large" msgid="3917966922000717407">"Umlayezo awuthunyelwe: mkhulu kakhulu"</string>
+ <string name="in_conversation_notify_new_message_text" msgid="8207541514656839042">"Umlayezo omusha"</string>
+ <string name="in_conversation_notify_new_message_action" msgid="7311780674392006753">"Buka"</string>
+ <string name="message_image_content_description" msgid="621604138442762130">"Isithombe"</string>
+ <string name="activity_not_found_message" msgid="2355153262520375529">"Ayikwazanga ukuthola uhlelo lokusebenza olufanelekile"</string>
+ <string name="chips_delete_content_description" msgid="4655699207140895492">"Susa umamukeli"</string>
+ <string name="share_new_message" msgid="2135955613694195483">"Umlayezo omusha"</string>
+ <string name="share_cancel" msgid="6666929391961668469">"Khansela"</string>
+ <string name="apn_edit" msgid="2134993966166435648">"Hlela iphoyinti lokufinyelela"</string>
+ <string name="apn_not_set" msgid="463728018542184151">"Ayisethiwe"</string>
+ <string name="apn_name" msgid="1572691851070894985">"Igama"</string>
+ <string name="apn_apn" msgid="1197541953189716999">"I-APN"</string>
+ <string name="apn_mmsc" msgid="2584154739440281747">"I-MMSC"</string>
+ <string name="apn_mms_proxy" msgid="4343743931563107100">"Ummeleli we-MMS"</string>
+ <string name="apn_mms_port" msgid="6181253508404620479">"Imbobo ye-MMS"</string>
+ <string name="apn_mcc" msgid="8102023058623950736">"I-MCC"</string>
+ <string name="apn_mnc" msgid="1372437523197012866">"I-MNC"</string>
+ <string name="apn_type" msgid="8409755622399386584">"Uhlobo lwe-APN"</string>
+ <string name="menu_delete_apn" msgid="4947391038600888284">"Susa i-APN"</string>
+ <string name="menu_new_apn" msgid="1564461309350814037">"I-APN Entsha"</string>
+ <string name="menu_save_apn" msgid="5445335896951613691">"Londoloza"</string>
+ <string name="menu_discard_apn_change" msgid="310569150407968648">"Lahla"</string>
+ <string name="error_apn_name_empty" msgid="7770363665690646716">"Igama lenkambu akumele lingabi nalutho."</string>
+ <string name="error_apn_empty" msgid="6799644880052534839">"I-APN akumele ingabi nalutho."</string>
+ <string name="error_mcc_not3" msgid="3693788817630985838">"Inkundla ye-MCC kumele ibe namadijithi angu-3."</string>
+ <string name="error_mnc_not23" msgid="3726130455168743554">"Inkambu ye-MNC kumelwe ibe amadijithi angu-2 noma angu-3."</string>
+ <string name="restore_default_apn" msgid="7180447255249638396">"Ibuyisela izilungiselelo ze-APN zokuzenzakalelayo."</string>
+ <string name="menu_restore_default_apn" msgid="6342935229867973523">"Setha kabusha kube ngokuzenzakalelayo"</string>
+ <string name="restore_default_apn_completed" msgid="7421687541468349715">"Izilungiselelo ze-APN zokusethwa kabusha okuzenzakalelayo ziqediwe."</string>
+ <string name="untitled_apn" msgid="3110576506102839465">"Akunasihloko"</string>
+ <string name="sms_apns_title" msgid="1351513141821300879">"Amagama wephoyinti lokufinyelela"</string>
+ <string name="apn_settings" msgid="3179955111000379490">"Ama-APN"</string>
+ <string name="menu_new" msgid="8286285392706532511">"I-APN Entsha"</string>
+ <string name="apn_settings_not_available" msgid="5136389328322585717">"Izilungiselelo zegama le-Phoyinti Lokufinyelela azitholakali kulo msebenzisi"</string>
+ <string name="copy_to_clipboard_dialog_title" msgid="494269163973708182">"Kopisha kubhodi lokunamathisela?"</string>
+ <string name="copy_to_clipboard" msgid="1977083934277981786">"Kopisha"</string>
+ <string name="incoming_sim_name_text" msgid="4244076415705614525">"ukuya ku-<xliff:g id="SIM_NAME">%s</xliff:g>"</string>
+ <string name="general_settings" msgid="5409336577057897710">"Okuvamile"</string>
+ <string name="advanced_settings" msgid="5870459931510000742">"Okuthuthukisiwe"</string>
+ <string name="general_settings_activity_title" msgid="3012187932521771578">"Izilungiselelo ezijwayelekile"</string>
+ <string name="advanced_settings_activity_title" msgid="7397017836928206201">"Izilungiselelo ezithuthukisiwe"</string>
+ <string name="sim_specific_settings" msgid="948779178668552448">"\"<xliff:g id="SIM_NAME">%s</xliff:g>\" SIM"</string>
+ <string name="disable_group_mms" msgid="1016285369211000297">"Thumela imilayezo ye-SIM ngamunye kubo bonke abamukeli. Uwena kuphela ozothla izimpendulo"</string>
+ <string name="enable_group_mms" msgid="4311733160940564593">"Thumela i-SMS eyodwa kubo bonke abamukeli"</string>
+ <string name="sim_settings_unknown_number" msgid="1885996389432991383">"Inombolo engaziwa"</string>
+ <string name="secondary_user_new_message_title" msgid="201938947573987596">"Umlayezo omusha."</string>
+ <string name="secondary_user_new_message_ticker" msgid="2386669280323553032">"Umlayezo omusha."</string>
+ <string name="sim_selector_button_content_description" msgid="2407660715957787727">"Iskhethi se-SIM"</string>
+ <string name="sim_selector_button_content_description_with_selection" msgid="4042539046779910617">"<xliff:g id="SIM_0">%1$s</xliff:g> okukhethiwe, isikhethi se-SIM"</string>
+ <string name="send_button_long_click_description_no_sim_selector" msgid="979624100711380593">"Hlela isihloko"</string>
+ <string name="send_button_long_click_description_with_sim_selector" msgid="7670274457707760465">"Khetha i-SIM noma hlela isihloko"</string>
+ <string name="audio_record_view_content_description" msgid="29401157183728655">"Thinta futhi ubambe ukuze urekhode umsindo"</string>
+ <string name="widget_new_conversation_content_description" msgid="4214201569175733579">"Qala ingxoxo entsha"</string>
+ <string name="widget_title_content_description" msgid="5802836840910466231">"Imilayezo"</string>
+ <string name="widget_conversation_list_name" msgid="4005037639000611925">"Uhlu lokulayeza"</string>
+ <string name="widget_conversation_name" msgid="3900606239571892871">"Imilayezo"</string>
+ <string name="widget_new_message_content_description" msgid="2706347689522691188">"Umlayezo omusha"</string>
+ <string name="widget_conversation_list_content_description" msgid="4137121313374868821">"Uhlu lwengxoxo"</string>
+ <string name="loading_conversations" msgid="2890879017729068514">"Ilayisha izingxoxo"</string>
+ <string name="loading_messages" msgid="3894765818932489665">"Ilayisha"</string>
+ <string name="view_more_conversations" msgid="2430542054023217740">"Buka izingxoxo ezengeziwe"</string>
+ <string name="view_more_messages" msgid="6463224009407336178">"Buka eminye imilayezo"</string>
+ <string name="conversation_deleted" msgid="6088388615460305424">"Ingxoxo isusiwe"</string>
+ <string name="tap_to_configure" msgid="7591682335533041774">"Ingxoxo isusiwe. Thinta ukuze ubonise ingxoxo ehlukile yokulayeza"</string>
+ <string name="update_destination_blocked" msgid="4577227174205233981">"Uvinjelwe"</string>
+ <string name="update_destination_unblocked" msgid="5369499874676600478">"Uvulelwe"</string>
+ <string name="db_full" msgid="8459265782521418031">"Isikhala sesitoreji siphansi. Enye idatha ingalahleka."</string>
+ <string name="attachment_chooser_activity_title" msgid="4049093653421684774">"Khetha okunamathiselwe"</string>
+ <string name="action_confirm_selection" msgid="3787494008738625806">"Qinisekisa ukukhetha"</string>
+ <string name="attachment_chooser_selection" msgid="3616821885478549778">"<xliff:g id="COUNT">%d</xliff:g> okukhethiwe"</string>
+ <string name="attachment_limit_reached_dialog_message_when_composing" msgid="2983376679583030373">"Sicela ususe okunamathiselwe kwi-imeyili okukodwa noma okuningi uphinde uzame futhi."</string>
+ <string name="attachment_limit_reached_dialog_message_when_sending" msgid="3917529855170816197">"Ungazama ukuthumela umlayezo wakho, kodwa kungenzeka ungathunyelwa ngaphandle kokuthi ususe okunamathiselwe kwi-imeyili okukodwa noma okuningi."</string>
+ <string name="video_attachment_limit_exceeded_when_sending" msgid="8545536951461996462">"Ungathumela ividiyo eyodwa kuphela ngomlayezo. Sicela ususe amavidiyo angeziwe uphinde uzame futhi."</string>
+ <string name="attachment_load_failed_dialog_message" msgid="4917927407363303333">"Ukulayeza kuhlulekile ukulayisha okunamathiselwe kwi-imeyili."</string>
+ <string name="attachment_limit_reached_send_anyway" msgid="2348790618093735551">"Thumela noma kunjalo"</string>
+ <string name="conversation_creation_failure" msgid="8597624563218724441">"Ayikwazanga ukuqala ingxoxo"</string>
+ <string name="link_display_format" msgid="8700344957248709584">"<xliff:g id="TEXT">%1$s</xliff:g> (<xliff:g id="URL">%2$s</xliff:g>)"</string>
+ <string name="selected_sim_content_message" msgid="4504796674843354505">"I-<xliff:g id="SELECTED_SIM">%s</xliff:g> ikhethiwe"</string>
+</resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..59e3206
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,42 @@
+<?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">
+ <!-- String to match as no subject and filter out as a subject. For example, if the
+ subject string is "no subject", we won't display that. We'll pretend
+ no subject string was delivered. -->
+ <string-array name="empty_subject_strings">
+ <item>no subject</item>
+ <item>nosubject</item>
+ </string-array>
+
+ <!-- Choices for quick-reply to message notification. Users with an Android
+ Wear device (e.g. smartwatch) can reply to messages either by speaking their
+ response, or choosing one of these predefined options. -->
+ <string-array name="notification_reply_choices">
+ <item>Yes</item>
+ <item>No</item>
+ <item>OK</item>
+ <item>Hehe</item>
+ <item>Thanks</item>
+ <item>I agree</item>
+ <item>Nice</item>
+ <item>On my way</item>
+ <item>OK, let me get back to you later</item>
+ <item>:)</item>
+ <item>:(</item>
+ </string-array>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..233cf9c
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,61 @@
+<?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>
+ <declare-styleable name="ContactIconView">
+ <attr name="iconSize">
+ <enum name="normal" value="0" />
+ <enum name="large" value="1" />
+ <enum name="small" value="2" />
+ </attr>
+ </declare-styleable>
+ <declare-styleable name="SoundLevels">
+ <attr name="maxLevelRadius" format="dimension|reference" />
+ <attr name="minLevelRadius" format="dimension|reference" />
+ <attr name="primaryColor" format="color" />
+ </declare-styleable>
+ <declare-styleable name="AsyncImageView">
+ <attr name="fadeIn" format="boolean" />
+ <attr name="placeholderDrawable" format="reference" />
+ <attr name="cornerRadius" format="dimension|reference" />
+ <attr name="reveal" format="boolean" />
+ </declare-styleable>
+ <declare-styleable name="VideoThumbnailView">
+ <attr name="mode" format="integer">
+ <enum name="imageThumbnail" value="0" />
+ <enum name="playableVideo" value="1" />
+ </attr>
+ <attr name="playOnLoad" format="boolean" />
+ <attr name="loop" format="boolean" />
+ <attr name="allowCrop" format="boolean" />
+ <attr name="android:maxHeight" />
+ </declare-styleable>
+ <declare-styleable name="MaxHeightScrollView">
+ <attr name="android:maxHeight" />
+ </declare-styleable>
+ <declare-styleable name="ConversationNameView">
+ <attr name="textSize" format="dimension|reference" />
+ <attr name="textColor" format="color" />
+ </declare-styleable>
+ <declare-styleable name="AudioAttachmentView">
+ <attr name="layoutMode" format="integer">
+ <enum name="normal" value="0" />
+ <enum name="compact" value="1" />
+ <enum name="subcompact" value="2" />
+ </attr>
+ </declare-styleable>
+ <attr name="apnPreferenceStyle" format="reference" />
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..f33e105
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,174 @@
+<?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>
+ <color name="primary_color">#689F38</color>
+ <color name="permission_check_activity_background">@color/primary_color</color>
+
+ <!-- Action bar -->
+ <color name="action_bar_title_text_color">#ffffff</color>
+ <color name="action_bar_background_color">@color/primary_color</color>
+ <color name="action_bar_background_color_dark">#537F2D</color>
+ <color name="contextual_action_bar_background_color">#ffffff</color>
+ <color name="archived_conversation_action_bar_background_color">#9D9D9D</color>
+ <color name="archived_conversation_action_bar_background_color_dark">#838383</color>
+
+ <!-- Conversation list -->
+ <color name="conversation_list_item_read">#636363</color>
+ <color name="conversation_list_item_unread">#323232</color>
+ <color name="conversation_list_error">#F05A24</color>
+ <color name="conversation_list_name">#DE000000</color>
+ <color name="conversation_list_details">#8A000000</color>
+
+ <color name="conversation_compose_divider_start">#44000000</color>
+
+ <color name="message_text">#000000</color>
+ <color name="timestamp_text_outgoing">#99323232</color>
+ <color name="timestamp_text_incoming">#99ffffff</color>
+ <color name="timestamp_text_failed">#99ff0000</color>
+ <color name="compose_send_text_color">#000000</color>
+
+ <color name="message_title_text">#ff323232</color>
+ <color name="message_download_failed_status_text">#000000</color>
+ <color name="message_info_text">#99ffffff</color>
+ <color name="message_info_text_incoming_download_failed">#6a6a6a</color>
+ <color name="message_download_failed_timestamp_text">#007fce</color>
+ <color name="message_failed_timestamp_text">#99ff0000</color>
+ <color name="message_action_status_text">#ffffffff</color>
+ <color name="message_action_info_text">#99ffffff</color>
+ <color name="message_action_timestamp_text">#ffffffff</color>
+
+ <color name="background_item_transparent">@android:color/transparent</color>
+ <color name="background_item_activated">#670099cc</color>
+ <color name="background_item_selected">#cc0099cc</color>
+ <color name="background_item_pressed">#6733b5e5</color>
+
+ <color name="subject_editor_bubble">#ffffffff</color>
+ <color name="message_text_color_incoming">#ffffffff</color>
+ <color name="message_text_color_incoming_download_failed">#6a6a6a</color>
+ <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_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="generic_video_icon">#ff808080</color>
+
+ <!-- Base color used for color filtering. -->
+ <color name="color_filter_base_color">#ff0000</color>
+
+ <!-- Activity background color -->
+ <drawable name="class_zero_background">#7f040000</drawable>
+
+ <color name="contact_avatar_default_background">#d2d2d2</color>
+ <color name="contact_avatar_pressed_color">#40000000</color>
+ <color name="contact_list_text_primary">#4d4d4d</color>
+ <color name="contact_list_text_secondary">#999999</color>
+
+ <color name="compose_contact_text">@android:color/white</color>
+ <color name="compose_contact_faint_text">#a0ffffff</color>
+ <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_picker_background">#ffffff</color>
+ <color name="chips_dropdown_background_activated">#4285f4</color>
+ <color name="chips_dropdown_background_pressed">#ededed</color>
+ <color name="chips_background_color">#33000000</color>
+ <color name="chips_text_color">@android:color/white</color>
+
+ <color name="camera_button_container_background">#80000000</color>
+
+ <color name="translucent_white">#80ffffff</color>
+
+ <color name="gallery_image_default_background">#eeeeee</color>
+ <color name="gallery_image_pressed">#6733b5e5</color>
+ <color name="attachment_preview_more_items_text_background">#44000000</color>
+
+ <color name="letter_tile_font_color">#ffffff</color>
+
+ <color name="audio_picker_level_primary_color">#29000000</color>
+ <color name="audio_record_control_button_stroke">#30000000</color>
+ <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="notification_sender_text">#9A9A9A</color>
+ <color name="notification_secondary_text">#FFFFFF</color>
+ <color name="notification_tertiary_text">#FFFFFF</color>
+ <color name="wearable_notification_participants_count">#a2a2a2</color>
+ <color name="notification_accent_color">@color/primary_color</color>
+ <color name="notification_warning_color">#ff0000</color>
+ <color name="notification_subject_color">#99aaaaaa</color>
+
+ <color name="participant_list_text_primary">#4d4d4d</color>
+ <color name="participant_list_text_secondary">#6d6d6d</color>
+ <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_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="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="open_conversation_animation_background_shadow">#40000000</color>
+ <color name="compose_notification_bar_background">@color/primary_color</color>
+
+ <color name="sim_selector_text_primary">#4d4d4d</color>
+ <color name="sim_selector_text_secondary">#999999</color>
+ <color name="sim_selector_background_end">#cceeeeee</color>
+ <color name="sim_selector_background_start">#ffeeeeee</color>
+ <color name="sim_indicator_color_light">#ffffff</color>
+ <color name="sim_indicator_color_dark">#323232</color>
+
+ <color name="text_highlight_color">#80689F38</color>
+ <color name="search_view_text_cursor">#b0dddddd</color>
+
+ <color name="button_bar_action_button_text_color">#03a9f4</color>
+ <color name="button_bar_cancel_button_text_color">#4d4d4d</color>
+
+ <color name="settings_list_text_primary">#4d4d4d</color>
+ <color name="settings_list_text_secondary">#6d6d6d</color>
+ <color name="group_mms_setting_text_color">#333333</color>
+ <color name="settings_item_title_text_primary">#000000</color>
+
+ <color name="contact_picker_button_text_color">@color/primary_color</color>
+
+ <color name="widget_text_color">#424242</color>
+ <color name="widget_incoming_text_color">@android:color/white</color>
+ <color name="widget_outgoing_text_color">#424242</color>
+ <color name="widget_background_color">#ffffff</color>
+ <color name="ripple_material_light">#40000000</color>
+ <color name="widget_conversation_title_color">#FFFFFF</color>
+
+ <color name="attachment_chooser_audio_background">#eeeeee</color>
+ <color name="attachment_chooser_vcard_background">#eeeeee</color>
+
+ <color name="fastscroll_track_color">#1e000000</color>
+ <color name="fastscroll_thumb_color">#42000000</color>
+ <color name="fastscroll_preview_text_color">#ffffff</color>
+
+ <color name="google_gray">#F1F1F1</color>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..954f5ed
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,21 @@
+<?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>
+ <!-- The packaged version of APNs and MMS configurations -->
+ <integer name="apnsAndMmsConfigsVersion">0</integer>
+</resources>
diff --git a/res/values/constants.xml b/res/values/constants.xml
new file mode 100644
index 0000000..8985fd1
--- /dev/null
+++ b/res/values/constants.xml
@@ -0,0 +1,95 @@
+<?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>
+
+ <!-- Preference keys for user-visible settings -->
+ <!-- Application-wide settings -->
+ <string name="notifications_category_pref_key" translatable="false">notifications_category</string>
+ <string name="mms_messaging_category_pref_key" translatable="false">mms_messaging_category</string>
+ <string name="advanced_category_pref_key" translatable="false">advanced_category</string>
+ <string name="notifications_enabled_pref_key" translatable="false">notifications_enabled</string>
+ <bool name="notifications_enabled_pref_default" translatable="false">true</bool>
+ <string name="notification_sound_pref_key" translatable="false">notification_sound</string>
+ <string name="notification_vibration_pref_key" translatable="false">notification_vibration</string>
+ <bool name="notification_vibration_pref_default" translatable="false">true</bool>
+ <string name="sms_disabled_pref_key" translatable="false">sms_disabled</string>
+ <string name="sms_enabled_pref_key" translatable="false">sms_enabled</string>
+ <string name="send_sound_pref_key" translatable="false">send_sound</string>
+ <bool name="send_sound_pref_default" translatable="false">true</bool>
+ <string name="advanced_pref_key" translatable="false">advanced_prefs</string>
+
+ <!-- Subscription-specific settings. The values of these pref keys must be prefixed with
+ "buglesub_" to allow for runtime sanity checks -->
+ <string name="delivery_reports_pref_key" translatable="false">buglesub_delivery_reports</string>
+ <bool name="delivery_reports_pref_default" translatable="false">false</bool>
+ <string name="auto_retrieve_mms_pref_key" translatable="false">buglesub_auto_retrieve_mms</string>
+ <bool name="auto_retrieve_mms_pref_default" translatable="false">true</bool>
+ <string name="auto_retrieve_mms_when_roaming_pref_key" translatable="false">buglesub_auto_retrieve_mms_when_roaming</string>
+ <bool name="auto_retrieve_mms_when_roaming_pref_default" translatable="false">false</bool>
+ <string name="group_mms_pref_key" translatable="false">buglesub_group_messaging</string>
+ <bool name="group_mms_pref_default" translatable="false">true</bool>
+ <string name="mms_phone_number_pref_key" translatable="false">buglesub_mms_phone_number</string>
+ <string name="wireless_alerts_key" translatable="false">buglesub_wireless_alerts_key</string>
+ <string name="apn_list_pref_key" translatable="false">buglesub_apn_list</string>
+
+ <!-- Debug preferences -->
+ <string name="debug_pref_key" translatable="false">debug_category</string>
+ <string name="dump_sms_pref_key" translatable="false">dump_sms</string>
+ <bool name="dump_sms_pref_default" translatable="false">false</bool>
+ <string name="dump_mms_pref_key" translatable="false">dump_mms</string>
+ <bool name="dump_mms_pref_default" translatable="false">false</bool>
+
+
+ <!-- SMS/MMS settings keys -->
+ <!--
+ TODO: Several of these are currently unused but are expected to be needed to
+ implement SMS/MMS delivery and basic settings. Once we have the core functionality in place
+ we should do a pass to remove any unused values here.
+ -->
+ <string name="sms_apns_key" translatable="false">sms_apns_key</string>
+ <string name="use_local_apn_pref_key" translatable="false">use_local_apn_pref_key</string>
+ <bool name="use_local_apn_pref_default" translatable="false">false</bool>
+
+ <integer name="mediapicker_transition_duration">600</integer><!-- ms -->
+ <integer name="asyncimage_transition_duration">300</integer><!-- ms -->
+ <integer name="compose_transition_duration">300</integer><!-- ms -->
+ <integer name="camera_shutter_duration">200</integer><!-- ms -->
+ <fraction name="camera_shutter_max_alpha">70%</fraction>
+
+ <integer name="snackbar_translation_duration_ms">300</integer>
+
+ <!-- Notification preferences -->
+ <string name="notifications_group_children_key" translatable="false">notifications_group_children</string>
+
+ <!-- format string for creating new image files. Passed to java.text.SimpleDateFormat. -->
+ <string name="new_image_file_name_format" translatable="false">"'IMG'_yyyyMMdd_HHmmss"</string>
+ <!-- format string for creating new files of unknown type. Passed to java.text.SimpleDateFormat. -->
+ <string name="new_file_name_format" translatable="false">"yyyyMMdd_HHmmss"</string>
+
+ <!-- Swipe to Archive consts -->
+ <integer name="swipe_duration_ms">300</integer>
+ <integer name="swipe_max_fling_velocity_px_per_s">4000</integer>
+
+ <!-- Fab animation const -->
+ <integer name="fab_animation_duration_ms">300</integer>
+
+ <!-- Conversation list -> conversation animation const -->
+ <integer name="list_to_conversation_animation_duration_ms">300</integer>
+
+ <!-- Generic reveal view animation duration -->
+ <integer name="reveal_view_animation_duration">300</integer>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..270ea9e
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,191 @@
+<?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>
+
+ <dimen name="action_bar_height">56dp</dimen>
+ <dimen name="action_bar_elevation">8dp</dimen>
+ <dimen name="action_bar_text_size">20sp</dimen>
+ <dimen name="conversation_message_text_size">16sp</dimen>
+ <dimen name="conversation_status_text_size">12sp</dimen>
+ <dimen name="conversation_title_text_size">12sp</dimen>
+ <dimen name="conversation_info_text_size">12sp</dimen>
+ <dimen name="conversation_compose_send_text_size">16sp</dimen>
+ <dimen name="contact_icon_view_normal_size">42dp</dimen>
+ <dimen name="contact_icon_view_large_size">56dp</dimen>
+ <dimen name="contact_icon_view_small_size">12dp</dimen>
+ <dimen name="conversation_list_item_view_padding">16dp</dimen>
+ <dimen name="conversation_list_first_item_extra_padding">8dp</dimen>
+ <dimen name="conversation_list_image_preview_corner_radius">3dp</dimen>
+ <dimen name="conversation_list_image_preview_size">56dp</dimen>
+ <dimen name="conversation_list_contact_icon_size">56dp</dimen>
+ <dimen name="conversation_message_contact_icon_size">42dp</dimen>
+ <dimen name="conversation_message_photo_min_size">96dp</dimen>
+ <dimen name="conversation_list_notification_bell_padding">4dp</dimen>
+ <dimen name="conversation_list_empty_text_bottom_margin">30dp</dimen>
+ <dimen name="blocked_participant_list_item_view_padding">16dp</dimen>
+
+ <dimen name="message_bubble_arrow_width">9dp</dimen>
+ <dimen name="message_text_bottom_padding">12dp</dimen>
+ <dimen name="message_text_top_padding">10dp</dimen>
+ <dimen name="message_text_left_right_padding">14dp</dimen>
+ <dimen name="message_padding_default">18dp</dimen>
+ <dimen name="message_padding_same_author">2dp</dimen>
+ <dimen name="message_metadata_top_padding">4dp</dimen>
+ <dimen name="message_audio_attachment_padding">12dp</dimen>
+
+ <dimen name="mediapicker_default_chooser_height">215dp</dimen>
+ <dimen name="compose_message_contacts_container_margin">4dp</dimen>
+ <dimen name="compose_message_contacts_height">42dp</dimen>
+ <dimen name="compose_message_chip_height">36dp</dimen>
+ <dimen name="compose_message_chip_padding">3dp</dimen>
+ <dimen name="compose_message_fields_horizontal_margin">16dp</dimen>
+ <dimen name="compose_message_font_size">16sp</dimen>
+ <dimen name="compose_message_attachment_padding_sides">20dp</dimen>
+ <dimen name="compose_message_attachment_padding_topBottom">10dp</dimen>
+ <dimen name="compose_message_chips_view_max_height">133dp</dimen>
+ <dimen name="compose_message_subject_left_padding">52dp</dimen>
+ <dimen name="compose_message_subject_right_padding">64dp</dimen>
+ <dimen name="compose_message_subject_top_padding">4dp</dimen>
+ <dimen name="compose_message_subject_bottom_padding">6dp</dimen>
+ <dimen name="compose_message_subject_cancel_top_offset">-12dp</dimen>
+ <dimen name="compose_message_subject_cancel_left_margin">12dp</dimen>
+ <dimen name="compose_message_mms_indicator_padding_top">2dp</dimen>
+ <dimen name="compose_message_send_button_padding_right">12dp</dimen>
+ <dimen name="compose_message_text_box_padding_side">8dp</dimen>
+
+ <!-- Notification related dimensions. -->
+ <dimen name="notification_wearable_image_height">240dp</dimen>
+ <dimen name="notification_wearable_image_width">240dp</dimen>
+
+ <!-- Contact Picker related dimensions. -->
+ <dimen name="contact_list_text_size">16sp</dimen>
+ <dimen name="contact_list_detail_text_size">14sp</dimen>
+ <dimen name="contact_list_icon_size">42dp</dimen>
+ <dimen name="recipient_edit_text_size">20sp</dimen>
+ <dimen name="pager_tab_header_text_size">14sp</dimen>
+ <dimen name="pager_tab_underline_selected">2dp</dimen>
+ <dimen name="contact_list_alphabet_header_text_size">26dp</dimen>
+
+ <!-- Flings slower than this length / sec will be ignored -->
+ <dimen name="mediapicker_fling_threshold">150dp</dimen>
+ <!-- Flings faster than this length / sec will go from fullscreen straight to closed -->
+ <dimen name="mediapicker_big_fling_threshold">1000dp</dimen>
+ <dimen name="gallery_image_cell_size">110dp</dimen>
+ <dimen name="single_attachment_min_dimen">50dp</dimen>
+ <dimen name="single_attachment_max_height">150dp</dimen>
+ <dimen name="multiple_attachment_preview_height">130dp</dimen>
+ <dimen name="multiple_attachment_preview_width">260dp</dimen>
+ <dimen name="multiple_attachment_preview_padding">1dp</dimen>
+ <dimen name="attachment_preview_more_items_text_size">22sp</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>
+
+ <dimen name="audio_picker_text_size">16sp</dimen>
+ <dimen name="audio_attachment_text_size">14sp</dimen>
+ <dimen name="audio_progress_bar_height">6dp</dimen>
+ <!-- Videos in the message list view should at least be this big in the smallest dimension -->
+ <dimen name="video_message_min_size">320dp</dimen>
+ <dimen name="attachment_rounded_corner_radius">3dp</dimen>
+ <dimen name="progress_indicator_default_stroke_width">2dp</dimen>
+ <dimen name="progress_indicator_default_radius">12dp</dimen>
+ <dimen name="participant_list_text_size">18sp</dimen>
+ <dimen name="participant_list_detail_text_size">14sp</dimen>
+ <dimen name="people_and_options_header_text_size">16sp</dimen>
+ <dimen name="image_attachment_fallback_width">240dp</dimen>
+ <dimen name="image_attachment_fallback_height">240dp</dimen>
+
+ <dimen name="fab_size">60dp</dimen>
+ <dimen name="fab_padding_bottom">4dp</dimen>
+ <dimen name="fab_elevation">2dp</dimen>
+ <dimen name="fab_elevation_pressed">6dp</dimen>
+ <dimen name="fab_bottom_margin">12dp</dimen>
+ <dimen name="fab_left_right_margin">14dp</dimen>
+ <dimen name="message_text_counter_size">12sp</dimen>
+
+ <dimen name="vcard_detail_group_indicator_width">40dp</dimen>
+ <dimen name="mms_indicator_size">12sp</dimen>
+ <dimen name="conversation_fast_fling_threshold">10dp</dimen>
+ <dimen name="list_empty_text_size">14sp</dimen>
+ <dimen name="list_empty_text_top_margin">20dp</dimen>
+ <dimen name="list_empty_text_left_right_margin">60dp</dimen>
+ <dimen name="low_storage_action_item_text_size">16sp</dimen>
+
+ <dimen name="snack_bar_left_right_margin">24dp</dimen>
+ <dimen name="snack_bar_top_bottom_margin">18dp</dimen>
+
+ <dimen name="version_dialog_large_padding">24dp</dimen>
+ <dimen name="version_dialog_bottom_padding">20dp</dimen>
+ <dimen name="version_dialog_info_left_padding">14dp</dimen>
+ <dimen name="version_dialog_copyright_top_margin">20dp</dimen>
+
+ <dimen name="explode_animation_highlight_elevation">6dp</dimen>
+
+ <dimen name="sim_selector_text_size">14sp</dimen>
+ <dimen name="sim_selector_detail_text_size">14sp</dimen>
+ <dimen name="sim_selector_icon_size">42dp</dimen>
+
+ <dimen name="copy_contact_dialog_left_padding">25dp</dimen>
+ <dimen name="copy_contact_dialog_right_padding">10dp</dimen>
+ <dimen name="copy_contact_dialog_top_padding">5dp</dimen>
+
+ <dimen name="settings_list_text_size">16sp</dimen>
+ <dimen name="settings_list_detail_text_size">14sp</dimen>
+ <dimen name="settings_list_item_height">60dp</dimen>
+ <dimen name="group_mms_setting_text_size">14sp</dimen>
+
+ <!-- Camera focus indicator values -->
+ <dimen name="pie_radius_start">40dp</dimen>
+ <dimen name="pie_radius_increment">30dp</dimen>
+ <dimen name="pie_touch_offset">20dp</dimen>
+ <dimen name="focus_radius_offset">8dp</dimen>
+ <dimen name="focus_inner_offset">12dp</dimen>
+ <dimen name="focus_outer_stroke">3dp</dimen>
+ <dimen name="focus_inner_stroke">2dp</dimen>
+
+ <dimen name="conversation_bubble_width_snap">20dp</dimen>
+
+ <!-- Widget -->
+ <dimen name="widget_margin">8dp</dimen>
+ <dimen name="widget_subtitle_margin">0dp</dimen>
+ <dimen name="widget_avatar_padding">2dp</dimen>
+ <dimen name="widget_header_height">48dp</dimen>
+ <dimen name="widget_header_new_conv_button_width">48dp</dimen>
+ <dimen name="widget_conversation_title_size">18sp</dimen>
+ <dimen name="widget_compose_top_padding">12dp</dimen>
+ <dimen name="widget_compose_top_padding_compressed">16dp</dimen>
+
+ <dimen name="attachment_grid_image_cell_size">110dp</dimen>
+ <dimen name="attachment_grid_checkbox_area_increase">15dp</dimen>
+
+ <!-- The default size of a pending attachment while its size is unknown -->
+ <dimen name="pending_attachment_size">120dp</dimen>
+
+ <!-- Conversation fast-scroller -->
+ <dimen name="fastscroll_touch_slop">8dp</dimen>
+ <dimen name="fastscroll_track_width">8dp</dimen>
+ <dimen name="fastscroll_thumb_height">48dp</dimen>
+ <dimen name="fastscroll_preview_min_width">88dp</dimen>
+ <dimen name="fastscroll_preview_height">88dp</dimen>
+ <dimen name="fastscroll_preview_corner_radius">44dp</dimen>
+ <dimen name="fastscroll_preview_padding">24dp</dimen>
+ <dimen name="fastscroll_preview_margin_top">8dp</dimen>
+ <dimen name="fastscroll_preview_margin_left_right">8dp</dimen>
+ <dimen name="fastscroll_preview_text_size">24sp</dimen>
+
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..ec675ef
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,976 @@
+<?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">
+ <!-- The name of the application as it appears under the main Launcher icon and in various activity titles -->
+ <string name="app_name">Messaging</string>
+
+ <!-- The name of the application as it appears in external share intent dialogs -->
+ <string name="share_intent_label">Messaging</string>
+ <!-- The title when selecting a conversation to share to -->
+ <string name="share_intent_activity_label">Select conversation</string>
+
+ <string name="action_settings">Settings</string>
+ <string name="sendButtonContentDescription">Send Message</string>
+ <string name="attachMediaButtonContentDescription">Add an attachment</string>
+ <string name="help_and_feedback_activity_label">Help</string>
+
+ <string name="welcome">Welcome</string>
+ <string name="skip">Skip</string>
+ <string name="next_with_arrow">Next &gt;</string>
+ <string name="next">Next</string>
+ <string name="exit">Exit</string>
+ <string name="settings_with_arrow">Settings &gt;</string>
+ <string name="settings">Settings</string>
+ <!-- Inform user of the names of permissions that are required to use the app -->
+ <string name="required_permissions_promo">Messaging needs permission to SMS, Phone and Contacts.</string>
+ <string name="enable_permission_procedure">You can change permissions in Settings > Apps > Messaging > Permissions.</string>
+ <string name="enable_permission_procedure_description">You can change permissions in Settings, Apps, Messaging, Permissions.</string>
+
+ <!-- The tab header for the frequently used contacts list -->
+ <string name="contact_picker_frequents_tab_title">Frequents</string>
+
+ <!-- The tab header for all contacts list -->
+ <string name="contact_picker_all_contacts_tab_title">All contacts</string>
+
+ <!-- The text on the list item in the contact picker that allows a user to send an SMS directly
+ to a number that he/she typed. eg. "Send to 650-555-1234" -->
+ <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_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>
+ <string name="mediapicker_gallery_item_unselected_content_description">The media is unselected.</string>
+ <string name="mediapicker_gallery_title_selection"><xliff:g id="count">%d</xliff:g> selected</string>
+ <!-- example: "image January 17 2015 1 59 pm" -->
+ <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>
+
+ <!-- Abbreviated message to express that something occurred some number of minutes in the past (e.g., 5 minutes ago). -->
+ <plurals name="num_minutes_ago">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> min</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> mins</item>
+ </plurals>
+
+ <!-- Abbreviated message to express that something occurred some number of hours in the past (e.g., 5 hours ago). -->
+ <plurals name="num_hours_ago">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> hour</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> hours</item>
+ </plurals>
+
+ <!-- Abbreviated message to express that something occurred some number of days in the past (e.g., 5 days ago). -->
+ <plurals name="num_days_ago">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> day</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> days</item>
+ </plurals>
+
+ <!--
+ Plurals used for the duration in free sms storage action and confirm strings above
+ Example: "Delete messages older than a week"
+ -->
+ <plurals name="week_count">
+ <!-- Singular case -->
+ <item quantity="one">a week</item>
+ <!-- Plural case -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> weeks</item>
+ </plurals>
+ <plurals name="month_count">
+ <!-- Singular case -->
+ <item quantity="one">a month</item>
+ <!-- Plural case -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> months</item>
+ </plurals>
+ <plurals name="year_count">
+ <!-- Singular case -->
+ <item quantity="one">a year</item>
+ <!-- Plural case -->
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> years</item>
+ </plurals>
+
+ <!-- Title of the class-0 message activity. -->
+ <string name="class_0_message_activity">Class 0 message</string>
+ <string name="save">Save</string>
+
+ <!-- Text for SMS storage low text, when auto delete is enabled -->
+ <string name="sms_storage_low_auto_delete_enabled_dialog_text">Device is low on space. Messaging will automatically delete older messages to free up space.</string>
+ <!-- Title for SMS storage low notification and dialog. Must match the framework's notification title, i.e. //frameworks/base/core/res/res/values/strings.xml#low_internal_storage_view_title -->
+ <string name="sms_storage_low_title">Storage space running out</string>
+ <!-- Text for SMS storage low notification and dialog -->
+ <string name="sms_storage_low_text">Messaging might not send or receive messages until more space is available on your device.</string>
+ <!-- Ticker for SMS storage low notification -->
+ <string name="sms_storage_low_notification_ticker">Low SMS storage. You may need to delete messages.</string>
+
+ <!-- Title for asking the user to enter their phone number -->
+ <string name="enter_phone_number_title">Confirm your phone number</string>
+ <!-- Text for asking the user to enter their phone number -->
+ <string name="enter_phone_number_text">This one-time step will ensure Messaging will deliver your group messages properly.</string>
+ <!-- EditText hint for asking the user to enter their phone number -->
+ <string name="enter_phone_number_hint">Phone number</string>
+
+ <!-- Free SMS storage actions -->
+ <string name="delete_all_media">Delete all messages with media</string>
+ <string name="delete_oldest_messages">Delete messages older than <xliff:g id="duration">%s</xliff:g></string>
+ <string name="auto_delete_oldest_messages">Auto-delete messages older than <xliff:g id="duration">%s</xliff:g></string>
+ <string name="ignore">Ignore</string>
+
+ <!-- Free SMS storage action confirm dialog message -->
+ <string name="delete_all_media_confirmation">Delete all messages with media?</string>
+ <string name="delete_oldest_messages_confirmation">Delete messages older than <xliff:g id="duration">%s</xliff:g>?</string>
+ <string name="auto_delete_oldest_messages_confirmation">Delete messages older than <xliff:g id="duration">%s</xliff:g> and turn on auto-delete?</string>
+
+ <!-- ####### Strings to support the mms/sms service ####### -->
+
+ <!-- Prefix for accessibility to indicate who the sender of a plain text message is. -->
+ <string name="incoming_text_sender_content_description"><xliff:g id="sender">%s</xliff:g> said</string>
+ <string name="outgoing_text_sender_content_description">You said</string>
+
+ <!-- Prefix for accessibility to indicate who the sender of text with hyperlinks, an attachment, or yet-to-be-downloaded message is. -->
+ <string name="incoming_sender_content_description">Message from <xliff:g id="sender">%s</xliff:g></string>
+ <string name="outgoing_sender_content_description">You sent a message</string>
+
+ <!-- While sending a message display this message. -->
+ <string name="message_status_sending">Sending&#8230;</string>
+ <!-- When sending a message failed display this message. -->
+ <string name="message_status_send_failed">Not sent. Touch to try again.</string>
+ <!-- When retrying sending for a message. -->
+ <string name="message_status_send_retrying">Not sent. Trying again&#8230;</string>
+ <!-- When showing resend action display this message. -->
+ <string name="message_status_resend">Resend or delete</string>
+
+ <!-- When sending a message to an emergency number failed display this message in the conversation. -->
+ <string name="message_status_send_failed_emergency_number">Please make a voice call to emergency services. Your text message could not be delivered at this time.</string>
+
+ <!-- When showing resend action, display this message. -->
+ <string name="message_status_failed">Failed</string>
+
+ <!-- Title line for an MMS requiring manual download. -->
+ <string name="message_title_manual_download">New MMS message to download</string>
+ <!-- Title line for an MMS auto-downloading. -->
+ <string name="message_title_downloading">New MMS message</string>
+ <!-- Title line for MMS which failed to download. -->
+ <string name="message_title_download_failed">Couldn\'t download</string>
+ <!-- Timestamp line for MMS which failed to download. -->
+ <string name="message_status_download_failed">Touch to try again</string>
+ <!-- Timestamp line for MMS which failed to download. -->
+ <string name="message_status_download">Touch to download</string>
+ <!-- Timestamp line for MMS which is selected. -->
+ <string name="message_status_download_action">Download or delete</string>
+ <!-- Timestamp line to display while downloading a message. -->
+ <string name="message_status_downloading">Downloading&#8230;</string>
+ <!-- Timestamp line for MMS for expired or invalid message. -->
+ <string name="message_status_download_error">Message expired or not available</string>
+ <!-- Display this info line with an MMS notification -->
+ <string name="mms_info">size: <xliff:g id="messageSize">%1$s</xliff:g>, expiration: <xliff:g id="messageExpire">%2$s</xliff:g></string>
+ <!-- While sending a message, if it has invalid recipient, display this message. -->
+ <string name="invalid_destination">Can\'t send. Recipient not valid.</string>
+ <!-- While sending a message, this error is expected to be generated when user does not have
+ MMS enabled on his account. [CHAR LIMIT=NONE] -->
+ <string name="service_not_activated">Service not activated on network</string>
+ <!-- If a message can't be sent because of a MMSC network problem, show this toast. [CHAR LIMIT=NONE] -->
+ <string name="service_network_problem">Couldn\'t send due to network problem</string>
+ <!-- If a message has expired and is no longer available on MMSC, show this toast. [CHAR LIMIT=NONE] -->
+ <string name="service_message_not_found">Message expired or not available</string>
+ <!-- Download Manager -->
+ <!-- When an error occurs downloading a new message and a subject isn't available, substitute
+ this default subject. -->
+ <string name="no_subject">(No subject)</string>
+ <!-- When an error occurs downloading a new message and the sender is unknown, substitute
+ this default text. -->
+ <string name="unknown_sender">Unknown sender</string>
+ <string name="delivered_status_content_description">Delivered</string>
+ <!-- When an error occurs downloading a new message, display this message.
+ An example: Download of message Wanna get pizza from dorkman was unsuccessful. -->
+ <string name="dl_failure_notification">Couldn\'t download message <xliff:g id="subject">%1$s</xliff:g> from <xliff:g id="from">%2$s</xliff:g>.</string>
+ <!-- When a database error occurs due to low memory this toast is shown. -->
+ <string name="low_memory">Couldn\'t complete database operation due to low memory</string>
+ <!-- notification line 1 for "there are some failed messages" -->
+ <string name="notification_send_failures_line1_singular">Message not sent</string>
+ <!-- notification line 1 for "there are some failed messages" -->
+ <string name="notification_send_failures_line1_plural">Some messages not sent in Messaging</string>
+ <plurals name="notification_send_failures">
+ <item quantity="one"><xliff:g id="messages">%d</xliff:g> messages in one conversation</item>
+ <item quantity="other"><xliff:g id="messages">%d</xliff:g> messages in <xliff:g id="conversations">%d</xliff:g> conversations</item>
+ </plurals>
+ <!-- notification line 1 for download failed -->
+ <string name="notification_download_failures_line1_singular">Message not downloaded</string>
+ <!-- notification line 1 for multiple downloads failed -->
+ <string name="notification_download_failures_line1_plural">Some messages not downloaded in Messaging</string>
+ <plurals name="notification_download_failures">
+ <item quantity="one"><xliff:g id="messages">%d</xliff:g> messages in one conversation</item>
+ <item quantity="other"><xliff:g id="messages">%d</xliff:g> messages in <xliff:g id="conversations">%d</xliff:g> conversations</item>
+ </plurals>
+ <!-- Notification title when emergency SMS (e.g. to 911) fails to send -->
+ <string name="notification_emergency_send_failure_line1">Message to <xliff:g id="number">%1$s</xliff:g> not sent</string>
+ <!-- Notification content when emergency SMS fails to send -->
+ <string name="notification_emergency_send_failure_line2">
+ Please make a voice call to emergency services. Your text message to
+ <xliff:g id="number">%1$s</xliff:g> could not be delivered at this time.
+ </string>
+ <!-- notification line 1 for new message notification on the lock screen -->
+ <plurals name="notification_new_messages">
+ <item quantity="one">New message</item>
+ <item quantity="other"><xliff:g id="messages">%d</xliff:g> new messages</item>
+ </plurals>
+
+ <!-- Text for starting a new conversation button in the compose UI -->
+ <string name="start_conversation">Start</string>
+
+ <string name="camera_error_opening">Camera not available</string>
+ <string name="camera_error_unknown">Camera not available</string>
+ <string name="camera_error_video_init_fail">Video capture not available</string>
+ <!-- Error message when we are unable to write a picture or video file because of an error writing to storage (usually because insufficient space) -->
+ <string name="camera_error_storage_fail">Can\'t save media</string>
+ <string name="camera_error_failure_taking_picture">Can\'t take picture</string>
+
+ <!-- Content description text for Back button on the action bar -->
+ <string name="back">Back</string>
+ <!-- Action menu title for showing archived conversations -->
+ <string name="action_menu_show_archived">Archived</string>
+ <!-- Action menu title for deleting selected conversations in conversation list -->
+ <string name="action_delete">Delete</string>
+ <!-- Action menu title for archiving selected conversations in conversation list -->
+ <string name="action_archive">Archive</string>
+ <!-- Action menu title for unarchiving selected conversations in conversation list -->
+ <string name="action_unarchive">Unarchive</string>
+ <!-- Action menu title for turning off notification for the selected conversations in conversation list -->
+ <string name="action_notification_off">Turn off notifications</string>
+ <!-- Action menu title for turning on notification for the selected conversations in conversation list -->
+ <string name="action_notification_on">Turn on notifications</string>
+ <!-- Action menu title for adding the contacts for the selected conversations in conversation list -->
+ <string name="action_add_contact">Add contact</string>
+ <!-- Action menu title for downloading failed message selected in conversation -->
+ <string name="action_download">Download</string>
+ <!-- Action menu title for sending failed message selected in conversation -->
+ <string name="action_send">Send</string>
+ <!-- Action menu title for deleting failed message selected in conversation -->
+ <string name="action_delete_message">Delete</string>
+
+ <string name="delete_message_confirmation_dialog_title">Delete this message?</string>
+ <string name="delete_message_confirmation_dialog_text">This action cannot be undone.</string>
+ <string name="delete_message_confirmation_button">Delete</string>
+
+ <!-- Alert dialog title to accept or decline deleting conversation(s). -->
+ <plurals name="delete_conversations_confirmation_dialog_title">
+ <item quantity="one">Delete this conversation?</item>
+ <item quantity="other">Delete these conversations?</item>
+ </plurals>
+
+ <!-- Alert dialog accept deleting this conversation button. -->
+ <string name="delete_conversation_confirmation_button">Delete</string>
+ <!-- Alert dialog decline deleting this conversation button. -->
+ <string name="delete_conversation_decline_button">Cancel</string>
+
+ <!-- Hint text for the recipient chips text box when it's empty -->
+ <string name="recipient_hint">To</string>
+ <!-- Action menu title for multiple selection in gallery image picker -->
+ <string name="action_multiselect">Select multiple images</string>
+ <!-- Action menu title for confirming multiple selection in gallery image picker -->
+ <string name="action_confirm_multiselect">Confirm selection</string>
+ <!-- Text for showing there are more items in the attachments than is shown (e.g. "+2") -->
+ <string name="attachment_more_items">+<xliff:g id="count">%d</xliff:g></string>
+ <!-- Failed to start recording audio -->
+ <string name="audio_recording_start_failed">Can\'t record audio. Try again.</string>
+ <!-- Failed to replay recorded audio -->
+ <string name="audio_recording_replay_failed">Can\'t play audio. Try again.</string>
+ <!-- Error occurred while recording audio -->
+ <string name="audio_recording_error"> Couldn\'t save audio. Try again.</string>
+ <!-- Hint text on the audio recorder that instructs user how to start recording -->
+ <string name="audio_picker_hint_text">Touch &amp; hold</string>
+
+ <!-- An enumeration comma for separating multiple names in notifications. [CHAR LIMIT=2] -->
+ <string name="enumeration_comma">,\u0020</string>
+ <!-- Separator between parts of a notification in each line of an inboxStyle notification. [CHAR LIMIT=2] -->
+ <string name="notification_separator">\u0020\u0020</string>
+ <!-- Separator between title and content in notification ticker -->
+ <string name="notification_ticker_separator">:\u0020</string>
+ <!-- Separator between title and content in notifications -->
+ <string name="notification_space_separator">\u0020\u0020</string>
+ <!-- Shown in notifications when there's a picture. [CHAR LIMIT=25] -->
+ <string name="notification_picture">Picture</string>
+ <!-- Shown in notifications when there's an audio clip. [CHAR LIMIT=25] -->
+ <string name="notification_audio">Audio clip</string>
+ <!-- Shown in notifications when there's a video. [CHAR LIMIT=25] -->
+ <string name="notification_video">Video</string>
+ <!-- Shown in notifications when there's a vcard. [CHAR LIMIT=25] -->
+ <string name="notification_vcard">Contact card</string>
+ <!-- Notification action label for download MMS when set to manual downloads. [CHAR LIMIT=15] -->
+ <string name="notification_download_mms">Download</string>
+ <!-- Notification action label for SMS reply (visible on wearable device only). [CHAR LIMIT=15] -->
+ <string name="notification_reply_via_sms">Reply via SMS</string>
+ <!-- Notification action label for MMS reply (visible on wearable device only). [CHAR LIMIT=15] -->
+ <string name="notification_reply_via_mms">Reply via MMS</string>
+ <!-- Notification voice reply prompt (visible on wearable device only). [CHAR LIMIT=15] -->
+ <string name="notification_reply_prompt">Reply</string>
+ <string name="ellipsis" translatable="false">...</string>
+ <!-- The bottom line on a wearable notification that shows how many participants in the conversation -->
+ <plurals name="wearable_participant_count">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> participant</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> participants</item>
+ </plurals>
+ <!-- Shown in the Android Wear conversation log as the sender for outgoing messages,
+ when we don't know the user's real name (e.g. no "Me" contact on the phone). -->
+ <string name="unknown_self_participant">Me</string>
+
+ <!-- The text of the toast/snack message shown when a contact is blocked -->
+ <string name="blocked_toast_message">Contact blocked &amp; archived</string>
+ <!-- The text of the toast/snack message shown when a contact is unblocked -->
+ <string name="unblocked_toast_message">Contact unblocked &amp; unarchived</string>
+ <!-- The text of the toast/snack message shown when the selected conversation(s) are archived.
+ Displays the number of converstions archived -->
+ <string name="archived_toast_message"><xliff:g id="count">%d</xliff:g> archived</string>
+ <!-- The text of the toast/snack message shown when the selected conversation(s) are unarchived.
+ Displays the number of conversations unarchived -->
+ <string name="unarchived_toast_message"><xliff:g id="count">%d</xliff:g> unarchived</string>
+ <!-- The text of the toast/snack message shown when a notifications turned off for selected conversations -->
+ <string name="notification_off_toast_message">Notifications turned off</string>
+ <!-- The text of the toast/snack message shown when a notifications turned on for selected conversations -->
+ <string name="notification_on_toast_message">Notifications turned on</string>
+
+ <!-- Toast shown when the user tries to send a message, and then sets Bugle as the default SMS app. -->
+ <string name="toast_after_setting_default_sms_app_for_message_send">All set. Touch Send again.</string>
+ <!-- Toast shown when the user successfully sets Bugle as the default SMS app. -->
+ <string name="toast_after_setting_default_sms_app">Messaging successfully set as the default SMS app.</string>
+
+ <!-- Accessibility : Content descriptinon for ImageButtons and ImageViews -->
+ <plurals name="attachment_preview_close_content_description">
+ <item quantity="one">Discard attachment</item>
+ <item quantity="other">Discard attachments</item>
+ </plurals>
+ <string name="audio_attachment_content_description">Audio attachment</string>
+ <string name="audio_play_content_description">Play audio attachment</string>
+ <string name="audio_pause_content_description">Pause</string>
+
+ <!-- Accessibility announcement for an incoming message -->
+ <string name="incoming_message_announcement">Message from <xliff:g id="sender">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>.</string>
+ <!-- Accessibility description for conversation list for 1:1 conversations -->
+ <string name="one_on_one_incoming_failed_message_prefix">Failed message from <xliff:g id="sender">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="one_on_one_incoming_successful_message_prefix">Message from <xliff:g id="sender">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="one_on_one_outgoing_draft_message_prefix">Unsent message to <xliff:g id="contact">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="one_on_one_outgoing_sending_message_prefix">Sending message to <xliff:g id="contact">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="one_on_one_outgoing_failed_message_prefix">Failed message to <xliff:g id="contact">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="one_on_one_outgoing_successful_message_prefix">Message to <xliff:g id="contact">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <!-- Accessibility description for conversation list for group conversations -->
+ <string name="group_incoming_failed_message_prefix">Failed message from <xliff:g id="sender">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>. <xliff:g id="groupInfo">%s</xliff:g>.</string>
+ <string name="group_incoming_successful_message_prefix">Message from <xliff:g id="sender">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>. <xliff:g id="groupInfo">%s</xliff:g>.</string>
+ <string name="group_outgoing_draft_message_prefix">Unsent message to <xliff:g id="group">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="group_outgoing_sending_message_prefix">Sending message to <xliff:g id="group">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="group_outgoing_failed_message_prefix">Failed message to <xliff:g id="group">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <string name="group_outgoing_successful_message_prefix">Message to <xliff:g id="group">%s</xliff:g>: <xliff:g id="message">%s</xliff:g>. Time: <xliff:g id="time">%s</xliff:g>.</string>
+ <!-- Accessibility description for conversation list for failed messages -->
+ <string name="failed_message_content_description">Failed message. Touch to retry.</string>
+
+ <!-- Accessibility name for a group conversation -->
+ <string name="group_conversation_description">Conversation with <xliff:g id="participants">%s</xliff:g></string>
+
+ <!-- Accessibility : text read on the small X icon on the subject editor -->
+ <string name="delete_subject_content_description">Delete subject</string>
+
+ <string name="camera_switch_to_video_mode">Capture video</string>
+ <string name="camera_switch_to_still_mode">Capture a still image</string>
+ <string name="camera_take_picture">Take picture</string>
+ <string name="camera_start_recording">Start recording video</string>
+ <string name="camera_switch_full_screen">Switch to full screen camera</string>
+ <string name="camera_switch_camera_facing">Switch between front and back camera</string>
+ <string name="camera_stop_recording">Stop recording and attach video</string>
+ <string name="camera_cancel_recording">Stop recording video</string>
+
+ <string name="photo_view_activity_title">Messaging photos</string>
+ <plurals name="photos_saved_to_album">
+ <item quantity="one"><xliff:g id="quantity">%d</xliff:g> photo saved to \"<xliff:g id="albumName">%s</xliff:g>\" album</item>
+ <item quantity="other"><xliff:g id="quantity">%d</xliff:g> photos saved to \"<xliff:g id="albumName">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="videos_saved_to_album">
+ <item quantity="one"><xliff:g id="quantity">%d</xliff:g> video saved to \"<xliff:g id="albumName">%s</xliff:g>\" album</item>
+ <item quantity="other"><xliff:g id="quantity">%d</xliff:g> videos saved to \"<xliff:g id="albumName">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_album">
+ <item quantity="one"><xliff:g id="quantity">%d</xliff:g> attachment saved to \"<xliff:g id="albumName">%s</xliff:g>\" album</item>
+ <item quantity="other"><xliff:g id="quantity">%d</xliff:g> attachments saved to \"<xliff:g id="albumName">%s</xliff:g>\" album</item>
+ </plurals>
+ <plurals name="attachments_saved_to_downloads">
+ <item quantity="one"><xliff:g id="quantity">%d</xliff:g> attachment saved to \"Downloads\"</item>
+ <item quantity="other"><xliff:g id="quantity">%d</xliff:g> attachments saved to \"Downloads\"</item>
+ </plurals>
+ <plurals name="attachments_saved">
+ <item quantity="one"><xliff:g id="quantity">%d</xliff:g> attachment saved</item>
+ <item quantity="other"><xliff:g id="quantity">%d</xliff:g> attachments saved</item>
+ </plurals>
+ <plurals name="attachment_save_error">
+ <item quantity="one">Couldn\'t save <xliff:g id="quantity">%d</xliff:g> attachment</item>
+ <item quantity="other">Couldn\'t save <xliff:g id="quantity">%d</xliff:g> attachments</item>
+ </plurals>
+ <!-- Description shown in the download manager for an attachment that the user manually saved -->
+ <string name="attachment_file_description">Saved MMS attachment</string>
+
+ <!-- Title for the preferences/settings activity -->
+ <string name="settings_activity_title">Settings</string>
+ <!-- Title for the archived conversations activity -->
+ <string name="archived_activity_title">Archived</string>
+ <!-- Action title: Close -->
+ <string name="action_close">Close</string>
+ <!-- Preference category: MMS messaging -->
+ <string name="mms_messaging_category_pref_title">MMS</string>
+ <!-- Preference category: Advanced -->
+ <string name="advanced_category_pref_title">Advanced</string>
+ <!-- Preference category: Debug -->
+ <string name="debug_category_pref_title">Debug</string>
+ <!-- Title for the preference for whether or to notify the user of new messages -->
+ <string name="notifications_enabled_pref_title">Notifications</string>
+ <!-- Title for the notification sound preference -->
+ <string name="notification_sound_pref_title">Sound</string>
+ <!-- What to display in the settings screen for Ringtone when the user chooses "silent" [CHAR LIMIT=100]-->
+ <string name="silent_ringtone">Silent</string>
+ <!-- Title for the preference for whether or not to vibrate with notifications -->
+ <string name="notification_vibrate_pref_title">Vibrate</string>
+ <string name="blocked_pref_title">Blocked</string>
+ <!-- Title for the preference for whether or not to request/show delivery reports for SMS -->
+ <string name="delivery_reports_pref_title">SMS delivery reports</string>
+ <string name="delivery_reports_pref_summary">Request a delivery report for each SMS you send</string>
+ <string name="auto_retrieve_mms_pref_title">Auto-retrieve</string>
+ <string name="auto_retrieve_mms_pref_summary">Automatically retrieve MMS messages</string>
+ <!-- Title for the preference for whether or not to auto-retrieve MMS when roaming -->
+ <string name="auto_retrieve_mms_when_roaming_pref_title">Roaming auto-retrieve</string>
+ <string name="auto_retrieve_mms_when_roaming_pref_summary">Automatically retrieve MMS when roaming</string>
+ <string name="group_mms_pref_title">Group messaging</string>
+ <string name="group_mms_pref_summary">Use MMS to send a single message when there are multiple recipients</string>
+ <!-- Preference title: SMS disabled (Messaging is not default SMS app) -->
+ <string name="sms_disabled_pref_title">Default SMS app</string>
+ <!-- Preference title: SMS enabled (Messaging is the default SMS app) -->
+ <string name="sms_enabled_pref_title">Default SMS app</string>
+ <!-- Preference summary: Current default SMS app -->
+ <string name="default_sms_app"><xliff:g id="app_name">%s</xliff:g></string>
+ <!-- Preference title: MMS phone number -->
+ <string name="mms_phone_number_pref_title">Your phone number</string>
+ <!-- Display text for phone number preference when the number is unknown -->
+ <string name="unknown_phone_number_pref_display_value">Unknown</string>
+ <!-- Title for the preference for playing a message send sound -->
+ <string name="send_sound_pref_title">Outgoing message sounds</string>
+
+ <string name="dump_sms_pref_title">Dump SMS</string>
+ <string name="dump_sms_pref_summary">Dump received SMS raw data into external storage file</string>
+ <string name="dump_mms_pref_title">Dump MMS</string>
+ <string name="dump_mms_pref_summary">Dump received MMS raw data into external storage file</string>
+ <!-- Strings for dump sms/mms dialog -->
+ <string name="email_sms_mms_dump_file_subject" translatable="false">SMS dump file</string>
+ <string name="email_sms_mms_dump_file_chooser_title" translatable="false">Email SMS dump</string>
+ <string name="load_sms_mms_from_dump_file_dialog_title" translatable="false">Load dump file</string>
+ <string name="email_sms_mms_from_dump_file_dialog_title" translatable="false">Email dump file</string>
+ <string name="wireless_alerts_title">Wireless alerts</string>
+
+ <!-- Title of long press menu on an individual message. -->
+ <string name="message_context_menu_title">Message options</string>
+ <!-- Menu item to copy message text -->
+ <string name="message_context_menu_copy_text">Copy text</string>
+ <!-- Title of long press menu for details about an individual message. -->
+ <string name="message_context_menu_view_details">View details</string>
+ <!-- Title of long press menu for deleting an individual message. -->
+ <string name="message_context_menu_delete_message">Delete</string>
+ <!-- Title of long press menu for forwarding an individual message. -->
+ <string name="message_context_menu_forward_message">Forward</string>
+ <!-- Title of "Message details" dialog -->
+ <string name="message_details_title">Message details</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="message_type_label">Type:\u0020</string>
+ <!-- "Type" value in "Message details" dialog -->
+ <string name="text_message">Text message</string>
+ <!-- "Type" value in "Message details" dialog -->
+ <string name="multimedia_message">Multimedia message</string>
+ <!-- Label in "Message details" dialog showing who sent the message. -->
+ <string name="from_label">From:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="to_address_label">To:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="sent_label">Sent:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="received_label">Received:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="subject_label">Subject:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="message_size_label">Size:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="priority_label">Priority:\u0020</string>
+ <!-- Label in "Message details" dialog -->
+ <string name="sim_label">SIM:\u0020</string>
+ <!-- "Priority" value in "Message details" dialog -->
+ <string name="priority_high">High</string>
+ <!-- "Priority" value in "Message details" dialog -->
+ <string name="priority_normal">Normal</string>
+ <!-- "Priority" value in "Message details" dialog -->
+ <string name="priority_low">Low</string>
+ <!-- SIM identified using the slot number. e.g. SIM 1 -->
+ <string name="sim_slot_identifier">SIM <xliff:g id="sim_slot_number">%s</xliff:g></string>
+ <!-- Sender to be used if the sender address has been hidden -->
+ <string name="hidden_sender_address">Hidden sender address</string>
+ <!-- Shows when the user attempts to send message while some attachment(s) haven't finished loading -->
+ <string name="cant_send_message_while_loading_attachments">Can\'t send message while loading attachments.</string>
+ <!-- Notifies the user when an attachment fails to load -->
+ <string name="fail_to_load_attachment">Can\'t load attachment. Try again.</string>
+ <!-- Shows when the user attempts to send message when there is no active subscription -->
+ <string name="cant_send_message_without_active_subscription">Network is not ready. Try again.</string>
+
+ <string name="chips_text_delete_button_content_description">Delete text</string>
+ <string name="numeric_text_keyboard_toggle_button_content_description">Switch between entering text and numbers</string>
+ <string name="add_more_participants_button_content_description">Add more participants</string>
+ <string name="confrim_participants_button_content_description">Confirm participants</string>
+ <string name="start_new_conversation">Start new conversation</string>
+ <!-- Content description for the checkbox when selecting an item in the gallery -->
+ <string name="gallery_checkbox_content_description">Select this item</string>
+ <string name="video_thumbnail_view_play_button_content_description">Play video</string>
+ <!-- Option menu item in the conversation view that lets user view list of conversation participants and change options -->
+ <string name="action_people_and_options">People &amp; options</string>
+ <!-- Option menu item in the conversation list view and conversation view that shows debug menu -->
+ <string name="action_debug_options">Debug</string>
+ <!-- Title for the "People and Options" activity -->
+ <string name="people_and_options_activity_title">People &amp; options</string>
+ <!-- Title for the general settings section in the "People and options" activity -->
+ <string name="general_settings_title">General</string>
+ <!-- Title for the list of participants in the "People and options" activity -->
+ <string name="participant_list_title">People in this conversation</string>
+ <!-- Title for the phone call button in the action bar that lets the user call a participant in an SMS conversation -->
+ <string name="action_call">Make a call</string>
+ <!-- The hint text shown in the message compose input box prompting the user to send a message -->
+ <string name="compose_message_view_hint_text">Send message</string>
+ <!-- The hint text shown in the message compose input box prompting the user to send a message when there are multiple SIMs. -->
+ <string name="compose_message_view_hint_text_multi_sim">Send message&lt;br/&gt;&lt;small&gt;from <xliff:g id="sim_name">%s</xliff:g>&lt;/small&gt;</string>
+ <!-- The hint text shown in the message compose input box when photo(s) is attached -->
+ <plurals name="compose_message_view_hint_text_photo">
+ <item quantity="one">Send photo</item>
+ <item quantity="other">Send photos</item>
+ </plurals>
+ <!-- The hint text shown in the message compose input box when audio recording(s) is attached -->
+ <plurals name="compose_message_view_hint_text_audio">
+ <item quantity="one">Send audio</item>
+ <item quantity="other">Send audios</item>
+ </plurals>
+ <!-- The hint text shown in the message compose input box when video(s) is attached -->
+ <plurals name="compose_message_view_hint_text_video">
+ <item quantity="one">Send video</item>
+ <item quantity="other">Send videos</item>
+ </plurals>
+ <!-- The hint text shown in the message compose input box when vcard(s) is attached -->
+ <plurals name="compose_message_view_hint_text_vcard">
+ <item quantity="one">Send contact card</item>
+ <item quantity="other">Send contact cards</item>
+ </plurals>
+ <!-- The hint text shown in the message compose input box when a single unknown attachment, or multiple different attachments are attached -->
+ <plurals name="compose_message_view_hint_text_attachments">
+ <item quantity="one">Send attachment</item>
+ <item quantity="other">Send attachments</item>
+ </plurals>
+ <!-- Announcement after the user adds or removes an attachment -->
+ <plurals name="attachment_changed_accessibility_announcement">
+ <item quantity="zero">No attachments selected</item>
+ <item quantity="one">One attachment ready to send</item>
+ <item quantity="other"><xliff:g id="attachment_count">%d</xliff:g> attachments ready to send</item>
+ </plurals>
+ <!-- Action menu title in the HelpAndFeedbackFragment to let users send feedback -->
+ <string name="menu_send_feedback">Send feedback</string>
+ <!-- Action menu title in the HelpAndFeedbackFragment to let users view the app in the Google Play Store -->
+ <string name="menu_view_in_store">View in Google Play Store</string>
+ <!-- Action menu title in the HelpAndFeedbackFragment to let users view the version of Bugle -->
+ <string name="menu_version_info">Version info</string>
+ <!-- The format of the subtitle of the help activity. -->
+ <string name="subtitle_format_for_version_number">Version %1$s</string>
+
+ <!-- Action menu title to let users view open source license -->
+ <string name="menu_license">Open source licenses</string>
+ <!-- Title for the per-conversation preference for whether or to notify the user of new messages -->
+ <string name="notifications_enabled_conversation_pref_title">Notifications</string>
+ <!-- Reached MMS message attachment limit -->
+ <string name="mms_attachment_limit_reached">Attachment limit reached</string>
+ <!-- Message attachment load failed -->
+ <string name="mms_attachment_load_failed">Failed to load attachment.</string>
+ <!-- Alert dialog title to accept or decline adding the given phone number to your contacts. -->
+ <string name="add_contact_confirmation_dialog_title">Add to Contacts?</string>
+ <!-- Alert dialog accept adding the given phone number to your contacts button. -->
+ <string name="add_contact_confirmation">Add Contact</string>
+ <!-- The text shown as a label before the message subject input box -->
+ <string name="compose_message_view_subject_hint_text">Subject</string>
+ <!-- The text shown as a label before the message subject input box -->
+ <string name="conversation_message_view_subject_text">Subject:\u0020</string>
+ <!-- When there's a subject in an mms, the subject + message are shown in a notification -->
+ <string name="notification_subject"><xliff:g id="subject_label">%s</xliff:g><xliff:g id="messageText">%s</xliff:g></string>
+
+ <!-- Text shown on contact VCard when it's being loaded -->
+ <string name="loading_vcard">Loading contact card</string>
+ <!-- Text shown on contact VCard when it failed to load -->
+ <string name="failed_loading_vcard">Couldn\'t load contact card</string>
+ <!-- Text shown on contact VCard promping the user to tap to view the contact -->
+ <string name="vcard_tap_hint">View contact card</string>
+ <!-- VCard display name for when there's more than one contact, e.g. "2 contact cards" -->
+ <plurals name="vcard_multiple_display_name">
+ <!-- quantity="one" is not needed by the app, but it keeps the translation pipeline happy. -->
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> contact</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> contacts</item>
+ </plurals>
+ <!-- Title for the activity that shows details of VCards -->
+ <string name="vcard_detail_activity_title">Contact cards</string>
+ <!-- VCard label for displaying birthdays -->
+ <string name="vcard_detail_birthday_label">Birthday</string>
+ <!-- VCard label for describing the Notes field of a contact. -->
+ <string name="vcard_detail_notes_label">Notes</string>
+ <!-- Title for the message forwarding dialog -->
+ <string name="forward_message_activity_title">Forward message</string>
+ <!-- Title for the message reply dialog -->
+ <string name="reply_activity_title">Reply</string>
+
+ <!-- String to show when eliding the number of participants in a conversation due to length (used when there is one extra) -->
+ <string name="plus_one">+1</string>
+
+ <!-- String to show when eliding the number of participants in a conversation due to length (used when there is more than one extra) -->
+ <string name="plus_n">+%d</string>
+
+ <!-- Toast error shown when SMS functionality is disabled -->
+ <string name="sms_disabled">SMS disabled</string>
+
+ <!-- Snackbar message that indicates users must set Messaging as their
+ default SMS application in order to send messages -->
+ <string name="requires_default_sms_app_to_send">To send, set Messaging as the default SMS app</string>
+
+ <!-- Snackbar message that indicates users must set Messaging as their default SMS application in order to perform the requested action. -->
+ <string name="requires_default_sms_app">Set Messaging as the default SMS app</string>
+
+ <!-- Snackbar button text to switch default SMS apps shown in snackbar. -->
+ <string name="requires_default_sms_change_button">Change</string>
+
+ <!-- Toast message that indicates uses must set Messaging as their
+ default SMS application in order to receive replies in the app. -->
+ <string name="recommend_set_default_sms_app">To receive messages, set Messaging as the default SMS app</string>
+
+ <!-- Toast message that indicates no preferred SIM was selected for sending the message -->
+ <string name="no_preferred_sim_selected">No preferred SIM selected for sending SMS messages</string>
+
+ <!-- Dialog message that indicates user must be granted permissions from the phone owner to
+ send SMS messages. -->
+ <string name="requires_sms_permissions_message">This app isn\'t allowed by the device owner.</string>
+ <!-- Dialog button text that closes the application. -->
+ <string name="requires_sms_permissions_close_button">OK</string>
+
+ <!-- Toast text when creating a conversation with more participants than MMS/SMS config permits -->
+ <string name="too_many_participants">Too many participants in a conversation</string>
+ <!-- Toast text when creating a conversation with an invalid participant -->
+ <plurals name="add_invalid_contact_error">
+ <item quantity="one">Invalid contact</item>
+ <item quantity="other">Invalid contacts</item>
+ </plurals>
+ <!-- Error toast message shown when a camera image failed to attach to the message -->
+ <string name="camera_media_failure">Couldn\'t load camera image</string>
+
+ <!-- A message prefix to indicate the message was sent by the user. -->
+ <string name="conversation_list_item_view_sent_from_you_prefix">You:\u0020</string>
+
+ <!-- A message prefix to indicate the message was sent by another user in a group conversation. -->
+ <string name="conversation_list_item_view_sent_from_other_prefix"><xliff:g id="firstNameOfSender">%s</xliff:g>:\u0020</string>
+
+ <!-- A message in conversation list view to indicate that the snipit text and preview is from an unsent draft. -->
+ <string name="conversation_list_item_view_draft_message">Draft</string>
+
+ <!-- String to show in the conversation list view when there are no messages. -->
+ <string name="conversation_list_empty_text">Once you start a new conversation, you’ll see it listed here</string>
+ <!-- String to show in the archived conversation list view when there are no messages. -->
+ <string name="archived_conversation_list_empty_text">Archived conversations appear here</string>
+ <!-- String to show in the conversation list view when we haven't synchronized the SMS database yet -->
+ <string name="conversation_list_first_sync_text">Loading conversations&#8230;</string>
+
+ <!-- Shown in conversation as the snippet when there is no text and an attached picture. [CHAR LIMIT=25] -->
+ <string name="conversation_list_snippet_picture">Picture</string>
+ <!-- Shown in conversation as the snippet when there is no text and an attached audio clip. [CHAR LIMIT=25] -->
+ <string name="conversation_list_snippet_audio_clip">Audio clip</string>
+ <!-- Shown in conversation as the snippet when there is no text and an attached video. [CHAR LIMIT=25] -->
+ <string name="conversation_list_snippet_video">Video</string>
+ <!-- Shown in conversation as the snippet when there is no text and an attached vcard. [CHAR LIMIT=25] -->
+ <string name="conversation_list_snippet_vcard">Contact card</string>
+
+ <!-- Indicates that the current message will be sent via MMS -->
+ <string name="mms_text">MMS</string>
+
+ <!-- The string we display with the snack bar notification (very similar to toast) to undo the action that the notification is about. -->
+ <string name="snack_bar_undo">Undo</string>
+ <string name="snack_bar_retry">Retry</string>
+
+ <!-- Hint text shown on the contact list when there's no contacts -->
+ <string name="contact_list_empty_text">Enter a contact name or phone number to start a new message</string>
+
+ <!-- Action menu title for blocking and archiving selected conversations in conversation list -->
+ <string name="action_block">Block</string>
+
+ <!-- Label of block contact item in People & Options for 1:1 conversation -->
+ <string name="block_contact_title">Block <xliff:g id="destination">%s</xliff:g></string>
+
+ <!-- Label of unblock contact item in People & Options for 1:1 conversation -->
+ <string name="unblock_contact_title">Unblock <xliff:g id="destination">%s</xliff:g></string>
+
+ <!-- Title of block confirmation dialog -->
+ <string name="block_confirmation_title">Block <xliff:g id="destination">%s</xliff:g>?</string>
+
+ <!-- Text describing what happens when user is blocked via the block actionbar action -->
+ <string name="block_confirmation_message">You\'ll continue to receive messages from this number but won\'t be notified anymore. This conversation will be archived.</string>
+
+ <!-- The title of the list of blocked contacts -->
+ <string name="blocked_contacts_title">Blocked contacts</string>
+
+ <!-- The message in a blocked contact item which indicates tapping will unblock -->
+ <string name="tap_to_unblock_message">UNBLOCK</string>
+
+ <!-- Action menu title for viewing the list of blocked contacts -->
+ <string name="view_blocked_contacts_title">Blocked contacts</string>
+
+ <!-- Content description for the button in gallery media picker to pick an image from the document library -->
+ <string name="pick_image_from_document_library_content_description">Choose image from document library</string>
+ <!-- Accessibility toast shown when a message is sending. -->
+ <string name="sending_message">Sending message</string>
+ <!-- Accessibility toast shown when a message is sent. -->
+ <string name="send_message_success">Message sent</string>
+ <!-- Error toast shown when an MMS could not be sent because mobile data was disabled. -->
+ <string name="send_message_failure_no_data">Cellular data is turned off. Check your settings.</string>
+ <!-- Error toast shown when a message could not be sent because of airplane mode. -->
+ <string name="send_message_failure_airplane_mode">Can\'t send messages in Airplane mode</string>
+ <!-- Error toast shown when a message could not be sent because of unspecified error. -->
+ <string name="send_message_failure">Message could not be sent</string>
+
+ <!-- Accessibility toast shown when an MMS is downloaded. -->
+ <string name="download_message_success">Message downloaded</string>
+ <!-- Error toast shown when an MMS could not be downloaded because mobile data was disabled. -->
+ <string name="download_message_failure_no_data">Cellular data is turned off. Check your settings.</string>
+ <!-- Error toast shown when a message could not be downloaded because of airplane mode. -->
+ <string name="download_message_failure_airplane_mode">Can\'t download messages in Airplane mode</string>
+ <!-- Error toast shown when a message could not be downloaded because of unspecified error. -->
+ <string name="download_message_failure">Message could not be downloaded</string>
+
+ <!-- Accessibility : content description for numbers -->
+ <string name="content_description_for_number_zero">Zero</string>
+ <string name="content_description_for_number_one">One</string>
+ <string name="content_description_for_number_two">Two</string>
+ <string name="content_description_for_number_three">Three</string>
+ <string name="content_description_for_number_four">Four</string>
+ <string name="content_description_for_number_five">Five</string>
+ <string name="content_description_for_number_six">Six</string>
+ <string name="content_description_for_number_seven">Seven</string>
+ <string name="content_description_for_number_eight">Eight</string>
+ <string name="content_description_for_number_nine">Nine</string>
+
+ <!-- Error message which shows the user a carrier specific error code such as "Unable to send message: xxxxx error 97" -->
+ <string name="carrier_send_error">Can\'t send message with <xliff:g id="carrierName">%1$s</xliff:g>, error <xliff:g id="errorCode">%2$d</xliff:g></string>
+ <string name="carrier_send_error_unknown_carrier">Can\'t send message with unknown carrier, error <xliff:g id="errorCode">%1$d</xliff:g></string>
+
+ <!-- Text prepended to the subject of a forwarded message -->
+ <string name="message_fwd">Fwd: <xliff:g id="subject">%s</xliff:g></string>
+
+ <!-- Detailed MMS failure strings, may need to be adjusted per carrier for TA -->
+ <string name="mms_failure_outgoing_service">Message not sent: service not activated on network</string>
+ <string name="mms_failure_outgoing_address">Message not sent: invalid destination address</string>
+ <string name="mms_failure_outgoing_corrupt">Message not sent: invalid message</string>
+ <string name="mms_failure_outgoing_content">Message not sent: unsupported content</string>
+ <string name="mms_failure_outgoing_unsupported">Message not sent: unsupported message</string>
+ <string name="mms_failure_outgoing_too_large">Message not sent: too large</string>
+
+ <!-- The notification toast text and action button label shown at bottom of screen, for when a new message comes in while the user
+ is in that conversation, but the message list is not scrolled to the bottom -->
+ <string name="in_conversation_notify_new_message_text">New message</string>
+ <string name="in_conversation_notify_new_message_action">View</string>
+
+ <!-- Content description for the message images in a conversation or for the image preview in the conversation list if the lastest message in that conversation contains an image-->
+ <string name="message_image_content_description">Image</string>
+
+ <!-- Text for a toast message notifying the user that we can't find an appropriate application to handle their request -->
+ <string name="activity_not_found_message">Could not find an appropriate application</string>
+
+ <string name="debug_sub_id_spinner_text" translatable="false">SUB Id</string>
+
+ <!-- Accessibility : Content description for deleting a recipient chip. -->
+ <string name="chips_delete_content_description">Remove recipient</string>
+
+ <!-- Label for new message button in share intent dialog -->
+ <string name="share_new_message">New message</string>
+
+ <!-- Label for new message button in share intent dialog -->
+ <string name="share_cancel">Cancel</string>
+
+ <!-- Screen title after user selects APNs setting option -->
+ <string name="apn_edit">Edit access point</string>
+ <!-- Edit access point label summary text when no value has been set -->
+ <string name="apn_not_set">Not set</string>
+ <!-- Edit access point labels: A label the user can give to the APN to allow him to differentiate it from the others -->
+ <string name="apn_name">Name</string>
+ <!-- Edit access point labels: The actual access point name-->
+ <string name="apn_apn">APN</string>
+ <!-- Edit access point labels: -->
+ <string name="apn_mmsc">MMSC</string>
+ <!-- Edit access point labels: The proxy to use for MMS (multimedia messages)-->
+ <string name="apn_mms_proxy">MMS proxy</string>
+ <!-- Edit access point labels: The port on the proxy used for MMS-->
+ <string name="apn_mms_port">MMS port</string>
+ <!-- Edit access point labels: -->
+ <string name="apn_mcc">MCC</string>
+ <!-- Edit access point labels: -->
+ <string name="apn_mnc">MNC</string>
+ <!-- Edit access point labels: The type of APN -->
+ <string name="apn_type">APN type</string>
+ <!-- Edit access point screen menu option to delete this APN -->
+ <string name="menu_delete_apn">Delete APN</string>
+ <!-- APNs screen menu option to create a brand spanking new APN -->
+ <string name="menu_new_apn">New APN</string>
+ <!-- Edit access point screen menu option to save the user's changes for this APN to the persistent storage -->
+ <string name="menu_save_apn">Save</string>
+ <!-- Edit access point screen menu option to discard the user's changes for this APN -->
+ <string name="menu_discard_apn_change">Discard</string>
+ <!-- APN error dialog messages: -->
+ <string name="error_apn_name_empty">The Name field can\'t be empty.</string>
+ <!-- APN error dialog messages: -->
+ <string name="error_apn_empty">The APN can\'t be empty.</string>
+ <!-- APN error dialog messages: -->
+ <string name="error_mcc_not3">MCC field must be 3 digits.</string>
+ <!-- APN error dialog messages: -->
+ <string name="error_mnc_not23">MNC field must be 2 or 3 digits.</string>
+ <!-- The message of dialog indicated restoring default APN settings in progress -->
+ <string name="restore_default_apn">Restoring default APN settings.</string>
+ <!-- APNs screen menu option to reset default APN settings -->
+ <string name="menu_restore_default_apn">Reset to default</string>
+ <!-- APNs screen toast message to inform reset default APN settings is completed -->
+ <string name="restore_default_apn_completed">Reset default APN settings completed.</string>
+ <!-- Name to assign to a Network Access Point that was saved without a name -->
+ <string name="untitled_apn">Untitled</string>
+ <!-- Title for Access Point Names settings -->
+ <string name="sms_apns_title">Access Point Names</string>
+ <!-- Title for Access Point Names settings activity -->
+ <string name="apn_settings">APNs</string>
+ <!-- APNs screen menu option to create a brand spanking new APN -->
+ <string name="menu_new">New APN</string>
+ <!-- Error message for users that aren't allowed to modify Access Point Names settings [CHAR LIMIT=none] -->
+ <string name="apn_settings_not_available">Access Point Name settings are not available for this user</string>
+ <!-- Alert dialog title to copy information displayed in dialog to system clipboard. -->
+ <string name="copy_to_clipboard_dialog_title">Copy to clipboard?</string>
+ <!-- Alert dialog accept to copy information displayed in dialog to system clipboard. -->
+ <string name="copy_to_clipboard">Copy</string>
+ <!-- SIM name incoming message bubbles indicating which SIM the message was delivered to -->
+ <string name="incoming_sim_name_text">to <xliff:g id="sim_name">%s</xliff:g></string>
+
+ <!-- The title for general settings section in the settings activity -->
+ <string name="general_settings">General</string>
+
+ <!-- The title for advanced settings section in the settings activity -->
+ <string name="advanced_settings">Advanced</string>
+
+ <!-- The title for general settings activity -->
+ <string name="general_settings_activity_title">General settings</string>
+
+ <!-- The title for advanced settings activity -->
+ <string name="advanced_settings_activity_title">Advanced settings</string>
+
+ <!-- The title for sim_specific settings -->
+ <string name="sim_specific_settings">\"<xliff:g id="sim_name">%s</xliff:g>\" SIM</string>
+
+ <!-- The group MMS choices in settings -->
+ <string name="disable_group_mms">Send individual SMS messages to all recipients. Only you will get any replies</string>
+ <string name="enable_group_mms">Send a single MMS to all recipients</string>
+
+ <!-- SIM number shown in the settings when the number is not known -->
+ <string name="sim_settings_unknown_number">Unknown number</string>
+
+ <!-- Notification title for incoming sms/mms message for secondary user -->
+ <string name="secondary_user_new_message_title">New message</string>
+ <!-- Ticker for notification for incoming sms/mms message for secondary user -->
+ <string name="secondary_user_new_message_ticker">New message.</string>
+
+ <!-- Content description for the SIM selector button in the conversation -->
+ <string name="sim_selector_button_content_description">SIM selector</string>
+ <!-- Content description for the SIM selector button with selected sim name -->
+ <string name="sim_selector_button_content_description_with_selection"><xliff:g id="sim">%1$s</xliff:g> selected, SIM selector</string>
+
+ <!-- Custom TalkBack description for send button long click event when there's no SIM selector, i.e. edit subject -->
+ <string name="send_button_long_click_description_no_sim_selector">Edit subject</string>
+
+ <!-- Custom TalkBack description for send button long click event when there is SIM selector -->
+ <string name="send_button_long_click_description_with_sim_selector">Select SIM or edit subject</string>
+
+ <!-- Content description for the audio record view -->
+ <string name="audio_record_view_content_description">Touch and hold to record audio</string>
+
+ <!-- Content description for new conversation button in desktop widget -->
+ <string name="widget_new_conversation_content_description">Start new conversation</string>
+ <!-- Content description for widget title -->
+ <string name="widget_title_content_description">Messaging</string>
+ <!-- Displayed name of the desktop widget that shows the list of conversations-->
+ <string name="widget_conversation_list_name">Messaging List</string>
+ <!-- Displayed name of the desktop widget that shows a single conversation-->
+ <string name="widget_conversation_name">Messaging</string>
+ <!-- Content description for new message button in desktop widget -->
+ <string name="widget_new_message_content_description">New message</string>
+ <!-- Content description for conversation list button in desktop widget -->
+ <string name="widget_conversation_list_content_description">Conversation list</string>
+ <!-- Shown when loading conversations in the widget -->
+ <string name="loading_conversations">Loading conversations</string>
+ <!-- Shown when loading messages in the widget -->
+ <string name="loading_messages">Loading messages</string>
+ <!-- Displayed at the end of the conversation list in the widget. Tapping on this will open bugle home activity. [CHAR LIMIT=35] -->
+ <string name="view_more_conversations">View more conversations</string>
+ <!-- Displayed at the beginning of the message list in the widget. Tapping on this will open bugle home activity. [CHAR LIMIT=35] -->
+ <string name="view_more_messages">View more messages</string>
+ <!-- Toast message telling the user a conversation has been deleted -->
+ <string name="conversation_deleted">Conversation deleted</string>
+ <!-- Displayed when user adds a new conversation widget. Tapping on the widget in this
+ mode will bring user to the conversation selection screen -->
+ <string name="tap_to_configure">Conversation deleted. Touch to show a different Messaging conversation</string>
+
+ <!-- Toast message telling the user that someone was blocked -->
+ <string name="update_destination_blocked">Blocked</string>
+ <!-- Toast message telling the user that someone was unblocked -->
+ <string name="update_destination_unblocked">Unblocked</string>
+ <string name="db_full">Storage space is low. Some data may be lost.</string>
+
+ <!-- Title for attachment chooser activity -->
+ <string name="attachment_chooser_activity_title">Select attachments</string>
+
+ <!-- Action menu title for confirming attachment selection in the attachment chooser -->
+ <string name="action_confirm_selection">Confirm selection</string>
+
+ <!-- Shows the number of selected attachments in the attachment chooser activity -->
+ <string name="attachment_chooser_selection"><xliff:g id="count">%d</xliff:g> selected</string>
+
+ <!-- The dialog message content for when the message is over the attachment limit when composing the message -->
+ <string name="attachment_limit_reached_dialog_message_when_composing">Please remove one or more attachments and try again.</string>
+
+ <!-- The dialog message content for when the message is over the attachment limit when sending the message -->
+ <string name="attachment_limit_reached_dialog_message_when_sending">You can try sending your message, but it might not be delivered unless you remove one or more attachments.</string>
+
+ <!-- The dialog message content for when the message is over the attachment limit when sending the message -->
+ <string name="video_attachment_limit_exceeded_when_sending">You can only send one video per message. Please remove additional videos and try again.</string>
+
+ <!-- The dialog message content for when the message load failed -->
+ <string name="attachment_load_failed_dialog_message">Messaging failed to load attachment.</string>
+
+ <!-- The option to send the message anyway when the user is presented with the warning that the attachment size might be over limit -->
+ <string name="attachment_limit_reached_send_anyway">Send anyway</string>
+
+ <!-- Toast message telling the user a conversation with a recipient could not be found or created -->
+ <string name="conversation_creation_failure">Could not start conversation</string>
+
+ <!-- When converting html to text link urls are inlined. This specifies how the link should be
+ displayed. For example, a link "click here" which navigates to "www.google.com" would have
+ text="click here" and url="www.google.com", using the below format to create "click here (www.google.com)" -->
+ <string name="link_display_format"><xliff:g id="text">%1$s</xliff:g> (<xliff:g id="url">%2$s</xliff:g>)</string>
+
+ <!-- The accessibility text read when the sim chooser pops up to read the current selected sim -->
+ <string name="selected_sim_content_message"><xliff:g id="selected_sim">%s</xliff:g> selected</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..d45f2e1
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,626 @@
+<?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>
+ <!--
+ Base application theme, dependent on API level. This theme is replaced
+ by BugleBaseTheme from res/values-vXX/styles.xml on newer devices.
+ -->
+ <style name="BugleBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!--
+ Theme customizations available in newer API levels can go in
+ res/values-vXX/styles.xml, while customizations related to
+ backward-compatibility can go here.
+ -->
+ </style>
+
+
+ <!-- Application theme. -->
+ <style name="BugleTheme" parent="BugleBaseTheme">
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+ <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="android:textColorHighlight">@color/text_highlight_color</item>
+ <item name="actionBarStyle">@style/BugleActionBar</item>
+ <item name="apnPreferenceStyle">@style/ApnPreference</item>
+ </style>
+
+ <style name="BugleTheme.ConversationActivity"
+ parent="@style/BugleTheme.ConversationActivityBase">
+ </style>
+
+ <style name="BugleTheme.ConversationActivityBase" parent="BugleTheme">
+ <item name="android:windowBackground">@color/conversation_background</item>
+ <item name="windowActionBarOverlay">true</item>
+ <item name="android:fastScrollPreviewBackgroundLeft">@drawable/contacts_fastscroll_label_left</item>
+ <item name="android:fastScrollPreviewBackgroundRight">@drawable/contacts_fastscroll_label_right</item>
+ </style>
+
+ <style name="BugleTheme.DialogActivity" parent="@style/Theme.AppCompat.Light.Dialog">
+ </style>
+
+ <style name="BugleTheme.ConversationListActivity" parent="BugleTheme">
+ <item name="android:windowBackground">@android:color/background_light</item>
+ </style>
+
+ <style name="BugleTheme.ArchivedConversationListActivity" parent="BugleTheme.ConversationListActivity">
+ <item name="colorPrimary">@color/archived_conversation_action_bar_background_color</item>
+ <item name="colorPrimaryDark">@color/archived_conversation_action_bar_background_color_dark</item>
+ </style>
+
+ <style name="BugleTheme.SettingsActivity" parent="BugleTheme">
+ <item name="android:textSize">@dimen/settings_list_text_size</item>
+ </style>
+
+ <style name="Invisible" 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>
+ <item name="android:windowNoDisplay">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>
+ <item name="titleTextStyle">@style/BugleActionBarTitleTextStyle</item>
+ </style>
+
+ <style name="BugleActionBarTitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">
+ <item name="android:textColor">@color/action_bar_title_text_color</item>
+ <item name="android:textSize">@dimen/action_bar_text_size</item>
+ <item name="android:lines">1</item>
+ </style>
+
+ <style name="ConversationActionBarTitleTextStyle" parent="BugleActionBarTitleTextStyle">
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="BuglePhotoViewTheme" parent="PhotoViewTheme.Translucent">
+ <item name="actionModeShareDrawable">@drawable/ic_share_light</item>
+ </style>
+
+ <style name="ConversationMessageText">
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="ConversationMessage" parent="ConversationMessageText">
+ <item name="android:textSize">@dimen/conversation_message_text_size</item>
+ <item name="android:textColor">@color/message_text</item>
+ </style>
+
+ <style name="ConversationMessageStatus" parent="ConversationMessageText">
+ <item name="android:textSize">@dimen/conversation_status_text_size</item>
+ </style>
+
+ <style name="ConversationMessageTitle" parent="ConversationMessageText">
+ <item name="android:textSize">@dimen/conversation_title_text_size</item>
+ <item name="android:background">@null</item>
+ <item name="android:textColor">@color/message_title_text</item>
+ </style>
+
+ <style name="ConversationMessageInfo" parent="ConversationMessageText">
+ <item name="android:textSize">@dimen/conversation_info_text_size</item>
+ <item name="android:textColor">@color/message_info_text</item>
+ </style>
+
+ <style name="ConversationComposeSendText">
+ <item name="android:textColor">@color/compose_send_text_color</item>
+ <item name="android:singleLine">false</item>
+ <item name="android:textSize">@dimen/conversation_compose_send_text_size</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:paddingLeft">@dimen/compose_message_text_box_padding_side</item>
+ <item name="android:paddingRight">@dimen/compose_message_text_box_padding_side</item>
+ <item name="android:minHeight">@dimen/conversation_message_contact_icon_size</item>
+ <item name="android:maxLines">4</item>
+ <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>
+ </style>
+
+ <style name="ConversationComposeSubjectText" parent="ConversationComposeSendText">
+ <item name="android:paddingLeft">18dp</item>
+ <item name="android:layout_marginLeft">@dimen/compose_message_subject_cancel_left_margin</item>
+ </style>
+
+ <style name="ContactListItem">
+ <item name="android:textSize">@dimen/contact_list_text_size</item>
+ <item name="android:textColor">@color/contact_list_text_primary</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="ContactListItemDetail">
+ <item name="android:textSize">@dimen/contact_list_detail_text_size</item>
+ <item name="android:textColor">@color/contact_list_text_secondary</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="ContactListItemDetailType" parent="ContactListItemDetail">
+ <item name="android:paddingLeft">12dp</item>
+ </style>
+
+ <style name="ContactListItemDrawableIndicator">
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="RecipientEditTextView">
+ <item name="android:textSize">@dimen/recipient_edit_text_size</item>
+ <item name="android:textColor">@color/compose_contact_text</item>
+ <item name="android:textColorHint">@color/compose_contact_faint_text</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:paddingLeft">4dp</item>
+ <item name="android:paddingRight">4dp</item>
+ </style>
+
+ <style name="PagerTabHeader">
+ <item name="android:textColor">@color/tab_text_color</item>
+ <item name="android:textSize">@dimen/pager_tab_header_text_size</item>
+ <item name="android:background">@null</item>
+ <item name="android:textStyle">bold</item>
+ </style>
+
+ <style name="ContactListAlphabetHeader">
+ <item name="android:textSize">@dimen/contact_list_alphabet_header_text_size</item>
+ <item name="android:textColor">@color/contact_list_alphabet_header</item>
+ </style>
+
+ <style name="AttachmentPreviewMoreItemsText">
+ <item name="android:textSize">@dimen/attachment_preview_more_items_text_size</item>
+ <item name="android:textColor">@android:color/white</item>
+ </style>
+
+ <style name="AudioPickerHintText">
+ <item name="android:textSize">@dimen/audio_picker_text_size</item>
+ <item name="android:textColor">@color/audio_picker_hint_text_color</item>
+ </style>
+
+ <style name="AudioPickerTimerText">
+ <item name="android:textSize">@dimen/audio_picker_text_size</item>
+ <item name="android:textColor">@color/audio_picker_timer_text_color</item>
+ </style>
+
+ <style name="AudioAttachmentTimerText">
+ <item name="android:textSize">@dimen/audio_attachment_text_size</item>
+ <item name="android:textColor">@color/audio_attachment_timer_text_color</item>
+ <item name="android:layout_marginRight">16dp</item>
+ </style>
+
+ <style name="AudioAttachmentViewStyle">
+ <item name="android:paddingRight">16dp</item>
+ <item name="android:paddingLeft">16dp</item>
+ </style>
+
+ <style name="VcardAttachmentSingleStyle">
+ <item name="android:paddingRight">@dimen/message_text_left_right_padding</item>
+ <item name="android:paddingLeft">@dimen/message_text_left_right_padding</item>
+ </style>
+
+ <style name="MessageVcardAttachmentStyle">
+ <item name="android:paddingRight">@dimen/message_text_left_right_padding</item>
+ <item name="android:paddingLeft">@dimen/message_text_left_right_padding</item>
+ </style>
+
+ <style name="NotificationSenderText">
+ <item name="android:textColor">@color/notification_sender_text</item>
+ </style>
+
+ <style name="NotificationSecondaryText">
+ <item name="android:textColor">@color/notification_secondary_text</item>
+ </style>
+
+ <style name="NotificationTertiaryText">
+ <item name="android:textColor">@color/notification_tertiary_text</item>
+ </style>
+
+ <style name="NotificationSubjectText">
+ <item name="android:textColor">@color/notification_subject_color</item>
+ </style>
+
+ <!-- Styles to support RTL for pre API 17 -->
+ <style name="AttachmentPreviewCloseButtonStyle">
+ <item name="android:gravity">left</item>
+ </style>
+
+ <style name="CameraChooserFrameStyle">
+ <item name="android:paddingLeft">16dp</item>
+ </style>
+
+ <style name="ComposeMessageViewAttachMediaButtonStyle">
+ <item name="android:paddingLeft">@dimen/compose_message_attachment_padding_sides</item>
+ <item name="android:paddingRight">@dimen/compose_message_attachment_padding_sides</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="ContactListItemViewStyle">
+ <item name="android:paddingLeft">16dp</item>
+ </style>
+
+ <style name="ContactListItemLinearLayoutStyle">
+ <item name="android:paddingLeft">12dp</item>
+ <item name="android:paddingRight">24dp</item>
+ </style>
+
+ <style name="ConversationListItemViewPaddingStyle">
+ <item name="android:paddingLeft">@dimen/conversation_list_item_view_padding</item>
+ <item name="android:paddingRight">@dimen/conversation_list_item_view_padding</item>
+ </style>
+
+ <style name="ConversationListItemViewTextStyle">
+ <item name="android:includeFontPadding">false</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:background">@null</item>
+ <item name="android:textColor">@color/conversation_list_details</item>
+ </style>
+
+ <style name="ConversationListItemViewConversationNameStyle" parent="ConversationListItemViewTextStyle">
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/conversation_list_name</item>
+ </style>
+
+ <style name="ConversationListNotificationBellPaddingStyle">
+ <item name="android:paddingRight">@dimen/conversation_list_notification_bell_padding</item>
+ </style>
+
+ <style name="ComposeMessageViewFrameLayoutStyle">
+ <item name="android:paddingRight">@dimen/compose_message_send_button_padding_right</item>
+ </style>
+
+ <style name="ConversationListFragmentStartNewButtonStyle">
+ <item name="android:layout_marginRight">@dimen/fab_left_right_margin</item>
+ </style>
+
+ <style name="GalleryGridItemViewCheckBoxStyle">
+ <item name="android:paddingRight">22dp</item>
+ </style>
+
+ <style name="AttachmentPreviewAttachmentStyle">
+ <item name="android:layout_marginLeft">10dp</item>
+ </style>
+
+ <style name="AudioAttachmentViewPlayPauseButtonStyle">
+ <item name="android:layout_marginRight">16dp</item>
+ </style>
+
+ <style name="ComposeMessageViewDraftViewStyle">
+ <item name="android:layout_marginLeft">53dp</item>
+ <item name="android:layout_marginRight">63dp</item>
+ </style>
+
+ <style name="ConversationMessageViewStyle">
+ <item name="android:paddingLeft">12dp</item>
+ <item name="android:paddingRight">12dp</item>
+ </style>
+
+ <style name="SubjectViewStyle">
+ <item name="android:paddingLeft">@dimen/compose_message_subject_left_padding</item>
+ <item name="android:paddingRight">@dimen/compose_message_subject_right_padding</item>
+ </style>
+
+ <style name="ParticipantListItem">
+ <item name="android:textSize">@dimen/participant_list_text_size</item>
+ <item name="android:textColor">@color/participant_list_text_primary</item>
+ <item name="android:background">@null</item>
+ <item name="android:layout_gravity">left|center_vertical</item>
+ </style>
+
+ <style name="AddContactConfirmationTextStyle" parent="ParticipantListItem">
+ <item name="android:paddingLeft">12dp</item>
+ <item name="android:paddingRight">24dp</item>
+ </style>
+
+ <style name="CopyContactDialogTextStyle" parent="ParticipantListItem">
+ <item name="android:paddingLeft">@dimen/copy_contact_dialog_left_padding</item>
+ <item name="android:paddingRight">@dimen/copy_contact_dialog_right_padding</item>
+ </style>
+
+ <style name="ParticipantListItemDetail">
+ <item name="android:textSize">@dimen/participant_list_detail_text_size</item>
+ <item name="android:textColor">@color/participant_list_text_secondary</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="PeopleListItemViewStyle">
+ <item name="android:paddingLeft">16dp</item>
+ </style>
+
+ <style name="PeopleAndOptionsItemStyle">
+ <item name="android:paddingRight">16dp</item>
+ <item name="android:paddingLeft">16dp</item>
+ </style>
+
+ <style name="PeopleAndOptionsSectionHeader">
+ <item name="android:textSize">@dimen/people_and_options_header_text_size</item>
+ <item name="android:textColor">@color/people_and_options_header_text</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="DropDownListViewStyle">
+ <item name="android:dividerHeight">0dp</item>
+ </style>
+
+ <style name="ComposeMessageViewTextCounterStyle">
+ <item name="android:textColor">@color/message_text_counter_color</item>
+ <item name="android:textSize">@dimen/message_text_counter_size</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:layout_gravity">right|center_vertical</item>
+ <item name="android:paddingRight">@dimen/compose_message_text_box_padding_side</item>
+ </style>
+
+ <style name="MmsIndicatorStyle">
+ <item name="android:textColor">@color/mms_indicator_color</item>
+ <item name="android:textSize">@dimen/mms_indicator_size</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:background">@null</item>
+ <item name="android:paddingRight">@dimen/compose_message_send_button_padding_right</item>
+ <item name="android:paddingTop">@dimen/compose_message_mms_indicator_padding_top</item>
+ <item name="android:layout_gravity">center_horizontal</item>
+ </style>
+
+ <style name="ListEmptyText">
+ <item name="android:textSize">@dimen/list_empty_text_size</item>
+ <item name="android:textColor">@color/list_empty_text</item>
+ </style>
+
+ <style name="LowStorageActionItemStyle">
+ <item name="android:textColor">@color/low_storage_action_item_color</item>
+ <item name="android:textSize">@dimen/low_storage_action_item_text_size</item>
+ <item name="android:singleLine">false</item>
+ </style>
+
+ <style name="SnackBarMessageWrapper">
+ <item name="android:layout_marginLeft">@dimen/snack_bar_left_right_margin</item>
+ <item name="android:layout_marginRight">0dp</item>
+ </style>
+
+ <style name="SnackBarText">
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">sans-serif</item>
+ </style>
+
+ <style name="SmsDeliverdBadge">
+ <item name="android:layout_marginLeft">4dp</item>
+ </style>
+
+ <style name="appTitleStyle">
+ <item name="android:textColor">@android:color/black</item>
+ <item name="android:textSize">20sp</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="appVersionStyle">
+ <item name="android:textColor">@android:color/black</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="appCopyrightStyle">
+ <item name="android:textColor">@android:color/black</item>
+ <item name="android:textSize">12sp</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="ChipDeleteIconStyle">
+ <item name="android:paddingLeft">8dp</item>
+ </style>
+
+ <style name="ChipIconStyle">
+ <item name="android:layout_marginRight">12dp</item>
+ </style>
+
+ <style name="SimSelectorItem">
+ <item name="android:textSize">@dimen/sim_selector_text_size</item>
+ <item name="android:textColor">@color/sim_selector_text_primary</item>
+ <item name="android:background">@null</item>
+ <item name="android:layout_gravity">right</item>
+ </style>
+
+ <style name="SimSelectorItemDetail">
+ <item name="android:textSize">@dimen/sim_selector_detail_text_size</item>
+ <item name="android:textColor">@color/sim_selector_text_secondary</item>
+ <item name="android:background">@null</item>
+ <item name="android:layout_gravity">right</item>
+ </style>
+
+ <style name="SimSelectorItemLinearLayoutStyle">
+ <item name="android:paddingRight">12dp</item>
+ </style>
+
+ <style name="MessageSimIndicator">
+ <item name="android:textSize">@dimen/conversation_status_text_size</item>
+ <item name="android:layout_marginLeft">4dp</item>
+ </style>
+
+ <style name="ColorAccentBlueOverrideStyle">
+ <item name="colorAccent">@color/action_bar_background_color</item>
+ </style>
+
+ <style name="ColorAccentGrayOverrideStyle">
+ <item name="colorAccent">@color/search_view_text_cursor</item>
+ </style>
+
+ <style name="ApnPreference">
+ <item name="android:layout">@layout/apn_preference_layout</item>
+ </style>
+
+ <style name="ApnPreferenceLayoutStyle">
+ <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:paddingRight">?android:attr/listPreferredItemPaddingEnd</item>
+ </style>
+
+ <style name="SettingsListItem">
+ <item name="android:textSize">@dimen/settings_list_text_size</item>
+ <item name="android:textColor">@color/settings_list_text_primary</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="SettingsListItemDetail">
+ <item name="android:textSize">@dimen/settings_list_detail_text_size</item>
+ <item name="android:textColor">@color/settings_list_text_secondary</item>
+ <item name="android:background">@null</item>
+ </style>
+
+ <style name="GroupMmsSettingItem">
+ <item name="android:textSize">@dimen/group_mms_setting_text_size</item>
+ <item name="android:textColor">@color/group_mms_setting_text_color</item>
+ <item name="android:layout_marginLeft">20dp</item>
+ <item name="android:layout_marginRight">20dp</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:paddingLeft">10dp</item>
+ <item name="android:paddingRight">10dp</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="DebugMmsConfigItemStyle">
+ <item name="android:paddingLeft">16dp</item>
+ <item name="android:paddingRight">16dp</item>
+ </style>
+
+ <style name="WidgetTitle" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
+ <item name="android:textColor">@color/action_bar_title_text_color</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:layout_marginEnd">8dp</item>
+ </style>
+
+ <style name="WidgetConversationTitle" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
+ <item name="android:textColor">@color/widget_conversation_title_color</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:layout_marginRight">8dp</item>
+ <item name="android:textSize">@dimen/widget_conversation_title_size</item>
+ <item name="android:shadowColor">#000000</item>
+ <item name="android:shadowDx">0</item>
+ <item name="android:shadowDy">2</item>
+ <item name="android:shadowRadius">1</item>
+ </style>
+
+ <style name="WidgetHeaderImage">
+ <item name="android:paddingStart">4dip</item>
+ </style>
+
+ <style name="WidgetConversationListItemAvatar">
+ <item name="android:layout_marginEnd">12dp</item>
+ <item name="android:layout_marginStart">8dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ </style>
+
+ <style name="WidgetConversationItemAvatarIncoming">
+ <item name="android:layout_marginStart">8dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ </style>
+
+ <style name="WidgetConversationItemAvatarOutgoing">
+ <item name="android:layout_marginRight">8dp</item>
+ <item name="android:layout_alignParentRight">true</item>
+ </style>
+
+ <style name="WidgetConversationItemBodyIncoming">
+ <item name="android:layout_toRightOf">@id/avatarFrame</item>
+ </style>
+
+ <style name="WidgetConversationItemBodyOutgoing">
+ <item name="android:layout_toLeftOf">@id/avatarFrame</item>
+ </style>
+
+ <style name="WidgetConversationItemIncoming">
+ <item name="android:layout_marginRight">5dp</item>
+ <item name="android:layout_alignParentLeft">true</item>
+ <item name="android:layout_toLeftOf">@id/date</item>
+ </style>
+
+ <style name="WidgetConversationItemIncomingAvatarShadow">
+ <item name="android:layout_marginLeft">4dp</item>
+ </style>
+
+ <style name="WidgetConversationItemOutgoingAvatarShadow">
+ <item name="android:layout_marginLeft">-4dp</item>
+ </style>
+
+ <style name="WidgetConversationAppIcon" parent="WidgetHeaderImage">
+ <item name="android:layout_marginLeft">-20dp</item>
+ </style>
+
+ <style name="WidgetConversationItemDate">
+ <item name="android:layout_marginRight">8dp</item>
+ <item name="android:layout_marginLeft">5dp</item>
+ <item name="android:layout_alignParentLeft">true</item>
+ </style>
+
+ <style name="WidgetConversationItemFailed">
+ <item name="android:layout_gravity">bottom|right</item>
+ </style>
+
+ <style name="WidgetConversationItemAttachment">
+ <item name="android:layout_gravity">bottom|right</item>
+ </style>
+
+ <style name="WidgetConversationItemWidget">
+ <item name="android:layout_marginRight">8dp</item>
+ <item name="android:layout_marginLeft">5dp</item>
+ <item name="android:layout_alignParentLeft">true</item>
+ </style>
+
+ <style name="WidgetConversationListItemBody">
+ <item name="android:layout_toRightOf">@id/avatarFrame</item>
+ </style>
+
+ <style name="WidgetConversationListItemFrom">
+ <item name="android:layout_marginRight">5dp</item>
+ <item name="android:layout_alignParentLeft">true</item>
+ </style>
+
+ <style name="WidgetConversationListItemDate">
+ <item name="android:layout_marginRight">8dp</item>
+ <item name="android:layout_marginLeft">5dp</item>
+ <item name="android:layout_gravity">right</item>
+ </style>
+
+ <style name="WidgetLoading">
+ <item name="android:layout_marginLeft">16dp</item>
+ </style>
+
+ <style name="WidgetConversationListItemStyle">
+ <item name="android:layout_height">80dp</item>
+ </style>
+
+ <style name="AttachmentGridItemViewCheckBoxStyle">
+ <item name="android:paddingRight">4dp</item>
+ </style>
+
+ <style name="PromoScreenTextStyle">
+ <item name="android:paddingLeft">40dp</item>
+ <item name="android:paddingRight">40dp</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:fontFamily">roboto-regular</item>
+ </style>
+ <style name="PromoScreenTextStyle.CenterAligned">
+ <item name="android:textAlignment">center</item>
+ </style>
+ <style name="PromoScreenButtonStyle">
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">roboto-medium</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:textColor">@android:color/white</item>
+ <item name="android:gravity">center</item>
+ </style>
+</resources>
diff --git a/res/values/versions.xml b/res/values/versions.xml
new file mode 100644
index 0000000..e82b79e
--- /dev/null
+++ b/res/values/versions.xml
@@ -0,0 +1,25 @@
+<?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>
+ <!-- DB version -->
+ <string name="database_version" translatable="false">1</string>
+
+ <!-- Version for shared preferences. This is used for handling prefs migration when old pref
+ keys are moved or renamed. You don't need to bump up the version number if you are just
+ adding/removing preferences -->
+ <string name="pref_version" translatable="false">1</string>
+</resources>
diff --git a/res/xml-mcc204-mnc04/mms_config.xml b/res/xml-mcc204-mnc04/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc204-mnc04/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc208-mnc01/mms_config.xml b/res/xml-mcc208-mnc01/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc208-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc208-mnc10/mms_config.xml b/res/xml-mcc208-mnc10/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc208-mnc10/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc208-mnc15/mms_config.xml b/res/xml-mcc208-mnc15/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc208-mnc15/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc208-mnc20/mms_config.xml b/res/xml-mcc208-mnc20/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc208-mnc20/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc208-mnc26/mms_config.xml b/res/xml-mcc208-mnc26/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc208-mnc26/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc214-mnc01/mms_config.xml b/res/xml-mcc214-mnc01/mms_config.xml
new file mode 100644
index 0000000..d6fad05
--- /dev/null
+++ b/res/xml-mcc214-mnc01/mms_config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - enable force 7 bit encoding
+ version 3 - removing v2 changes and move to correct location
+-->
+
+<mms_config version="2">
+ <!-- Disable SMS to MMS conversion for multiple recipient SMS -->
+ <bool name="enableGroupMms">false</bool>
+</mms_config>
diff --git a/res/xml-mcc214-mnc03/mms_config.xml b/res/xml-mcc214-mnc03/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc214-mnc03/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc214-mnc07/mms_config.xml b/res/xml-mcc214-mnc07/mms_config.xml
new file mode 100644
index 0000000..a90a26d
--- /dev/null
+++ b/res/xml-mcc214-mnc07/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">512000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc218-mnc05/mms_config.xml b/res/xml-mcc218-mnc05/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc218-mnc05/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc222-mnc01/mms_config.xml b/res/xml-mcc222-mnc01/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc222-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc222-mnc08/mms_config.xml b/res/xml-mcc222-mnc08/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc222-mnc08/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc234-mnc10/mms_config.xml b/res/xml-mcc234-mnc10/mms_config.xml
new file mode 100755
index 0000000..9f89f96
--- /dev/null
+++ b/res/xml-mcc234-mnc10/mms_config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+-->
+
+<mms_config version="1">
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">10</int>
+</mms_config>
diff --git a/res/xml-mcc234-mnc11 b/res/xml-mcc234-mnc11
new file mode 120000
index 0000000..e29c9d3
--- /dev/null
+++ b/res/xml-mcc234-mnc11
@@ -0,0 +1 @@
+xml-mcc234-mnc10 \ No newline at end of file
diff --git a/res/xml-mcc234-mnc15/mms_config.xml b/res/xml-mcc234-mnc15/mms_config.xml
new file mode 100755
index 0000000..9f89f96
--- /dev/null
+++ b/res/xml-mcc234-mnc15/mms_config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+-->
+
+<mms_config version="1">
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">10</int>
+</mms_config>
diff --git a/res/xml-mcc238-mnc02/mms_config.xml b/res/xml-mcc238-mnc02/mms_config.xml
new file mode 100644
index 0000000..6a27809
--- /dev/null
+++ b/res/xml-mcc238-mnc02/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">2097152</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc240-mnc01/mms_config.xml b/res/xml-mcc240-mnc01/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc240-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc240-mnc04/mms_config.xml b/res/xml-mcc240-mnc04/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc240-mnc04/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc240-mnc08/mms_config.xml b/res/xml-mcc240-mnc08/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc240-mnc08/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc240-mnc24/mms_config.xml b/res/xml-mcc240-mnc24/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc240-mnc24/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc242-mnc01/mms_config.xml b/res/xml-mcc242-mnc01/mms_config.xml
new file mode 100644
index 0000000..6a27809
--- /dev/null
+++ b/res/xml-mcc242-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">2097152</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc242-mnc02/mms_config.xml b/res/xml-mcc242-mnc02/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc242-mnc02/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc262-mnc07/mms_config.xml b/res/xml-mcc262-mnc07/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc262-mnc07/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc274-mnc02/mms_config.xml b/res/xml-mcc274-mnc02/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc274-mnc02/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc274-mnc03/mms_config.xml b/res/xml-mcc274-mnc03/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc274-mnc03/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc286-mnc01/mms_config.xml b/res/xml-mcc286-mnc01/mms_config.xml
new file mode 100644
index 0000000..a90a26d
--- /dev/null
+++ b/res/xml-mcc286-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">512000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc286-mnc03/mms_config.xml b/res/xml-mcc286-mnc03/mms_config.xml
new file mode 100644
index 0000000..a90a26d
--- /dev/null
+++ b/res/xml-mcc286-mnc03/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">512000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc294-mnc01/mms_config.xml b/res/xml-mcc294-mnc01/mms_config.xml
new file mode 100644
index 0000000..03ff16c
--- /dev/null
+++ b/res/xml-mcc294-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">102400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc294-mnc02/mms_config.xml b/res/xml-mcc294-mnc02/mms_config.xml
new file mode 100644
index 0000000..51af512
--- /dev/null
+++ b/res/xml-mcc294-mnc02/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">131072</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc294-mnc03/mms_config.xml b/res/xml-mcc294-mnc03/mms_config.xml
new file mode 100644
index 0000000..208a5a2
--- /dev/null
+++ b/res/xml-mcc294-mnc03/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">256000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc220/mms_config.xml b/res/xml-mcc302-mnc220/mms_config.xml
new file mode 100644
index 0000000..4267322
--- /dev/null
+++ b/res/xml-mcc302-mnc220/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">5</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc221/mms_config.xml b/res/xml-mcc302-mnc221/mms_config.xml
new file mode 100644
index 0000000..4267322
--- /dev/null
+++ b/res/xml-mcc302-mnc221/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">5</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc270/mms_config.xml b/res/xml-mcc302-mnc270/mms_config.xml
new file mode 100644
index 0000000..7f92daa
--- /dev/null
+++ b/res/xml-mcc302-mnc270/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc290/mms_config.xml b/res/xml-mcc302-mnc290/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc302-mnc290/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc320/mms_config.xml b/res/xml-mcc302-mnc320/mms_config.xml
new file mode 100644
index 0000000..0dc7956
--- /dev/null
+++ b/res/xml-mcc302-mnc320/mms_config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="1">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc370/mms_config.xml b/res/xml-mcc302-mnc370/mms_config.xml
new file mode 100644
index 0000000..7f92daa
--- /dev/null
+++ b/res/xml-mcc302-mnc370/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc490/mms_config.xml b/res/xml-mcc302-mnc490/mms_config.xml
new file mode 100644
index 0000000..7f92daa
--- /dev/null
+++ b/res/xml-mcc302-mnc490/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc500/mms_config.xml b/res/xml-mcc302-mnc500/mms_config.xml
new file mode 100644
index 0000000..0216774
--- /dev/null
+++ b/res/xml-mcc302-mnc500/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">3072000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc510/mms_config.xml b/res/xml-mcc302-mnc510/mms_config.xml
new file mode 100644
index 0000000..0216774
--- /dev/null
+++ b/res/xml-mcc302-mnc510/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">3072000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc520/mms_config.xml b/res/xml-mcc302-mnc520/mms_config.xml
new file mode 100644
index 0000000..21b15bb
--- /dev/null
+++ b/res/xml-mcc302-mnc520/mms_config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="1">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">3072000</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc610/mms_config.xml b/res/xml-mcc302-mnc610/mms_config.xml
new file mode 100644
index 0000000..576b5c1
--- /dev/null
+++ b/res/xml-mcc302-mnc610/mms_config.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">8</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- Disable SMS to MMS conversion for multiple recipient SMS -->
+ <bool name="enableGroupMms">false</bool>
+
+ <!-- Disable the link to the cell broadcast -->
+ <bool name="config_cellBroadcastAppLinks">false</bool>
+</mms_config>
diff --git a/res/xml-mcc302-mnc660/mms_config.xml b/res/xml-mcc302-mnc660/mms_config.xml
new file mode 100644
index 0000000..7f92daa
--- /dev/null
+++ b/res/xml-mcc302-mnc660/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc720/mms_config.xml b/res/xml-mcc302-mnc720/mms_config.xml
new file mode 100644
index 0000000..7f92daa
--- /dev/null
+++ b/res/xml-mcc302-mnc720/mms_config.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+</mms_config>
diff --git a/res/xml-mcc302-mnc780/mms_config.xml b/res/xml-mcc302-mnc780/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc302-mnc780/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc004/mms_config.xml b/res/xml-mcc310-mnc004/mms_config.xml
new file mode 100644
index 0000000..346218b
--- /dev/null
+++ b/res/xml-mcc310-mnc004/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1258291</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">Profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- enable alias -->
+ <bool name="aliasEnabled">true</bool>
+
+ <!-- alias rule: min chars -->
+ <int name="aliasMinChars">2</int>
+
+ <!-- alias rule: max chars -->
+ <int name="aliasMaxChars">48</int>
+
+ <!-- disable the option to attach an audio attachment to an Mms message. Currently Verizon
+ doesn't support OGG files and the only audio picker in the system allowed for picking
+ ringtones. All of our ringtones are OGG files so it doesn't make sense to allow a user
+ to select an audio file that eventually can't be handled. Therefore, the ability
+ to select a ringtone to send is disabled. -->
+ <bool name="allowAttachAudio">false</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">7</int>
+</mms_config>
+
diff --git a/res/xml-mcc310-mnc005/mms_config.xml b/res/xml-mcc310-mnc005/mms_config.xml
new file mode 100644
index 0000000..346218b
--- /dev/null
+++ b/res/xml-mcc310-mnc005/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1258291</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">Profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- enable alias -->
+ <bool name="aliasEnabled">true</bool>
+
+ <!-- alias rule: min chars -->
+ <int name="aliasMinChars">2</int>
+
+ <!-- alias rule: max chars -->
+ <int name="aliasMaxChars">48</int>
+
+ <!-- disable the option to attach an audio attachment to an Mms message. Currently Verizon
+ doesn't support OGG files and the only audio picker in the system allowed for picking
+ ringtones. All of our ringtones are OGG files so it doesn't make sense to allow a user
+ to select an audio file that eventually can't be handled. Therefore, the ability
+ to select a ringtone to send is disabled. -->
+ <bool name="allowAttachAudio">false</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">7</int>
+</mms_config>
+
diff --git a/res/xml-mcc310-mnc010/mms_config.xml b/res/xml-mcc310-mnc010/mms_config.xml
new file mode 100644
index 0000000..571bc07
--- /dev/null
+++ b/res/xml-mcc310-mnc010/mms_config.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- If this is true, The text message over 160 characters will be sent in separate,
+ independent messages after split into 160 character parts -->
+ <bool name="sendMultipartSmsAsSeparateMessages">true</bool>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- If true, need to read content_disposition field of an MMS part -->
+ <bool name="supportMmsContentDisposition">false</bool>
+</mms_config>
+
diff --git a/res/xml-mcc310-mnc026/mms_config.xml b/res/xml-mcc310-mnc026/mms_config.xml
new file mode 100644
index 0000000..91481f9
--- /dev/null
+++ b/res/xml-mcc310-mnc026/mms_config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- T-Mobile U.S. mcc=310, mnc=026 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc040/mms_config.xml b/res/xml-mcc310-mnc040/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc310-mnc040/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc070/mms_config.xml b/res/xml-mcc310-mnc070/mms_config.xml
new file mode 100755
index 0000000..e9404e8
--- /dev/null
+++ b/res/xml-mcc310-mnc070/mms_config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<!-- AT&T U.S. mcc=310, mnc=070 -->
+
+<mms_config version="1">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc090/mms_config.xml b/res/xml-mcc310-mnc090/mms_config.xml
new file mode 100755
index 0000000..0c8f8de
--- /dev/null
+++ b/res/xml-mcc310-mnc090/mms_config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- AT&T U.S. mcc=310, mnc=090 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc120/mms_config.xml b/res/xml-mcc310-mnc120/mms_config.xml
new file mode 100644
index 0000000..8c1ae11
--- /dev/null
+++ b/res/xml-mcc310-mnc120/mms_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">40</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI##</string>
+
+ <!-- Suffix to the NAI header (encoded together with base64) -->
+ <string name="naiSuffix">:pcs</string>
+
+ <!-- If true, need to read content_disposition field of an MMS part -->
+ <bool name="supportMmsContentDisposition">false</bool>
+</mms_config>
diff --git a/res/xml-mcc310-mnc130/mms_config.xml b/res/xml-mcc310-mnc130/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc310-mnc130/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc150/mms_config.xml b/res/xml-mcc310-mnc150/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc310-mnc150/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc170/mms_config.xml b/res/xml-mcc310-mnc170/mms_config.xml
new file mode 100644
index 0000000..6705cbc
--- /dev/null
+++ b/res/xml-mcc310-mnc170/mms_config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- T-Mobile U.S. mcc=310, mnc=170 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc260/mms_config.xml b/res/xml-mcc310-mnc260/mms_config.xml
new file mode 100644
index 0000000..9bf9d70
--- /dev/null
+++ b/res/xml-mcc310-mnc260/mms_config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- T-Mobile U.S. mcc=310, mnc=260 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc360/mms_config.xml b/res/xml-mcc310-mnc360/mms_config.xml
new file mode 100644
index 0000000..eb8da80
--- /dev/null
+++ b/res/xml-mcc310-mnc360/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1228800</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc380/mms_config.xml b/res/xml-mcc310-mnc380/mms_config.xml
new file mode 100755
index 0000000..0ea83d1
--- /dev/null
+++ b/res/xml-mcc310-mnc380/mms_config.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+-->
+<!-- AT&T U.S. mcc=310, mnc=380 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc410/mms_config.xml b/res/xml-mcc310-mnc410/mms_config.xml
new file mode 100755
index 0000000..4e42f3a
--- /dev/null
+++ b/res/xml-mcc310-mnc410/mms_config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- AT&T U.S. mcc=310, mnc=410 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc420/mms_config.xml b/res/xml-mcc310-mnc420/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc310-mnc420/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc450/mms_config.xml b/res/xml-mcc310-mnc450/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc310-mnc450/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc490/mms_config.xml b/res/xml-mcc310-mnc490/mms_config.xml
new file mode 100644
index 0000000..06da531
--- /dev/null
+++ b/res/xml-mcc310-mnc490/mms_config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- T-Mobile U.S. mcc=310, mnc=490 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc560/mms_config.xml b/res/xml-mcc310-mnc560/mms_config.xml
new file mode 100755
index 0000000..a12d640
--- /dev/null
+++ b/res/xml-mcc310-mnc560/mms_config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- AT&T U.S. mcc=310, mnc=560 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc580/mms_config.xml b/res/xml-mcc310-mnc580/mms_config.xml
new file mode 100644
index 0000000..eb8da80
--- /dev/null
+++ b/res/xml-mcc310-mnc580/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1228800</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc600/mms_config.xml b/res/xml-mcc310-mnc600/mms_config.xml
new file mode 100644
index 0000000..f76c945
--- /dev/null
+++ b/res/xml-mcc310-mnc600/mms_config.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+<!-- MCC:310 MNC:600 - New Cell Inc. dba Cellcom -->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">8388608</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Flag indicating whether MMS Delivery Reports UI option should be shown -->
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc680/mms_config.xml b/res/xml-mcc310-mnc680/mms_config.xml
new file mode 100755
index 0000000..b42fa02
--- /dev/null
+++ b/res/xml-mcc310-mnc680/mms_config.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- AT&T U.S. mcc=310, mnc=680 -->
+
+<mms_config version="3">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc750/mms_config.xml b/res/xml-mcc310-mnc750/mms_config.xml
new file mode 100644
index 0000000..eb8da80
--- /dev/null
+++ b/res/xml-mcc310-mnc750/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1228800</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc770/mms_config.xml b/res/xml-mcc310-mnc770/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc310-mnc770/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc310-mnc920/mms_config.xml b/res/xml-mcc310-mnc920/mms_config.xml
new file mode 100644
index 0000000..4319cb0
--- /dev/null
+++ b/res/xml-mcc310-mnc920/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1258291</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc310-mnc980/mms_config.xml b/res/xml-mcc310-mnc980/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc310-mnc980/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc311-mnc012/mms_config.xml b/res/xml-mcc311-mnc012/mms_config.xml
new file mode 100644
index 0000000..346218b
--- /dev/null
+++ b/res/xml-mcc311-mnc012/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1258291</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">Profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- enable alias -->
+ <bool name="aliasEnabled">true</bool>
+
+ <!-- alias rule: min chars -->
+ <int name="aliasMinChars">2</int>
+
+ <!-- alias rule: max chars -->
+ <int name="aliasMaxChars">48</int>
+
+ <!-- disable the option to attach an audio attachment to an Mms message. Currently Verizon
+ doesn't support OGG files and the only audio picker in the system allowed for picking
+ ringtones. All of our ringtones are OGG files so it doesn't make sense to allow a user
+ to select an audio file that eventually can't be handled. Therefore, the ability
+ to select a ringtone to send is disabled. -->
+ <bool name="allowAttachAudio">false</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">7</int>
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc070/mms_config.xml b/res/xml-mcc311-mnc070/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc311-mnc070/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc100/mms_config.xml b/res/xml-mcc311-mnc100/mms_config.xml
new file mode 100755
index 0000000..dd671f6
--- /dev/null
+++ b/res/xml-mcc311-mnc100/mms_config.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+<!--Nex-Tech Wireless configuration-->
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">-1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Flag indicating whether SMS Delivery Reports UI option should be shown -->
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <!-- Flag indicating whether MMS Delivery Reports UI option should be shown -->
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc180/mms_config.xml b/res/xml-mcc311-mnc180/mms_config.xml
new file mode 100644
index 0000000..891618b
--- /dev/null
+++ b/res/xml-mcc311-mnc180/mms_config.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">10</int>
+</mms_config>
diff --git a/res/xml-mcc311-mnc220/mms_config.xml b/res/xml-mcc311-mnc220/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc220/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc221/mms_config.xml b/res/xml-mcc311-mnc221/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc221/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc222/mms_config.xml b/res/xml-mcc311-mnc222/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc222/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc223/mms_config.xml b/res/xml-mcc311-mnc223/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc223/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc224/mms_config.xml b/res/xml-mcc311-mnc224/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc224/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc225/mms_config.xml b/res/xml-mcc311-mnc225/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc225/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc226/mms_config.xml b/res/xml-mcc311-mnc226/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc226/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc227/mms_config.xml b/res/xml-mcc311-mnc227/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc227/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc228/mms_config.xml b/res/xml-mcc311-mnc228/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc228/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc229/mms_config.xml b/res/xml-mcc311-mnc229/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc229/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc230/mms_config.xml b/res/xml-mcc311-mnc230/mms_config.xml
new file mode 100755
index 0000000..b82f1d6
--- /dev/null
+++ b/res/xml-mcc311-mnc230/mms_config.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+<!--C Spire configuration-->
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">-1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Flag indicating whether SMS Delivery Reports UI option should be shown -->
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <!-- Flag indicating whether MMS Delivery Reports UI option should be shown -->
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request. -->
+ <string name="httpParams">X-CS3G-MDN: 1##LINE1NOCOUNTRYCODE##</string>
+
+</mms_config>
diff --git a/res/xml-mcc311-mnc340/mms_config.xml b/res/xml-mcc311-mnc340/mms_config.xml
new file mode 100644
index 0000000..eb8da80
--- /dev/null
+++ b/res/xml-mcc311-mnc340/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1228800</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc370/mms_config.xml b/res/xml-mcc311-mnc370/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc311-mnc370/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc311-mnc410/mms_config.xml b/res/xml-mcc311-mnc410/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc311-mnc410/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc430/mms_config.xml b/res/xml-mcc311-mnc430/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc311-mnc430/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc440/mms_config.xml b/res/xml-mcc311-mnc440/mms_config.xml
new file mode 100644
index 0000000..0f26fab
--- /dev/null
+++ b/res/xml-mcc311-mnc440/mms_config.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1228800</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Flag indicating whether MMS Delivery Reports UI option should be shown -->
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc480/mms_config.xml b/res/xml-mcc311-mnc480/mms_config.xml
new file mode 100644
index 0000000..1929b6a
--- /dev/null
+++ b/res/xml-mcc311-mnc480/mms_config.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <!-- TODO: check if this is correct for Passion -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1258291</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">Profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-up-calling-line-id: 1##LINE1NOCOUNTRYCODE##|X-VzW-MDN: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <!-- enable alias -->
+ <bool name="aliasEnabled">true</bool>
+
+ <!-- alias rule: min chars -->
+ <int name="aliasMinChars">2</int>
+
+ <!-- alias rule: max chars -->
+ <int name="aliasMaxChars">48</int>
+
+ <!-- disable the option to attach an audio attachment to an Mms message. Currently Verizon
+ doesn't support OGG files and the only audio picker in the system allowed for picking
+ ringtones. All of our ringtones are OGG files so it doesn't make sense to allow a user
+ to select an audio file that eventually can't be handled. Therefore, the ability
+ to select a ringtone to send is disabled. -->
+ <bool name="allowAttachAudio">false</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">7</int>
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc490/mms_config.xml b/res/xml-mcc311-mnc490/mms_config.xml
new file mode 100644
index 0000000..8c1ae11
--- /dev/null
+++ b/res/xml-mcc311-mnc490/mms_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">40</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI##</string>
+
+ <!-- Suffix to the NAI header (encoded together with base64) -->
+ <string name="naiSuffix">:pcs</string>
+
+ <!-- If true, need to read content_disposition field of an MMS part -->
+ <bool name="supportMmsContentDisposition">false</bool>
+</mms_config>
diff --git a/res/xml-mcc311-mnc580/mms_config.xml b/res/xml-mcc311-mnc580/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc580/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc581/mms_config.xml b/res/xml-mcc311-mnc581/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc581/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc582/mms_config.xml b/res/xml-mcc311-mnc582/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc582/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc583/mms_config.xml b/res/xml-mcc311-mnc583/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc583/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc584/mms_config.xml b/res/xml-mcc311-mnc584/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc584/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc585/mms_config.xml b/res/xml-mcc311-mnc585/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc585/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc586/mms_config.xml b/res/xml-mcc311-mnc586/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc586/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc587/mms_config.xml b/res/xml-mcc311-mnc587/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc587/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc588/mms_config.xml b/res/xml-mcc311-mnc588/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc588/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc589/mms_config.xml b/res/xml-mcc311-mnc589/mms_config.xml
new file mode 100644
index 0000000..196d4f7
--- /dev/null
+++ b/res/xml-mcc311-mnc589/mms_config.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="2">
+ <!-- In case of single segment M-Notification.ind, this indicates whether
+ TransactionID should be appended to URI or not. -->
+ <bool name="enabledTransID">true</bool>
+
+ <!-- Send M-Acknowledge.ind and M-NotifyResp.ind to the URL received by the
+ M-Notification.ind (WAP-PUSH). -->
+ <bool name="enabledNotifyWapMMSC">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- UAProf parameter tag name -->
+ <string name="uaProfTagName">x-wap-profile</string>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- Additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">x-vzw-mdn: 1##LINE1NOCOUNTRYCODE##</string>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message or separate, independent SMS messages,
+ dependent on sendMultipartSMSAsSeparateMessages value. -->
+ <bool name="enableMultipartSMS">false</bool>
+
+ <!-- Email Gateway Number -->
+ <!-- TODO: check if this is correct for Passion -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="allowAttachAudio">true</bool>
+
+ <!-- Hide the MMS read reports UI in settings -->
+ <bool name="enableMMSReadReports">false</bool>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+</mms_config>
+
diff --git a/res/xml-mcc311-mnc590/mms_config.xml b/res/xml-mcc311-mnc590/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc311-mnc590/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc311-mnc870/mms_config.xml b/res/xml-mcc311-mnc870/mms_config.xml
new file mode 100644
index 0000000..8c1ae11
--- /dev/null
+++ b/res/xml-mcc311-mnc870/mms_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">40</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI##</string>
+
+ <!-- Suffix to the NAI header (encoded together with base64) -->
+ <string name="naiSuffix">:pcs</string>
+
+ <!-- If true, need to read content_disposition field of an MMS part -->
+ <bool name="supportMmsContentDisposition">false</bool>
+</mms_config>
diff --git a/res/xml-mcc312-mnc160/mms_config.xml b/res/xml-mcc312-mnc160/mms_config.xml
new file mode 100644
index 0000000..9622857
--- /dev/null
+++ b/res/xml-mcc312-mnc160/mms_config.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">2432</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">4320</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">500</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">50</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">20</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##</string>
+</mms_config>
diff --git a/res/xml-mcc312-mnc530/mms_config.xml b/res/xml-mcc312-mnc530/mms_config.xml
new file mode 100755
index 0000000..7b1b093
--- /dev/null
+++ b/res/xml-mcc312-mnc530/mms_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<mms_config version="1">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">40</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">6</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">false</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Maximum length in chars of mms subject field -->
+ <int name="maxSubjectLength">80</int>
+
+ <!-- Email Gateway Number -->
+ <string name="emailGatewayNumber">6245</string>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional. -->
+ <string name="httpParams">X-MDN: ##LINE1##|Proxy-Authorization: Basic ##NAI##</string>
+
+ <!-- Suffix to the NAI header (encoded together with base64) -->
+ <string name="naiSuffix">:pcs</string>
+
+ <!-- If true, need to read content_disposition field of an MMS part -->
+ <bool name="supportMmsContentDisposition">false</bool>
+</mms_config>
diff --git a/res/xml-mcc334-mnc020/mms_config.xml b/res/xml-mcc334-mnc020/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc334-mnc020/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc418-mnc05/mms_config.xml b/res/xml-mcc418-mnc05/mms_config.xml
new file mode 100644
index 0000000..03ff16c
--- /dev/null
+++ b/res/xml-mcc418-mnc05/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">102400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc418-mnc20/mms_config.xml b/res/xml-mcc418-mnc20/mms_config.xml
new file mode 100644
index 0000000..9ef0dd8
--- /dev/null
+++ b/res/xml-mcc418-mnc20/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">153600</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc418-mnc30/mms_config.xml b/res/xml-mcc418-mnc30/mms_config.xml
new file mode 100644
index 0000000..9ef0dd8
--- /dev/null
+++ b/res/xml-mcc418-mnc30/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">153600</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc420-mnc04/mms_config.xml b/res/xml-mcc420-mnc04/mms_config.xml
new file mode 100644
index 0000000..614ba8e
--- /dev/null
+++ b/res/xml-mcc420-mnc04/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">409600</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc440-mnc10/mms_config.xml b/res/xml-mcc440-mnc10/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc440-mnc10/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc440-mnc20/mms_config.xml b/res/xml-mcc440-mnc20/mms_config.xml
new file mode 100644
index 0000000..7f383c4
--- /dev/null
+++ b/res/xml-mcc440-mnc20/mms_config.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<!-- Softbank J.P. mcc=440, mnc=020 -->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">307200</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Maximum number of SMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultSMSMessagesPerThread">10000</int>
+
+ <!-- Maximum number of MMS message to save per thread before auto-delete kicks in.
+ This is the default value. -->
+ <int name="defaultMMSMessagesPerThread">1000</int>
+
+ <!-- Minimum value for the number of messages kept per conversation. The user can never
+ set the limit below this value. -->
+ <int name="minMessageCountPerThread">10</int>
+
+ <!-- Maximum value for the number of messages kept per conversation. The user can never
+ set the limit above this value. -->
+ <int name="maxMessageCountPerThread">5000</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">-1</int>
+
+ <!-- If true, The mms support slide duration.
+ If false, The mms does not support slide duration and we have to
+ set duration value. -->
+ <bool name="enableSlideDuration">true</bool>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- Set to true to add links to Cell Broadcast app from Settings and MMS app. -->
+ <bool name="config_cellBroadcastAppLinks">false</bool>
+</mms_config>
diff --git a/res/xml-mcc450-mnc00/mms_config.xml b/res/xml-mcc450-mnc00/mms_config.xml
new file mode 100644
index 0000000..96e5b9d
--- /dev/null
+++ b/res/xml-mcc450-mnc00/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc450-mnc02/mms_config.xml b/res/xml-mcc450-mnc02/mms_config.xml
new file mode 100644
index 0000000..2a7231c
--- /dev/null
+++ b/res/xml-mcc450-mnc02/mms_config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+</mms_config>
diff --git a/res/xml-mcc450-mnc05/mms_config.xml b/res/xml-mcc450-mnc05/mms_config.xml
new file mode 100644
index 0000000..2a7231c
--- /dev/null
+++ b/res/xml-mcc450-mnc05/mms_config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+</mms_config>
diff --git a/res/xml-mcc450-mnc06/mms_config.xml b/res/xml-mcc450-mnc06/mms_config.xml
new file mode 100644
index 0000000..5123984
--- /dev/null
+++ b/res/xml-mcc450-mnc06/mms_config.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- SMS text length threshold (in bytes) to turn into MMS -->
+ <int name="smsToMmsTextLengthThreshold">80</int>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+</mms_config>
diff --git a/res/xml-mcc450-mnc08/mms_config.xml b/res/xml-mcc450-mnc08/mms_config.xml
new file mode 100644
index 0000000..2a7231c
--- /dev/null
+++ b/res/xml-mcc450-mnc08/mms_config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">1048576</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <bool name="enableMMSReadReports">false</bool>
+
+ <bool name="enableSMSDeliveryReports">false</bool>
+
+ <bool name="enableMMSDeliveryReports">false</bool>
+</mms_config>
diff --git a/res/xml-mcc505-mnc01/mms_config.xml b/res/xml-mcc505-mnc01/mms_config.xml
new file mode 100644
index 0000000..6a27809
--- /dev/null
+++ b/res/xml-mcc505-mnc01/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">2097152</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc530-mnc05/mms_config.xml b/res/xml-mcc530-mnc05/mms_config.xml
new file mode 100644
index 0000000..fabcf3c
--- /dev/null
+++ b/res/xml-mcc530-mnc05/mms_config.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+
+ <!-- Content-Disposition header is not supported -->
+ <bool name="supportMmsContentDisposition">false</bool>
+</mms_config>
diff --git a/res/xml-mcc604-mnc00/mms_config.xml b/res/xml-mcc604-mnc00/mms_config.xml
new file mode 100644
index 0000000..03ff16c
--- /dev/null
+++ b/res/xml-mcc604-mnc00/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">102400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc604-mnc02/mms_config.xml b/res/xml-mcc604-mnc02/mms_config.xml
new file mode 100644
index 0000000..03ff16c
--- /dev/null
+++ b/res/xml-mcc604-mnc02/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">102400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-mcc647-mnc10/mms_config.xml b/res/xml-mcc647-mnc10/mms_config.xml
new file mode 100644
index 0000000..486c468
--- /dev/null
+++ b/res/xml-mcc647-mnc10/mms_config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2013 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">614400</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">1944</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">2592</int>
+</mms_config>
diff --git a/res/xml-v21/preferences_application.xml b/res/xml-v21/preferences_application.xml
new file mode 100644
index 0000000..5d8ee4c
--- /dev/null
+++ b/res/xml-v21/preferences_application.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+<!-- Preference screen definition for Bugle's application-wide settings -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- KLP+ only -->
+ <PreferenceScreen
+ android:key="@string/sms_disabled_pref_key"
+ android:title="@string/sms_disabled_pref_title"
+ android:persistent="false">
+ <intent
+ android:action="android.provider.Telephony.ACTION_CHANGE_DEFAULT">
+ <extra android:name="package" android:value="com.android.messaging" />
+ </intent>
+ </PreferenceScreen>
+ <PreferenceScreen
+ android:key="@string/sms_enabled_pref_key"
+ android:title="@string/sms_enabled_pref_title"
+ android:persistent="false">
+ <intent
+ android:action="android.settings.WIRELESS_SETTINGS">
+ </intent>
+ </PreferenceScreen>
+
+ <SwitchPreference
+ android:key="@string/send_sound_pref_key"
+ android:title="@string/send_sound_pref_title"
+ android:defaultValue="@bool/send_sound_pref_default"
+ android:persistent="true" />
+
+ <SwitchPreference
+ android:key="@string/notifications_enabled_pref_key"
+ android:title="@string/notifications_enabled_pref_title"
+ android:defaultValue="@bool/notifications_enabled_pref_default"
+ android:persistent="true"
+ android:disableDependentsState="false" />
+
+ <RingtonePreference
+ android:key="@string/notification_sound_pref_key"
+ android:title="@string/notification_sound_pref_title"
+ android:ringtoneType="notification"
+ android:showSilent="true"
+ android:showDefault="true"
+ android:persistent="true"
+ android:defaultValue=""
+ android:dependency="@string/notifications_enabled_pref_key" />
+
+ <SwitchPreference
+ android:key="@string/notification_vibration_pref_key"
+ android:title="@string/notification_vibrate_pref_title"
+ android:defaultValue="@bool/notification_vibration_pref_default"
+ android:persistent="true"
+ android:dependency="@string/notifications_enabled_pref_key" />
+
+ <PreferenceScreen
+ android:key="@string/advanced_pref_key"
+ android:title="@string/advanced_settings" />
+
+ <PreferenceCategory
+ android:key="@string/debug_pref_key"
+ android:title="@string/debug_category_pref_title">
+
+ <SwitchPreference
+ android:key="@string/dump_sms_pref_key"
+ android:title="@string/dump_sms_pref_title"
+ android:summary="@string/dump_sms_pref_summary"
+ android:defaultValue="@bool/dump_sms_pref_default" />
+
+ <SwitchPreference
+ android:key="@string/dump_mms_pref_key"
+ android:title="@string/dump_mms_pref_title"
+ android:summary="@string/dump_mms_pref_summary"
+ android:defaultValue="@bool/dump_mms_pref_default" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml-v21/preferences_per_subscription.xml b/res/xml-v21/preferences_per_subscription.xml
new file mode 100644
index 0000000..d0ae3e6
--- /dev/null
+++ b/res/xml-v21/preferences_per_subscription.xml
@@ -0,0 +1,66 @@
+<?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.
+-->
+<!-- Preference screen definition for Bugle's subscription-specific settings -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <PreferenceCategory
+ android:key="@string/mms_messaging_category_pref_key"
+ android:title="@string/mms_messaging_category_pref_title">
+
+ <Preference
+ android:title="@string/group_mms_pref_title"
+ android:key="@string/group_mms_pref_key"/>
+
+ <com.android.messaging.ui.appsettings.PhoneNumberPreference
+ android:key="@string/mms_phone_number_pref_key"
+ android:title="@string/mms_phone_number_pref_title" />
+
+ <SwitchPreference
+ android:key="@string/auto_retrieve_mms_pref_key"
+ android:title="@string/auto_retrieve_mms_pref_title"
+ android:summary="@string/auto_retrieve_mms_pref_summary"
+ android:defaultValue="@bool/auto_retrieve_mms_pref_default" />
+
+ <SwitchPreference
+ android:key="@string/auto_retrieve_mms_when_roaming_pref_key"
+ android:dependency="@string/auto_retrieve_mms_pref_key"
+ android:title="@string/auto_retrieve_mms_when_roaming_pref_title"
+ android:summary="@string/auto_retrieve_mms_when_roaming_pref_summary"
+ android:defaultValue="@bool/auto_retrieve_mms_when_roaming_pref_default" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="@string/advanced_category_pref_key"
+ android:title="@string/advanced_category_pref_title">
+
+ <SwitchPreference
+ android:key="@string/delivery_reports_pref_key"
+ android:title="@string/delivery_reports_pref_title"
+ android:summary="@string/delivery_reports_pref_summary"
+ android:defaultValue="@bool/delivery_reports_pref_default" />
+
+ <Preference
+ android:key="@string/wireless_alerts_key"
+ android:title="@string/wireless_alerts_title"/>
+
+ <PreferenceScreen
+ android:key="@string/sms_apns_key"
+ android:title="@string/sms_apns_title" />
+
+ </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml-v23/preferences_application.xml b/res/xml-v23/preferences_application.xml
new file mode 100644
index 0000000..8fbadc4
--- /dev/null
+++ b/res/xml-v23/preferences_application.xml
@@ -0,0 +1,92 @@
+<?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.
+-->
+<!-- Preference screen definition for Bugle's application-wide settings -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- KLP+ only -->
+ <PreferenceScreen
+ android:key="@string/sms_disabled_pref_key"
+ android:title="@string/sms_disabled_pref_title"
+ android:persistent="false">
+ <intent
+ android:action="android.provider.Telephony.ACTION_CHANGE_DEFAULT">
+ <extra android:name="package" android:value="com.android.messaging" />
+ </intent>
+ </PreferenceScreen>
+ <!-- MNC+ only -->
+ <PreferenceScreen
+ android:key="@string/sms_enabled_pref_key"
+ android:title="@string/sms_enabled_pref_title"
+ android:persistent="false">
+ <intent
+ android:action="android.provider.Telephony.ACTION_CHANGE_DEFAULT">
+ </intent>
+ </PreferenceScreen>
+
+ <SwitchPreference
+ android:key="@string/send_sound_pref_key"
+ android:title="@string/send_sound_pref_title"
+ android:defaultValue="@bool/send_sound_pref_default"
+ android:persistent="true" />
+
+ <SwitchPreference
+ android:key="@string/notifications_enabled_pref_key"
+ android:title="@string/notifications_enabled_pref_title"
+ android:defaultValue="@bool/notifications_enabled_pref_default"
+ android:persistent="true"
+ android:disableDependentsState="false" />
+
+ <RingtonePreference
+ android:key="@string/notification_sound_pref_key"
+ android:title="@string/notification_sound_pref_title"
+ android:ringtoneType="notification"
+ android:showSilent="true"
+ android:showDefault="true"
+ android:persistent="true"
+ android:defaultValue=""
+ android:dependency="@string/notifications_enabled_pref_key" />
+
+ <SwitchPreference
+ android:key="@string/notification_vibration_pref_key"
+ android:title="@string/notification_vibrate_pref_title"
+ android:defaultValue="@bool/notification_vibration_pref_default"
+ android:persistent="true"
+ android:dependency="@string/notifications_enabled_pref_key" />
+
+ <PreferenceScreen
+ android:key="@string/advanced_pref_key"
+ android:title="@string/advanced_settings" />
+
+ <PreferenceCategory
+ android:key="@string/debug_pref_key"
+ android:title="@string/debug_category_pref_title">
+
+ <SwitchPreference
+ android:key="@string/dump_sms_pref_key"
+ android:title="@string/dump_sms_pref_title"
+ android:summary="@string/dump_sms_pref_summary"
+ android:defaultValue="@bool/dump_sms_pref_default" />
+
+ <SwitchPreference
+ android:key="@string/dump_mms_pref_key"
+ android:title="@string/dump_mms_pref_title"
+ android:summary="@string/dump_mms_pref_summary"
+ android:defaultValue="@bool/dump_mms_pref_default" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/apn_editor.xml b/res/xml/apn_editor.xml
new file mode 100644
index 0000000..5afaeb5
--- /dev/null
+++ b/res/xml/apn_editor.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+ android:title="@string/apn_edit">
+ <EditTextPreference
+ android:title="@string/apn_name"
+ android:dialogTitle="@string/apn_name"
+ android:key="apn_name"
+ android:singleLine="true"
+ android:inputType="text"
+ />
+ <EditTextPreference
+ android:title="@string/apn_mmsc"
+ android:dialogTitle="@string/apn_mmsc"
+ android:key="apn_mmsc"
+ android:singleLine="true"
+ android:inputType="textUri"
+ />
+ <EditTextPreference
+ android:title="@string/apn_mms_proxy"
+ android:dialogTitle="@string/apn_mms_proxy"
+ android:key="apn_mms_proxy"
+ android:singleLine="true"
+ android:inputType="textUri"
+ />
+ <EditTextPreference
+ android:title="@string/apn_mms_port"
+ android:dialogTitle="@string/apn_mms_port"
+ android:key="apn_mms_port"
+ android:singleLine="true"
+ android:inputType="number"
+ />
+ <EditTextPreference
+ android:title="@string/apn_mcc"
+ android:dialogTitle="@string/apn_mcc"
+ android:key="apn_mcc"
+ android:singleLine="true"
+ android:inputType="number"
+ />
+ <EditTextPreference
+ android:title="@string/apn_mnc"
+ android:dialogTitle="@string/apn_mnc"
+ android:key="apn_mnc"
+ android:singleLine="true"
+ android:inputType="number"
+ />
+</PreferenceScreen>
diff --git a/res/xml/apn_settings.xml b/res/xml/apn_settings.xml
new file mode 100644
index 0000000..61a8b5b
--- /dev/null
+++ b/res/xml/apn_settings.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+ android:title="@string/apn_settings" android:key="@string/apn_list_pref_key">
+</PreferenceScreen>
diff --git a/res/xml/apns.xml b/res/xml/apns.xml
new file mode 100644
index 0000000..57e1163
--- /dev/null
+++ b/res/xml/apns.xml
@@ -0,0 +1,3284 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ 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.
+-->
+ <apns version="9">
+ <apn mcc="001" mnc="01" carrier="AT&amp;T PHONE TEST SIM" apn="phone" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="default,mms,supl,hipri,fota,dun"></apn>
+ <apn mcc="001" mnc="01" carrier="EHRPD - Test CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="01" carrier="LTE - Test CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="01" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="01" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="01" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="001" mnc="01" carrier="U.S.Cellular TEST SIM" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,hipri,dun,fota" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="010" carrier="EHRPD - VZW Test CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="010" carrier="LTE - VZW Test CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="010" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="010" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="001" mnc="010" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="1" mnc="1" carrier="AT&amp;T US MMS TEST SIM" apn="phone" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="mms"></apn>
+ <apn mcc="1" mnc="1" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="1" mnc="1" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="1" mnc="1" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="1" mnc="10" carrier="VZW Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="1" mnc="10" carrier="VZW Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="1" mnc="10" carrier="VZW Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="202" mnc="01" carrier="Cosmote Mms" apn="mms" mmsc="http://mmsc.cosmote.gr:8002" mmsproxy="10.10.10.20" mmsport="8080" type="mms"></apn>
+ <apn mcc="202" mnc="1" carrier="Cosmote Internet MMS" apn="internet" mmsc="http://mmsc.cosmote.gr:8002" mmsproxy="10.10.10.20" mmsport="8080" type="default,mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="202" mnc="1" carrier="Cosmote Mms" apn="mms" mmsc="http://mmsc.cosmote.gr:8002" mmsproxy="10.10.10.20" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="202" mnc="05" carrier="Vf MMS" apn="mms.vodafone.net" mmsc="http://mms.vodafone.gr" mmsproxy="213.249.19.49" mmsport="5080" type="mms" user="user" password="pass" authtype="1"></apn>
+ <apn mcc="202" mnc="5" carrier="VF MMS" apn="mms.vodafone.net" mmsc="http://mms.vodafone.gr" mmsproxy="213.249.19.49" mmsport="5080" type="mms" user="user" password="pass" authtype="0"></apn>
+ <apn mcc="202" mnc="09" carrier="Q-Telecom MMS GPRS" apn="q-mms.myq.gr" mmsc="http://mms.myq.gr" mmsproxy="192.168.80.134" mmsport="8080" type="mms"></apn>
+ <apn mcc="202" mnc="10" carrier="WIND GR MMS" apn="mnet.b-online.gr" mmsc="http://192.168.200.95/servlets/mms" mmsproxy="192.168.200.11" mmsport="9401" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="202" mnc="10" carrier="Wind MMS" apn="mnet.b-online.gr" mmsc="http://192.168.200.95/servlets/mms" mmsproxy="192.168.200.11" mmsport="9401" type="mms"></apn>
+ <apn mcc="202" mnc="10" carrier="Wind MMS" apn="mnet.b-online.gr" mmsc="http://192.168.200.95/servlets/mms" mmsproxy="192.168.200.11" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="204" mnc="02" carrier="Tele2 GPRS" apn="internet.tele2.nl" mmsc="http://mmsc.tele2.nl" mmsproxy="193.12.40.64" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="204" mnc="2" carrier="Tele2 GPRS" apn="internet.tele2.nl" mmsc="http://mmsc.tele2.nl" mmsproxy="193.12.40.64" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="204" mnc="2" carrier="Tele2 MMS" apn="internet.tele2.nl" mmsc="http://mmsc.tele2.nl" mmsproxy="193.12.40.64" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="204" mnc="04" carrier="CSpire international" apn="admin.cs4glte.com" mmsc="http://pix.cspire.com" type="admin,fota,ota" mvno_match_data="C Spire" mvno_type="spn" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="204" mnc="04" carrier="CSpire international" apn="internet.cs4glte.com" mmsc="http://pix.cspire.com" type="default,internet,mms" user="Uniroam@inet.cs.com" password="cs3g" authtype="3" mvno_match_data="C Spire" mvno_type="spn" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="204" mnc="04" carrier="CSpire international" apn="tethering.cs4glte.com" mmsc="http://pix.cspire.com" type="dun,pam" mvno_match_data="C Spire" mvno_type="spn" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="204" mnc="04" carrier="EHRPD - VZW Roaming CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="204" mnc="04" carrier="EHRPD - VZW Roaming CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" mvno_match_data="BAE0000000000000" mvno_type="gid" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="204" mnc="04" carrier="LTE - VZW Roaming CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="204" mnc="04" carrier="LTE - VZW Roaming CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" mvno_match_data="BAE0000000000000" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="204" mnc="04" carrier="SaskTel" apn="pda.stm.sk.ca" mmsc="http://mms.sasktel.com/" mmsproxy="mig.sasktel.com" mmsport="80" type="default,mms,supl" mvno_match_data="5A" mvno_type="gid"></apn>
+ <apn mcc="204" mnc="04" carrier="SaskTel" apn="pda.stm.sk.ca" mmsc="http://mms.sasktel.com/" mmsproxy="mig.sasktel.com" mmsport="80" type="default,mms,supl,dun" mvno_match_data="5A" mvno_type="gid"></apn>
+ <apn mcc="204" mnc="04" carrier="Vodafone NL" apn="live.vodafone.com" mmsc="http://mmsc.mms.vodafone.nl" mmsproxy="192.168.251.150" mmsport="8799" type="default,supl,mms" user="vodafone" password="vodafone" authtype="1"></apn>
+ <apn mcc="204" mnc="4" carrier="Vodafone NL" apn="live.vodafone.com" mmsc="http://mmsc.mms.vodafone.nl" mmsproxy="192.168.251.150" mmsport="8799" type="default,mms" user="vodafone" password="vodafone" authtype="1" proxy="192.168.251.150" port="8799"></apn>
+ <apn mcc="204" mnc="08" carrier="KPN/Hi 4G LTE Mobiel internet" apn="KPN4G.nl" mmsc="http://mp.mobiel.kpn/mmsc" mmsproxy="10.10.100.20" mmsport="5080" type="default,supl,mms"></apn>
+ <apn mcc="204" mnc="08" carrier="KPN/Hi Mobiel Internet" apn="portalmmm.nl" mmsc="http://mp.mobiel.kpn/mmsc" mmsproxy="10.10.100.20" mmsport="5080" type="default,supl,mms"></apn>
+ <apn mcc="204" mnc="08" carrier="Rabo Mobiel MMS" apn="rabo" mmsc="http://mp.mobiel.kpn/mmsc" mmsproxy="10.10.100.10" mmsport="5080" type="mms" mvno_match_data="Rabo Mobiel" mvno_type="spn"></apn>
+ <apn mcc="204" mnc="8" carrier="KPN" apn="portalmmm.nl" mmsc="http://mp.mobiel.kpn/mmsc" mmsproxy="10.10.100.20" mmsport="5080" type="default,mms" authtype="0"></apn>
+ <apn mcc="204" mnc="8" carrier="KPN/Hi 4G LTE Mobiel internet" apn="KPN4G.nl" mmsc="http://mp.mobiel.kpn/mmsc" mmsproxy="10.10.100.20" mmsport="5080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="204" mnc="8" carrier="KPN/Hi Mobiel Internet" apn="portalmmm.nl" mmsc="http://mp.mobiel.kpn/mmsc" mmsproxy="10.10.100.20" mmsport="5080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="204" mnc="12" carrier="Telfort" apn="internet" mmsc="http://mms" mmsproxy="193.113.200.195" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="204" mnc="12" carrier="Telfort Internet" apn="internet" mmsc="http://mms" mmsproxy="193.113.200.195" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="204" mnc="12" carrier="Telfort Internet" apn="internet" mmsc="http://mms" mmsproxy="193.113.200.195" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="204" mnc="16" carrier="Ben MMS" apn="mms.ben" mmsc="http://benmms/" mmsproxy="10.10.10.11" mmsport="8080" type="mms" authtype="1" mvno_match_data="BEN NL" mvno_type="spn"></apn>
+ <apn mcc="204" mnc="16" carrier="T-Mobile MMS" apn="mms" mmsc="http://t-mobilemms" mmsproxy="10.10.10.11" mmsport="8080" type="mms" user="tmobilemms" password="tmobilemms" authtype="1"></apn>
+ <apn mcc="204" mnc="16" carrier="T-Mobile MMS" apn="mms" mmsc="http://t-mobilemms" mmsproxy="10.10.10.11" mmsport="8080" type="mms" user="tmobilemms" password="tmobilemms" authtype="1" protocol="IP"></apn>
+ <apn mcc="204" mnc="16" carrier="T-Mobile NL" apn="internet" mmsc="http://t-mobilemms" mmsproxy="010.010.010.011" mmsport="8080" type="default,supl"></apn>
+ <apn mcc="204" mnc="16" carrier="T-Mobile NL MMS" apn="mms" mmsc="http://t-mobilemms" mmsproxy="010.010.010.011" mmsport="8080" type="mms"></apn>
+ <apn mcc="204" mnc="20" carrier="Orange NL MMS" apn="mms" mmsc="http://mms.orange.nl:8002" mmsproxy="10.250.255.183" mmsport="5080" type="mms" authtype="0"></apn>
+ <apn mcc="204" mnc="20" carrier="T-Mobile MMS" apn="mms" mmsc="http://t-mobilemms" mmsproxy="10.10.10.11" mmsport="8080" type="mms" user="tmobilemms" password="tmobilemms" authtype="1"></apn>
+ <apn mcc="206" mnc="01" carrier="MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm" authtype="0" mvno_match_data="IUSACELL" mvno_type="spn"></apn>
+ <apn mcc="206" mnc="01" carrier="MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm" authtype="0" mvno_match_data="UNEFON" mvno_type="spn"></apn>
+ <apn mcc="206" mnc="01" carrier="MMS" apn="mms.mobi.eastlink.ca" mmsc="http://mmss.mobi.eastlink.ca" mmsproxy="10.232.12.49" mmsport="8080" type="mms" mvno_match_data="B6" mvno_type="gid"></apn>
+ <apn mcc="206" mnc="01" carrier="NRJMMS" apn="event.proximus.be" mmsc="http://mmsc.proximus.be/mms" mmsproxy="10.55.14.75" mmsport="8080" type="mms" user="mms" password="mms" authtype="0" mvno_match_data="NRJ Mobile" mvno_type="spn"></apn>
+ <apn mcc="206" mnc="01" carrier="Px MMS" apn="EVENT.PROXIMUS.BE" mmsc="http://mmsc.proximus.be/mms" mmsproxy="10.55.14.75" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="206" mnc="01" carrier="Telenet MMS" apn="mms.be" mmsc="http://mmsc.telenet.be" mmsproxy="195.130.149.100" mmsport="80" type="mms" mvno_match_data="2060188" mvno_type="imsi"></apn>
+ <apn mcc="206" mnc="01" carrier="VM MMS" apn="virgin-mobile.fr" mmsc="http://virginmms.fr" mmsproxy="10.6.10.1" mmsport="8080" type="mms" mvno_match_data="52" mvno_type="gid"></apn>
+ <apn mcc="206" mnc="1" carrier="Px MMS" apn="EVENT.PROXIMUS.BE" mmsc="http://mmsc.proximus.be/mms" mmsproxy="10.55.14.75" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="206" mnc="1" carrier="Px MMS" apn="event.proximus.be" mmsc="http://mmsc.proximus.be/mms" mmsproxy="10.55.14.75" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="206" mnc="1" carrier="Telenet MMS" apn="mms.be" mmsc="http://mmsc.telenet.be" mmsproxy="195.130.149.100" mmsport="80" type="mms" authtype="0" mvno_match_data="2060188" mvno_type="imsi"></apn>
+ <apn mcc="206" mnc="1" carrier="Telenet Roam MMS" apn="mms.be" mmsc="http://mmsc.telenet.be" mmsproxy="195.130.149.100" mmsport="80" type="mms" authtype="0" mvno_match_data="Telenet" mvno_type="spn"></apn>
+ <apn mcc="206" mnc="05" carrier="Telenet MMS" apn="mms.be" mmsc="http://mmsc.telenet.be" mmsproxy="195.130.149.100" mmsport="80" type="mms"></apn>
+ <apn mcc="206" mnc="10" carrier="Mobistar MMS" apn="mms.be" mmsc="http://mmsc.mobistar.be" mmsproxy="212.65.63.143" mmsport="8080" type="mms"></apn>
+ <apn mcc="206" mnc="10" carrier="Mobistar MMS" apn="mms.be" mmsc="http://mmsc.mobistar.be" mmsproxy="212.65.63.143" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="206" mnc="10" carrier="Mobistar MMS" apn="mms.be" mmsc="http://mmsc.mobistar.be" mmsproxy="212.65.63.143" mmsport="8080" type="mms" user="mobistar" password="mobistar" authtype="0"></apn>
+ <apn mcc="206" mnc="20" carrier="BASE MMS" apn="mms.base.be" mmsc="http://mmsc.base.be" mmsproxy="217.72.235.1" mmsport="8080" type="mms" user="base" password="base" authtype="1"></apn>
+ <apn mcc="206" mnc="20" carrier="Base MMS" apn="mms.base.be" mmsc="http://mmsc.base.be" mmsproxy="217.72.235.1" mmsport="8080" type="mms" user="base" password="base" authtype="0"></apn>
+ <apn mcc="208" mnc="01" carrier="Carrefour MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1" mvno_match_data="33" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="01" carrier="NRJMMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1" mvno_match_data="4E" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="01" carrier="Orange MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1"></apn>
+ <apn mcc="208" mnc="01" carrier="Tele2 MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange"></apn>
+ <apn mcc="208" mnc="01" carrier="VM MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1" mvno_match_data="52" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="1" carrier="Carrefour MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1" mvno_match_data="33" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="1" carrier="MMS Carrefour" apn="orange.acte" mmsc="http://mms.orange.fr/" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="0" mvno_match_data="Carrefour Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="1" carrier="NRJMMS" apn="orange.acte" mmsc="http://mms.orange.fr/" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="0" mvno_match_data="NRJ Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="1" carrier="Orange MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1"></apn>
+ <apn mcc="208" mnc="1" carrier="Orange MMS" apn="orange.acte" mmsc="http://mms.orange.fr/" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="0"></apn>
+ <apn mcc="208" mnc="1" carrier="Tele2 MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="3"></apn>
+ <apn mcc="208" mnc="1" carrier="VM MMS" apn="orange.acte" mmsc="http://mms.orange.fr" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="1" mvno_match_data="52" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="1" carrier="VM MMS FR" apn="orange.acte" mmsc="http://mms.orange.fr/" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange" authtype="0" mvno_match_data="Virgin Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="09" carrier="SFR webphone" apn="sl2sfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="default,mms,supl,agps,fota" protocol="IP"></apn>
+ <apn mcc="208" mnc="10" carrier="Auchan MMS" apn="mms65" mmsc="http://mms65" mmsproxy="10.143.156.8" mmsport="8080" type="mms" mvno_match_data="A MOBILE" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="Auchan MMS" apn="mms65" mmsc="http://mms65" mmsproxy="10.143.156.8" mmsport="8080" type="mms" authtype="0" mvno_match_data="A MOBILE" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="Coriolis MMS" apn="mmscoriolis" mmsc="http://mmscoriolis" mmsproxy="10.143.156.6" mmsport="8080" type="mms" mvno_match_data="12" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="Coriolis MMS" apn="mmscoriolis" mmsc="http://mmscoriolis" mmsproxy="10.143.156.6" mmsport="8080" type="mms" authtype="0" mvno_match_data="12" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="Coriolis MMS FR" apn="mmscoriolis" mmsc="http://mmscoriolis" mmsproxy="10.143.156.6" mmsport="8080" type="mms" authtype="0" mvno_match_data="Coriolis" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="DARTY MMS" apn="mms68" mmsc="http://mms68/" mmsproxy="10.143.156.11" mmsport="8080" type="mms" authtype="0" mvno_match_data="Darty" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="Darty MMS" apn="mms68" mmsc="http://mms68/" mmsproxy="10.143.156.11" mmsport="8080" type="mms" mvno_match_data="44" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="Darty MMS" apn="mms68" mmsc="http://mms68/" mmsproxy="10.143.156.11" mmsport="8080" type="mms" authtype="0" mvno_match_data="44" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="Internet Joe" apn="sl2sfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="default,mms,supl,agps,fota" authtype="0" mvno_match_data="53" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="Keyyo Mobile MMS" apn="mms68" mmsc="http://mms68" mmsproxy="10.143.156.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="208" mnc="10" carrier="Keyyo Mobile MMS" apn="mms68" mmsc="http://mms68" mmsproxy="10.143.156.11" mmsport="8080" type="mms" authtype="0" mvno_match_data="Keyyo Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS" apn="sl2sfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="mms"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS" apn="sl2sfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS La Poste Mobile" apn="mmsdebitel" mmsc="http://mmsdebitel" mmsproxy="10.143.156.3" mmsport="8080" type="mms" mvno_match_data="4C" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS La Poste Mobile" apn="mmsdebitel" mmsc="http://mmsdebitel" mmsproxy="10.143.156.3" mmsport="8080" type="mms" authtype="0" mvno_match_data="4C" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS La Poste Mobile" apn="mmsdebitel" mmsc="http://mmsdebitel" mmsproxy="10.143.156.3" mmsport="8080" type="mms" authtype="0" mvno_match_data="La Poste Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS Leclerc mobile" apn="mms66" mmsc="http://mms66" mmsproxy="10.143.156.9" mmsport="8080" type="mms" authtype="0" mvno_match_data="Leclerc Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS LeclercMobile" apn="mms66" mmsc="http://mms66" mmsproxy="10.143.156.9" mmsport="8080" type="mms" mvno_match_data="LeclercMobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS LeclercMobile" apn="mms66" mmsc="http://mms66" mmsproxy="10.143.156.9" mmsport="8080" type="mms" authtype="0" mvno_match_data="LeclercMobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS RegloMobile" apn="mms66" mmsc="http://mms66" mmsproxy="10.143.156.9" mmsport="8080" type="mms" authtype="0" mvno_match_data="RegloMobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="MMS Simplicime" apn="mmsdebitel" mmsc="http://mmsdebitel" mmsproxy="10.143.156.3" mmsport="8080" type="mms"></apn>
+ <apn mcc="208" mnc="10" carrier="NRJMMS" apn="mmsnrj" mmsc="http://mmsnrj" mmsproxy="10.143.156.5" mmsport="8080" type="mms" mvno_match_data="4E" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="NRJMMS" apn="mmsnrj" mmsc="http://mmsnrj" mmsproxy="10.143.156.5" mmsport="8080" type="mms" authtype="0" mvno_match_data="4E" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="10" carrier="NRJMMS" apn="mmsnrj" mmsc="http://mmsnrj" mmsproxy="10.143.156.5" mmsport="8080" type="mms" authtype="0" mvno_match_data="NRJ Mobile" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="SFR MMS" apn="mmssfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="208" mnc="10" carrier="SFR webphone" apn="sl2sfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="default,mms,supl"></apn>
+ <apn mcc="208" mnc="10" carrier="SFR webphone" apn="sl2sfr" mmsc="http://mms1" mmsproxy="10.151.0.1" mmsport="8080" type="default,mms,supl,agps,fota" authtype="0"></apn>
+ <apn mcc="208" mnc="10" carrier="WEB La Poste Mobile" apn="sl2sfr" mmsc="http://mmsdebitel" mmsproxy="10.143.156.3" mmsport="8080" type="default,mms,supl" mvno_match_data="4C" mvno_type="gid" proxy="192.168.21.3" port="8080"></apn>
+ <apn mcc="208" mnc="10" carrier="Zero forfait MMS" apn="mms68" mmsc="http://mms68" mmsproxy="10.143.156.11" mmsport="8080" type="mms" authtype="0" mvno_match_data="ZERO FORFAIT" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="10" carrier="mms65 Amobile" apn="mms65" mmsc="http://mms65" mmsproxy="10.143.156.8" mmsport="8080" type="mms" authtype="0" mvno_match_data="A MOBILE" mvno_type="spn"></apn>
+ <apn mcc="208" mnc="15" carrier="Free MMS" apn="mmsfree" mmsc="http://mms.free.fr" type="mms"></apn>
+ <apn mcc="208" mnc="15" carrier="Free MMS" apn="mmsfree" mmsc="http://mms.free.fr" type="mms" authtype="0"></apn>
+ <apn mcc="208" mnc="15" carrier="Free Telecom MMS" apn="mmsfree" mmsc="http://mms.free.fr" type="mms" authtype="0"></apn>
+ <apn mcc="208" mnc="20" carrier="Bouygues" apn="mmsbouygtel.com" mmsc="http://mms.bouyguestelecom.fr/mms/wapenc" mmsproxy="62.201.129.226" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="208" mnc="20" carrier="Bouygues Telecom" apn="mmsbouygtel.com" mmsc="http://mms.bouyguestelecom.fr/mms/wapenc" mmsproxy="62.201.129.226" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="208" mnc="20" carrier="Bouygues Telecom" apn="mmsbouygtel.com" mmsc="http://mms.bouyguestelecom.fr/mms/wapenc" mmsproxy="62.201.129.226" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="208" mnc="23" carrier="VM MMS" apn="virgin-mobile.fr" mmsc="http://virginmms.fr" mmsproxy="10.6.10.1" mmsport="8080" type="mms" mvno_match_data="52" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="23" carrier="VM MMS" apn="virgin-mobile.fr" mmsc="http://virginmms.fr" mmsproxy="10.6.10.1" mmsport="8080" type="mms" authtype="0" mvno_match_data="52" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="23" carrier="Virgin mobile" apn="virgin-mobile.fr" mmsc="http://mmc.omeatelecom.fr/servlets/mms" mmsproxy="10.6.10.1" mmsport="8080" type="default,mms" authtype="0" mvno_match_data="Virgin" mvno_type="spn" proxy="10.6.10.1" port="8080"></apn>
+ <apn mcc="208" mnc="26" carrier="NRJ MMS" apn="mmsnrj" mmsc="http://mmsnrj" mmsproxy="10.143.156.5" mmsport="8080" type="mms" mvno_match_data="4E" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="26" carrier="NRJ MMS" apn="mmsnrj" mmsc="http://mmsnrj" mmsproxy="10.143.156.5" mmsport="8080" type="mms" authtype="0" mvno_match_data="4E" mvno_type="gid"></apn>
+ <apn mcc="208" mnc="26" carrier="NRJMMS" apn="mmsnrj" mmsc="http://mmsnrj" mmsproxy="10.143.156.5" mmsport="8080" type="mms" authtype="0" mvno_match_data="NRJ Mobile" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="01" carrier="MMS VODAFONE" apn="mms.vodafone.net" mmsc="http://mmsc.vodafone.es/servlets/mms" mmsproxy="212.73.32.10" mmsport="80" type="mms" user="wap@wap" password="wap125" authtype="1"></apn>
+ <apn mcc="214" mnc="03" carrier="Carrefour MMS" apn="CARREFOURMMS" mmsc="http://mms.orange.es" mmsproxy="172.022.188.025" mmsport="8080" type="mms" user="CARREFOUR" password="CARREFOUR" authtype="1" mvno_match_data="2140352xxxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="03" carrier="Euskaltel MMS" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="1" mvno_match_data="2140359" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="03" carrier="Orange MMS" apn="orangemms" mmsc="http://mms.orange.es" mmsproxy="172.22.188.25" mmsport="8080" type="mms" user="orange" password="orange" authtype="1"></apn>
+ <apn mcc="214" mnc="3" carrier="Orange MMS" apn="orangemms" mmsc="http://mms.orange.es" mmsproxy="172.22.188.25" mmsport="8080" type="mms" user="orange" password="orange" authtype="0"></apn>
+ <apn mcc="214" mnc="04" carrier="Yoigo MMS" apn="mms" mmsc="http://mmss/" mmsproxy="193.209.134.141" mmsport="80" type="mms"></apn>
+ <apn mcc="214" mnc="4" carrier="Yoigo MMS" apn="mms" mmsc="http://mmss/" mmsproxy="193.209.134.141" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="214" mnc="4" carrier="Yoigo MMS" apn="mms" mmsc="http://mmss/" mmsproxy="193.209.134.141" mmsport="80" type="mms" authtype="1"></apn>
+ <apn mcc="214" mnc="05" carrier="Tuenti" apn="tuenti.com" mmsc="http://tuenti.com" mmsproxy="10.138.255.43" mmsport="8080" user="tuenti" password="tuenti" authtype="1"></apn>
+ <apn mcc="214" mnc="5" carrier="Tuenti" apn="tuenti.com" mmsc="http://tuenti.com" mmsproxy="10.138.255.43" mmsport="8080" user="tuenti" password="tuenti" authtype="1"></apn>
+ <apn mcc="214" mnc="06" carrier="Eroski Movil MMS" apn="mms.eroskimovil.es" mmsc="http://mms.eroskimovil.es/servlets/mms" mmsproxy="212.73.32.10" mmsport="80" type="mms" user="wap@wap" password="wap125" authtype="1" mvno_match_data="2140606" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="06" carrier="Euskaltel MMS" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="1" mvno_match_data="0008" mvno_type="gid"></apn>
+ <apn mcc="214" mnc="06" carrier="MMS" apn="mms.pepephone.com" mmsc="http://mms.pepephone.com/servlets/mms" mmsproxy="212.73.32.10" mmsport="80" type="mms" user="wap@wap" password="wap125" authtype="0" mvno_match_data="pepephone" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="06" carrier="MMS R" apn="mms.mundo-r.com" mmsc="http://mms.mundo-r.com" mmsproxy="10.0.157.169" mmsport="8080" type="mms" authtype="1" mvno_match_data="2140612" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="06" carrier="MMS Vodafone" apn="mms.vodafone.net" mmsc="http://mmsc.vodafone.es/servlets/mms" mmsproxy="212.73.32.10" mmsport="80" type="mms" user="wap@wap" password="wap125"></apn>
+ <apn mcc="214" mnc="06" carrier="TeleCable MMS" apn="mms.telecable.es" mmsc="http://mms.telecable.es/mms/" mmsproxy="212.89.0.84" mmsport="8080" type="mms" user="telecable" password="telecable" authtype="1" mvno_match_data="2140613" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="6" carrier="Eroski Movil MMS" apn="mms.eroskimovil.es" mmsc="http://mms.eroskimovil.es/servlets/mms" mmsproxy="212.73.32.10" mmsport="80" type="mms" user="wap@wap" password="wap125" authtype="1" mvno_match_data="2140606" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="6" carrier="Eroski Movil MMS" apn="mms.eroskimovil.es" mmsc="http://mms.eroskimovil.es/servlets/mms" mmsproxy="212.73.32.10" mmsport="80" type="mms" user="wap@wap" password="wap125" authtype="1" mvno_match_data="Eroski Movil" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="6" carrier="Euskaltel MMS" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="0" mvno_match_data="EUSKALTEL" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="6" carrier="Euskaltel MMS" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="1" mvno_match_data="0008" mvno_type="gid"></apn>
+ <apn mcc="214" mnc="6" carrier="MMS R" apn="mms.mundo-r.com" mmsc="http://mms.mundo-r.com" mmsproxy="10.0.157.169" mmsport="8080" type="mms" authtype="1" mvno_match_data="2140612" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="6" carrier="R MMS" apn="mms.mundo-r.com" mmsc="http://mms.mundo-r.com" mmsproxy="10.0.157.169" mmsport="8080" type="mms" authtype="0" mvno_match_data="mobil R" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="6" carrier="TeleCable MMS" apn="mms.telecable.es" mmsc="http://mms.telecable.es/mms/" mmsproxy="212.89.0.84" mmsport="8080" type="mms" user="telecable" password="telecable" authtype="1" mvno_match_data="2140613" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="6" carrier="telecable MMS" apn="mms.telecable.es" mmsc="http://mms.telecable.es/mms/" mmsproxy="212.89.0.84" mmsport="8080" type="mms" user="telecable" password="telecable" authtype="1" mvno_match_data="telecable" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="07" carrier="Jazztel MMS" apn="jazzmms" mmsc="http://jazztelmms.com/servlets/mms" mmsproxy="37.132.0.10" mmsport="8080" type="mms" authtype="1" mvno_match_data="JAZZTEL" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="07" carrier="Jazztel MMS" apn="jazzmms" mmsc="http://jazztelmms.com/servlets/mms/" mmsproxy="37.132.0.10" mmsport="8080" type="mms" authtype="1" mvno_match_data="JAZZTEL" mvno_type="spn"></apn>
+ <apn mcc="214" mnc="07" carrier="Movistar" apn="telefonica.es" mmsc="http://mms.movistar.com" mmsproxy="10.138.255.5" mmsport="8080" type="default,supl,mms" user="telefonica" password="telefonica" authtype="1" proxy="10.138.255.133" port="8080"></apn>
+ <apn mcc="214" mnc="07" carrier="T-2" apn="internet.t-2.net" mmsc="http://www.mms.t-2.net:8002" mmsproxy="172.20.18.137" mmsport="8080" type="default,ims,mms,supl" mvno_match_data="2140759577xxxxx" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="07" carrier="T-2" apn="internet.t-2.net" mmsc="http://www.mms.t-2.net:8002" mmsproxy="172.20.18.137" mmsport="8080" type="default,ims,mms,supl" mvno_match_data="2140796692xxxxx" mvno_type="imsi"></apn>
+ <apn mcc="214" mnc="7" carrier="Movistar" apn="telefonica.es" mmsc="http://mms.movistar.com" mmsproxy="10.138.255.5" mmsport="8080" type="default,mms,supl,agps,fota" user="telefonica" password="telefonica" authtype="1" proxy="10.138.255.133" port="8080"></apn>
+ <apn mcc="214" mnc="7" carrier="Movistar" apn="telefonica.es" mmsc="http://mms.movistar.com" mmsproxy="10.138.255.5" mmsport="8080" type="default,supl,mms" user="telefonica" password="telefonica" authtype="1" proxy="10.138.255.133" port="8080"></apn>
+ <apn mcc="214" mnc="08" carrier="Euskaltel MMS" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="1"></apn>
+ <apn mcc="214" mnc="8" carrier="Euskaltel MMS" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="1"></apn>
+ <apn mcc="214" mnc="8" carrier="MMS Euskaltel" apn="euskaltelmms.euskaltel.mobi" mmsc="http://mms.euskaltel.mobi" mmsproxy="172.16.18.74" mmsport="8080" type="mms" user="MMS" password="EUSKALTEL" authtype="0"></apn>
+ <apn mcc="214" mnc="16" carrier="TeleCable MMS" apn="mms.telecable.es" mmsc="http://mms.telecable.es/mms/" mmsproxy="212.89.0.84" mmsport="8080" type="mms" user="telecable" password="telecable"></apn>
+ <apn mcc="214" mnc="16" carrier="TeleCable MMS" apn="mms.telecable.es" mmsc="http://mms.telecable.es/mms/" mmsproxy="212.89.0.84" mmsport="8080" type="mms" user="telecable" password="telecable" authtype="3"></apn>
+ <apn mcc="214" mnc="16" carrier="Telecable MMS" apn="mms.telecable.es" mmsc="http://mms.telecable.es/mms/" mmsproxy="212.89.0.84" mmsport="8080" type="mms" user="telecable" password="telecable" authtype="0"></apn>
+ <apn mcc="214" mnc="18" carrier="ONO MMS" apn="mms.ono.com" mmsc="http://mms.ono.com" mmsproxy="10.126.0.50" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="214" mnc="18" carrier="ONO MMS" apn="mms.ono.com" mmsc="http://mms.ono.com/" mmsproxy="10.126.0.50" mmsport="8080" type="mms"></apn>
+ <apn mcc="214" mnc="18" carrier="ONO MMS" apn="mms.ono.com" mmsc="http://mms.ono.com/" mmsproxy="10.126.0.50" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="214" mnc="19" carrier="KPN" apn="gprs-service.com" mmsc="http://217.18.32.180:8080" mmsproxy="217.18.32.181" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="214" mnc="19" carrier="Simyo Internet" apn="gprs-service.com" mmsc="http://217.18.32.180:8080" mmsproxy="217.18.32.181" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="214" mnc="19" carrier="Simyo Internet" apn="gprs-service.com" mmsc="http://217.18.32.180:8080" mmsproxy="217.18.32.181" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="214" mnc="21" carrier="Jazztel MMS" apn="jazzmms" mmsc="http://jazztelmms.com/servlets/mms" mmsproxy="37.132.0.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="214" mnc="21" carrier="Jazztel MMS" apn="jazzmms" mmsc="http://jazztelmms.com/servlets/mms/" mmsproxy="37.132.0.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="214" mnc="21" carrier="MMS" apn="jazzmms" mmsc="http://jazztelmms.com:8081" mmsproxy="217.18.32.183" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="216" mnc="01" carrier="Djuice MMS" apn="mms" mmsc="http://mmsc.pgsm.hu/" mmsproxy="84.225.255.1" mmsport="8080" type="mms" authtype="0" mvno_match_data="Djuice" mvno_type="spn"></apn>
+ <apn mcc="216" mnc="01" carrier="Telenor MMS" apn="mms" mmsc="http://mmsc.telenor.hu/" mmsproxy="84.225.255.1" mmsport="8080" type="mms"></apn>
+ <apn mcc="216" mnc="1" carrier="Telenor HU MMS" apn="mms" mmsc="http://mmsc.telenor.hu" mmsproxy="84.225.255.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="216" mnc="1" carrier="Telenor MMS" apn="mms" mmsc="http://mmsc.telenor.hu/" mmsproxy="84.225.255.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="216" mnc="30" carrier="T-Mobile H" apn="internet.telekom" mmsc="http://mms.t-mobile.hu/servlets/mms" mmsproxy="212.51.126.10" mmsport="8080" type="default,mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="216" mnc="30" carrier="T-Mobile H MMS" apn="internet.telekom" mmsc="http://mms.t-mobile.hu/servlets/mms" mmsproxy="212.51.126.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="216" mnc="30" carrier="T-Mobile MMS" apn="mms" mmsc="http://mms.t-mobile.hu/servlets/mms" mmsproxy="212.51.126.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="216" mnc="70" carrier="Vodafone MMS" apn="mms.vodafone.net" mmsc="http://mms.vodafone.hu/servlets/mms" mmsproxy="80.244.97.2" mmsport="8080" type="mms" mvno_match_data="21670xx1xxx" mvno_type="imsi"></apn>
+ <apn mcc="216" mnc="70" carrier="Vodafone MMS" apn="mms.vodafone.net" mmsc="http://mms.vodafone.hu/servlets/mms" mmsproxy="80.244.97.2" mmsport="8080" type="mms" authtype="0" mvno_match_data="21670xx2xxx" mvno_type="imsi"></apn>
+ <apn mcc="218" mnc="03" carrier="Ht Eronet MMS" apn="mms.eronet.ba" mmsc="http://mms.gprs.eronet.ba/mms/wapenc" mmsproxy="10.12.3.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="218" mnc="3" carrier="Eronet BH MMS" apn="mms.eronet.ba" mmsc="http://mms.gprs.eronet.ba/mms/wapenc" mmsproxy="10.12.3.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="218" mnc="3" carrier="Ht Eronet MMS" apn="mms.eronet.ba" mmsc="http://mms.gprs.eronet.ba/mms/wapenc" mmsproxy="10.12.3.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="218" mnc="05" carrier="mtelmms" apn="mtelmms" mmsc="http://mmsc.mtel.ba/mms/wapenc" mmsproxy="192.168.61.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="218" mnc="5" carrier="mtel MMS" apn="mtelmms" mmsc="http://mmsc.mtel.ba/mms/wapenc" mmsproxy="192.168.61.11" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="218" mnc="5" carrier="mtelmms" apn="mtelmms" mmsc="http://mmsc.mtel.ba/mms/wapenc" mmsproxy="192.168.61.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="218" mnc="90" carrier="BH Mobile MMS" apn="mms.bhmobile.ba" mmsc="http://mms.bhmobile.ba/cmmsc/post" mmsproxy="195.222.56.41" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="218" mnc="90" carrier="BHMobileMMS" apn="mms.bhmobile.ba" mmsc="http://mms.bhmobile.ba/cmmsc/post" mmsproxy="195.222.56.41" mmsport="8080" type="mms"></apn>
+ <apn mcc="218" mnc="90" carrier="BHMobileMMS" apn="mms.bhmobile.ba" mmsc="http://mms.bhmobile.ba/cmmsc/post" mmsproxy="195.222.56.41" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="219" mnc="01" carrier="T-Mobile MMS" apn="mms.htgprs" mmsc="http://mms.t-mobile.hr/servlets/mms" mmsproxy="10.12.0.4" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="219" mnc="01" carrier="bonbon MMS" apn="mms.htgprs" mmsc="http://mms.bonbon.com.hr/servlets/mms" mmsproxy="10.12.0.4" mmsport="8080" type="mms" authtype="1" mvno_match_data="bonbon" mvno_type="spn"></apn>
+ <apn mcc="219" mnc="1" carrier="MMS" apn="mms.htgprs" mmsc="http://mms.t-mobile.hr/servlets/mms" mmsproxy="10.12.0.4" mmsport="8080" type="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="219" mnc="1" carrier="T-Mobile MMS" apn="mms.htgprs" mmsc="http://mms.t-mobile.hr/servlets/mms" mmsproxy="10.12.0.4" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="219" mnc="02" carrier="Tele2" apn="internet.tele2.hr" mmsc="http://mmsc.tele2.hr" mmsproxy="193.12.40.66" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="219" mnc="2" carrier="TELE2" apn="internet.tele2.hr" mmsc="http://mmsc.tele2.hr" mmsproxy="193.12.40.66" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="219" mnc="2" carrier="Tele2" apn="internet.tele2.hr" mmsc="http://mmsc.tele2.hr" mmsproxy="193.12.40.66" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="219" mnc="10" carrier="HR VIP mms" apn="mms.vipnet.hr" mmsc="http://mms.vipnet.hr/servlets/mms" mmsproxy="212.91.99.91" mmsport="8080" type="mms" user="38591" password="38591" authtype="0"></apn>
+ <apn mcc="219" mnc="10" carrier="VIP.mms" apn="mms.vipnet.hr" mmsc="http://mms.vipnet.hr/servlets/mms" mmsproxy="212.91.99.91" mmsport="8080" type="mms"></apn>
+ <apn mcc="219" mnc="10" carrier="VIP.mms" apn="mms.vipnet.hr" mmsc="http://mms.vipnet.hr/servlets/mms" mmsproxy="212.91.99.91" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="220" mnc="01" carrier="Telenor MMS" apn="mms" mmsc="http://mms.telenor.rs/servlets/mms" mmsproxy="217.65.192.33" mmsport="8080" type="mms"></apn>
+ <apn mcc="220" mnc="1" carrier="Telenor MMS" apn="mms" mmsc="http://mms.telenor.rs/servlets/mms" mmsproxy="217.65.192.33" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="220" mnc="1" carrier="Telenor SRB MMS" apn="mms" mmsc="http://mms.telenor.rs/servlets/mms" mmsproxy="217.65.192.33" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="220" mnc="02" carrier="Telenor MNE mms" apn="mms" mmsc="http://mm.vor.telenor.me" mmsproxy="192.168.246.5" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="220" mnc="2" carrier="Telenor MNE mms" apn="mms" mmsc="http://mm.vor.telenor.me" mmsproxy="192.168.246.5" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="220" mnc="03" carrier="mt:s mms" apn="mms" mmsc="http://mms.mts064.telekom.rs/mms/wapenc" mmsproxy="172.17.85.131" mmsport="8080" type="mms" user="mts" password="064" authtype="1"></apn>
+ <apn mcc="220" mnc="3" carrier="MTS MMS RS" apn="mms" mmsc="http://mms.mts064.telekom.rs/mms/wapenc" mmsproxy="172.17.85.131" mmsport="8080" type="mms" user="mts" password="064" authtype="0"></apn>
+ <apn mcc="220" mnc="04" carrier="T-Mobile MMS" apn="mms" mmsc="http://192.168.180.100/servlets/mms" mmsproxy="10.0.5.19" mmsport="8080" type="mms" user="38267" password="38267"></apn>
+ <apn mcc="220" mnc="05" carrier="Vip MMS" apn="vipmobile.mms" mmsc="http://mmsc.vipmobile.rs" mmsproxy="212.15.182.82" mmsport="8080" type="mms" user="vipmobile" password="vipmobile" authtype="1"></apn>
+ <apn mcc="220" mnc="5" carrier="Vip SRB MMS" apn="vipmobile.mms" mmsc="http://mmsc.vipmobile.rs" mmsproxy="212.15.182.82" mmsport="8080" type="mms" user="vipmobile" password="vipmobile" authtype="0"></apn>
+ <apn mcc="222" mnc="01" carrier="MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm" authtype="0" mvno_match_data="IUSACELL" mvno_type="spn"></apn>
+ <apn mcc="222" mnc="01" carrier="MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm" authtype="0" mvno_match_data="UNEFON" mvno_type="spn"></apn>
+ <apn mcc="222" mnc="01" carrier="MMS" apn="mms.windmobile.ca" mmsc="http://mms.windmobile.ca" mmsproxy="74.115.197.70" mmsport="8080" type="mms" mvno_match_data="FFFFFF00" mvno_type="gid"></apn>
+ <apn mcc="222" mnc="01" carrier="NOVERCA MMS" apn="mms.noverca.it" mmsc="http://mms.noverca.it/" mmsproxy="213.230.130.89" mmsport="80" type="mms" authtype="0" mvno_match_data="Noverca" mvno_type="spn"></apn>
+ <apn mcc="222" mnc="01" carrier="TIM MMS" apn="unico.tim.it" mmsc="http://mms.tim.it/servlets/mms" mmsproxy="213.230.130.89" mmsport="80" type="mms"></apn>
+ <apn mcc="222" mnc="1" carrier="TIM MMS" apn="unico.tim.it" mmsc="http://mms.tim.it/servlets/mms" mmsproxy="213.230.130.89" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="222" mnc="10" carrier="MMS Vodafone" apn="mms.vodafone.it" mmsc="http://mms.vodafone.it/servlets/mms" mmsproxy="10.128.224.10" mmsport="80" type="mms"></apn>
+ <apn mcc="222" mnc="10" carrier="MMS Vodafone" apn="mms.vodafone.it" mmsc="http://mms.vodafone.it/servlets/mms" mmsproxy="10.128.224.10" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="222" mnc="10" carrier="PosteMobile MMS" apn="mms.postemobile.it" mmsc="http://mms.postemobile.it/servlets/mms" mmsproxy="10.128.224.10" mmsport="80" type="mms" authtype="0" mvno_match_data="PosteMobile" mvno_type="spn"></apn>
+ <apn mcc="222" mnc="88" carrier="MMS" apn="mms.windmobile.ca" mmsc="http://mms.windmobile.ca" mmsproxy="74.115.197.70" mmsport="8080" type="mms" mvno_match_data="FFFFFF00" mvno_type="gid"></apn>
+ <apn mcc="222" mnc="88" carrier="MMS WIND" apn="mms.wind" mmsc="http://mms.wind.it" mmsproxy="212.245.244.100" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="222" mnc="88" carrier="WIND MMS" apn="mms.wind" mmsc="http://mms.wind.it" mmsproxy="212.245.244.100" mmsport="8080" type="mms"></apn>
+ <apn mcc="222" mnc="88" carrier="WIND MMS" apn="mms.wind" mmsc="http://mms.wind.it" mmsproxy="212.245.244.100" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="222" mnc="99" carrier="3" apn="tre.it" mmsc="http://10.216.59.240:10021/mmsc" mmsproxy="62.13.171.3" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="222" mnc="99" carrier="3" apn="tre.it" mmsc="http://10.216.59.240:10021/mmsc" mmsproxy="62.13.171.3" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="222" mnc="99" carrier="3 ITA" apn="tre.it" mmsc="http://10.216.59.240:10021/mmsc" mmsproxy="62.13.171.3" mmsport="8799" type="default,mms" authtype="0" port="80"></apn>
+ <apn mcc="222" mnc="99" carrier="Fastweb" apn="apn.fastweb.it" mmsc="http://mms.fastweb.it/mms/wapenc" mmsproxy="10.0.65.9" mmsport="8080" type="default,mms" authtype="0" mvno_match_data="FASTWEB" mvno_type="spn" port="80"></apn>
+ <apn mcc="222" mnc="99" carrier="Fastweb WEB" apn="apn.fastweb.it" mmsc="http://mms.fastweb.it/mms/wapenc" mmsproxy="10.0.65.9" mmsport="8080" type="default,supl,mms" mvno_match_data="FASTWEB" mvno_type="spn"></apn>
+ <apn mcc="222" mnc="99" carrier="Fastweb WEB" apn="apn.fastweb.it" mmsc="http://mms.fastweb.it/mms/wapenc" mmsproxy="10.0.65.9" mmsport="8080" type="default,supl,mms" authtype="0" mvno_match_data="FASTWEB" mvno_type="spn"></apn>
+ <apn mcc="226" mnc="01" carrier="Vodafone MMS" apn="mms.vodafone.ro" mmsc="http://multimedia/servlets/mms" mmsproxy="193.230.161.231" mmsport="8080" type="mms" user="mms" password="vodafone" authtype="1"></apn>
+ <apn mcc="226" mnc="1" carrier="Vodafone MMS" apn="mms.vodafone.ro" mmsc="http://multimedia/servlets/mms" mmsproxy="193.230.161.231" mmsport="8080" type="mms" user="mms" password="vodafone" authtype="1"></apn>
+ <apn mcc="226" mnc="1" carrier="Vodafone RO MMS" apn="mms.vodafone.ro" mmsc="http://multimedia/servlets/mms" mmsproxy="193.230.161.231" mmsport="8080" type="mms" user="mms" password="vodafone" authtype="0"></apn>
+ <apn mcc="226" mnc="03" carrier="Cosmote MMS" apn="mms" mmsc="http://mmsc1.mms.cosmote.ro:8002" mmsproxy="10.252.1.62" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="226" mnc="3" carrier="Cosmote MMS" apn="mms" mmsc="http://mmsc1.mms.cosmote.ro:8002" mmsproxy="10.252.1.62" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="226" mnc="3" carrier="Cosmote MMS" apn="mms" mmsc="http://mmsc1.mms.cosmote.ro:8002" mmsproxy="10.252.1.62" mmsport="8080" type="mms" user="mms" password="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="226" mnc="05" carrier="MMS" apn="mms" mmsc="http://10.10.3.133:8002" mmsproxy="10.10.3.130" mmsport="8080" type="mms"></apn>
+ <apn mcc="226" mnc="5" carrier="MMS" apn="mms" mmsc="http://10.10.3.133:8002" mmsproxy="10.10.3.130" mmsport="8080" type="mms"></apn>
+ <apn mcc="226" mnc="06" carrier="Cosmote MMS" apn="mms" mmsc="http://mmsc1.mms.cosmote.ro:8002" mmsproxy="10.252.1.62" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="226" mnc="6" carrier="Cosmote MMS" apn="mms" mmsc="http://mmsc1.mms.cosmote.ro:8002" mmsproxy="10.252.1.62" mmsport="8080" type="mms" user="mms" password="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="226" mnc="6" carrier="Cosmote MMS" apn="mms" mmsc="http://mmsc1.mms.cosmote.ro:8002" mmsproxy="10.252.1.62" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="226" mnc="10" carrier="ORANGE RO MMS" apn="mms" mmsc="http://wap.mms.orange.ro:8002" mmsproxy="62.217.247.252" mmsport="8799" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="226" mnc="10" carrier="Orange MMS" apn="mms" mmsc="http://wap.mms.orange.ro:8002" mmsproxy="62.217.247.252" mmsport="8799" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="228" mnc="01" carrier="Swisscom MMS" apn="event.swisscom.ch" mmsc="http://mms.natel.ch:8079" mmsproxy="192.168.210.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="228" mnc="1" carrier="Swisscom MMS" apn="event.swisscom.ch" mmsc="http://mms.natel.ch:8079" mmsproxy="192.168.210.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="228" mnc="1" carrier="Swisscom MMS (CH)" apn="event.swisscom.ch" mmsc="http://mms.natel.ch:8079" mmsproxy="192.168.210.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="228" mnc="02" carrier="Sunrise MMS" apn="mms.sunrise.ch" mmsc="http://mmsc.sunrise.ch" mmsproxy="212.35.34.75" mmsport="8080" type="mms"></apn>
+ <apn mcc="228" mnc="2" carrier="Sunrise MMS" apn="mms.sunrise.ch" mmsc="http://mmsc.sunrise.ch" mmsproxy="212.35.34.75" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="228" mnc="2" carrier="Sunrise MMS (CH)" apn="mms.sunrise.ch" mmsc="http://mmsc.sunrise.ch" mmsproxy="212.35.34.75" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="228" mnc="03" carrier="Orange MMS" apn="mms" mmsc="http://192.168.151.3:8002" mmsproxy="192.168.151.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="228" mnc="3" carrier="Orange MMS" apn="mms" mmsc="http://192.168.151.3:8002" mmsproxy="192.168.151.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="228" mnc="3" carrier="Orange MMS (CH)" apn="mms" mmsc="http://192.168.151.3:8002" mmsproxy="192.168.151.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="230" mnc="01" carrier="T-Mobile CZ" apn="internet.t-mobile.cz" mmsc="http://mms" mmsproxy="010.000.000.010" mmsport="80" type="default,supl"></apn>
+ <apn mcc="230" mnc="01" carrier="T-Mobile CZ MMS" apn="mms.t-mobile.cz" mmsc="http://mms" mmsproxy="010.000.000.010" mmsport="80" type="mms"></apn>
+ <apn mcc="230" mnc="01" carrier="T-Mobile MMS" apn="mms.t-mobile.cz" mmsc="http://mms" mmsproxy="10.0.0.10" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="230" mnc="1" carrier="T-Mobile MMS" apn="mms.t-mobile.cz" mmsc="http://mms" mmsproxy="10.0.0.10" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="230" mnc="1" carrier="T-Mobile MMS" apn="mms.t-mobile.cz" mmsc="http://mms" mmsproxy="10.0.0.10" mmsport="80" type="mms" user="mms" password="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="230" mnc="02" carrier="O2 MMS" apn="mms" mmsc="http://mms.o2active.cz:8002" mmsproxy="160.218.160.218" mmsport="8080" type="mms"></apn>
+ <apn mcc="230" mnc="2" carrier="O2 MMS" apn="mms" mmsc="http://mms.o2active.cz:8002" mmsproxy="160.218.160.218" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="230" mnc="03" carrier="MMS" apn="mms" mmsc="http://mms" mmsproxy="10.11.10.111" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="230" mnc="03" carrier="Vodafone MMS" apn="mms" mmsc="http://mms" mmsproxy="10.11.10.111" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="230" mnc="3" carrier="MMS" apn="mms" mmsc="http://mms" mmsproxy="10.11.10.111" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="230" mnc="3" carrier="Vodafone CZ mms" apn="mms" mmsc="http://mms" mmsproxy="10.11.10.111" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="231" mnc="01" carrier="Orange SK MMS" apn="mms" mmsc="http://imms.orange.sk" mmsproxy="213.151.208.145" mmsport="8799" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="231" mnc="1" carrier="Orange MMS" apn="mms" mmsc="http://imms.orange.sk" mmsproxy="213.151.208.145" mmsport="8799" type="mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="231" mnc="1" carrier="Orange SK MMS" apn="mms" mmsc="http://imms.orange.sk" mmsproxy="213.151.208.145" mmsport="8799" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="231" mnc="02" carrier="T-Mobile MMS" apn="mms" mmsc="http://mms" mmsproxy="192.168.1.1" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="231" mnc="2" carrier="T-Mobile MMS" apn="mms" mmsc="http://mms" mmsproxy="192.168.1.1" mmsport="8080" type="mms" user="mms" password="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="231" mnc="06" carrier="O2 MMS" apn="o2mms" mmsc="http://mms.o2world.sk:8002" mmsproxy="10.97.1.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="231" mnc="6" carrier="O2 MMS" apn="o2mms" mmsc="http://mms.o2world.sk:8002" mmsproxy="10.97.1.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="231" mnc="6" carrier="O2-SK mms" apn="o2mms" mmsc="http://mms.o2world.sk:8002" mmsproxy="10.97.1.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="232" mnc="01" carrier="A1 MMS" apn="free.a1.net" mmsc="http://mmsc.a1.net" mmsproxy="194.48.124.71" mmsport="8001" type="mms" user="ppp@a1plus.at" password="ppp" authtype="1"></apn>
+ <apn mcc="232" mnc="1" carrier="MMS Account" apn="free.A1.net" mmsc="http://mmsc.A1.net" mmsproxy="194.48.124.85" mmsport="8001" type="mms" user="ppp@A1plus.at" password="ppp" authtype="2" server="*"></apn>
+ <apn mcc="232" mnc="03" carrier="T-Mobile A" apn="gprsinternet" mmsc="http://mmsc.t-mobile.at/servlets/mms" mmsproxy="010.012.000.020" mmsport="80" type="default,supl"></apn>
+ <apn mcc="232" mnc="03" carrier="T-Mobile A MMS" apn="gprsmms" mmsc="http://mmsc.t-mobile.at/servlets/mms" mmsproxy="010.012.000.020" mmsport="80" type="mms"></apn>
+ <apn mcc="232" mnc="03" carrier="T-Mobile MMS" apn="gprsmms" mmsc="http://mmsc.t-mobile.at/servlets/mms" mmsproxy="10.12.0.20" mmsport="80" type="mms" user="t-mobile" password="tm" authtype="1"></apn>
+ <apn mcc="232" mnc="3" carrier="T-Mobile MMS" apn="gprsmms" mmsc="http://mmsc.t-mobile.at/servlets/mms" mmsproxy="10.12.0.20" mmsport="80" type="mms" user="t-mobile" password="tm" authtype="1"></apn>
+ <apn mcc="232" mnc="3" carrier="T-Mobile MMS" apn="gprsmms" mmsc="http://mmsc.t-mobile.at/servlets/mms" mmsproxy="10.12.0.20" mmsport="80" type="mms" user="t-mobile" password="tm" authtype="1" protocol="IP"></apn>
+ <apn mcc="232" mnc="05" carrier="Planet 3" apn="drei.at" mmsc="http://mmsc" mmsproxy="213.94.78.133" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="232" mnc="5" carrier="Orange MMS (AT)" apn="orange.mms" mmsc="http://mmsc.orange.at/mms/wapenc" mmsproxy="194.24.128.118" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="232" mnc="5" carrier="Planet 3" apn="drei.at" mmsc="http://mmsc" mmsproxy="213.94.78.133" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="232" mnc="07" carrier="tele.ring mms" apn="mms" mmsc="http://relay.mms.telering.at" mmsproxy="212.95.31.50" mmsport="80" type="mms" user="wap@telering.at" password="wap" authtype="1"></apn>
+ <apn mcc="232" mnc="7" carrier="telering mms" apn="mms" mmsc="http://relay.mms.telering.at" mmsproxy="212.95.31.50" mmsport="80" type="mms" user="wap@telering.at" password="wap" authtype="1" protocol="IP"></apn>
+ <apn mcc="232" mnc="10" carrier="3 AT" apn="drei.at" mmsc="http://mmsc" mmsproxy="213.94.78.133" mmsport="8799" type="default,mms" authtype="0"></apn>
+ <apn mcc="232" mnc="10" carrier="Planet3" apn="drei.at" mmsc="http://mmsc" mmsproxy="213.94.78.133" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="232" mnc="10" carrier="Planet3" apn="drei.at" mmsc="http://mmsc" mmsproxy="213.94.78.133" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="232" mnc="11" carrier="data.bob MMS" apn="mms.bob.at" mmsc="http://mmsc.bob.at" mmsproxy="194.48.124.7" mmsport="8001" type="mms" user="data@bob.at" password="ppp" authtype="1"></apn>
+ <apn mcc="232" mnc="11" carrier="data.bob mms" apn="mms.bob.at" mmsc="http://mmsc.bob.at" mmsproxy="194.48.124.7" mmsport="8001" type="mms" user="data@bob.at" password="ppp" authtype="0"></apn>
+ <apn mcc="234" mnc="02" carrier="O2 MMS Postpay" apn="wap.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="o2wap" password="password"></apn>
+ <apn mcc="234" mnc="02" carrier="O2 MMS Prepay" apn="payandgo.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="payandgo" password="password"></apn>
+ <apn mcc="234" mnc="08" carrier="BT One Phone MMS" apn="mms.btonephone.com" mmsc="http://MMSC/" mmsproxy="proxy.btonephone.com" mmsport="8080" type="mms" mvno_match_data="B2" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="10" carrier="O2 MMS" apn="wap.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="o2wap" password="password" authtype="0"></apn>
+ <apn mcc="234" mnc="10" carrier="O2 MMS" apn="wap.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="o2wap" password="password" authtype="1"></apn>
+ <apn mcc="234" mnc="10" carrier="O2 Pay &amp; Go" apn="payandgo.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="default,mms" user="payandgo" password="password" authtype="0"></apn>
+ <apn mcc="234" mnc="10" carrier="O2 Pay &amp; Go" apn="payandgo.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="default,supl,mms" user="payandgo" password="password" proxy="82.132.254.1" port="8080"></apn>
+ <apn mcc="234" mnc="10" carrier="O2 Pay &amp; Go" apn="payandgo.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="default,supl,mms" user="payandgo" password="password" authtype="3" proxy="82.132.254.1" port="8080"></apn>
+ <apn mcc="234" mnc="10" carrier="TESCO" apn="prepay.tesco-mobile.com" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="193.113.200.195" mmsport="8080" type="default,supl,mms" user="tescowap" password="password" authtype="1" mvno_match_data="0A" mvno_type="gid" proxy="193.113.200.195" port="8080"></apn>
+ <apn mcc="234" mnc="10" carrier="Tesco Mobile" apn="prepay.tesco-mobile.com" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="193.113.200.195" mmsport="8080" type="default,mms" user="tescowap" password="password" authtype="1" mvno_match_data="TESCO" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="10" carrier="giffgaff" apn="giffgaff.com" mmsc="http://mmsc.mediamessaging.co.uk:8002" mmsproxy="193.113.200.195" mmsport="8080" user="giffgaff" password="password" authtype="1" mvno_match_data="giffgaff" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="11" carrier="O2 MMS Postpay" apn="wap.o2.co.uk" mmsc="http://mmsc.mms.02.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="o2wap" password="password"></apn>
+ <apn mcc="234" mnc="11" carrier="O2 MMS Postpay" apn="wap.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="o2wap" password="password"></apn>
+ <apn mcc="234" mnc="11" carrier="O2 MMS Prepay" apn="payandgo.o2.co.uk" mmsc="http://mmsc.mms.02.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="payandgo" password="password"></apn>
+ <apn mcc="234" mnc="11" carrier="O2 MMS Prepay" apn="payandgo.o2.co.uk" mmsc="http://mmsc.mms.o2.co.uk:8002" mmsproxy="82.132.254.1" mmsport="8080" type="mms" user="payandgo" password="password"></apn>
+ <apn mcc="234" mnc="15" carrier="ASDA Mobile" apn="asdamobiles.co.uk" mmsc="http://mms.asdamobiles.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,mms" user="wap" password="wap" authtype="0" mvno_match_data="ASDA Mobile" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="15" carrier="ASDA WAP" apn="asdamobiles.co.uk" mmsc="http://mms.asdamobiles.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" user="wap" password="wap" authtype="1" mvno_match_data="A0" mvno_type="gid" proxy="212.183.137.12" port="8799"></apn>
+ <apn mcc="234" mnc="15" carrier="BT Mobile" apn="btmobile.bt.com" mmsc="http://mms.bt.com/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" user="bt" password="bt" authtype="1" mvno_match_data="B3" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="15" carrier="Contract WAP" apn="wap.vodafone.co.uk" mmsc="http://mms.vodafone.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="234" mnc="15" carrier="Lebara Internet" apn="uk.lebara.mobi" mmsc="http://mms.lebara.co.uk/servlets/mms" mmsproxy="212.183.137.012" mmsport="8799" type="default,supl,mms" user="wap" password="wap" authtype="1" mvno_match_data="Lebara" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="15" carrier="PAYG WAP" apn="pp.vodafone.co.uk" mmsc="http://mms.vodafone.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="234" mnc="15" carrier="Sainsbury&#39;s PAYG" apn="payg.mobilebysainsburys.co.uk" mmsc="http://mms.mobilebysainsburys.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" authtype="1" mvno_match_data="Sainsbury&#39;s" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="15" carrier="TalkTalk" apn="mobile.talktalk.co.uk" mmsc="http://mms.talktalk.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,mms" user="wap" password="wap" authtype="0" mvno_match_data="TalkTalk" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="15" carrier="TalkTalk WAP" apn="mobile.talktalk.co.uk" mmsc="http://mms.talktalk.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" mvno_match_data="70" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="15" carrier="TalkTalk WAP" apn="mobile.talktalk.co.uk" mmsc="http://mms.talktalk.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" authtype="0" mvno_match_data="70" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="15" carrier="Talkmob Internet" apn="talkmobile.co.uk" mmsc="http://mms.talkmobile.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,mms" password="wap" authtype="0" mvno_match_data="Talkmobile" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="15" carrier="Talkmob PAYG Int" apn="payg.talkmobile.co.uk" mmsc="http://mms.talkmobile.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,mms" password="wap" authtype="0" mvno_match_data="Talkmobile" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="15" carrier="Talkmob PAYG WAP" apn="payg.talkmobile.co.uk" mmsc="http://mms.talkmobile.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" user="wap" password="wap" authtype="1" mvno_match_data="C1" mvno_type="gid" proxy="212.183.137.12" port="8799"></apn>
+ <apn mcc="234" mnc="15" carrier="Talkmob WAP" apn="talkmobile.co.uk" mmsc="http://mms.talkmobile.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" user="wap" password="wap" authtype="1" mvno_match_data="C1" mvno_type="gid" proxy="212.183.137.12" port="8799"></apn>
+ <apn mcc="234" mnc="15" carrier="Vodafone UK" apn="wap.vodafone.co.uk" mmsc="http://mms.vodafone.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="234" mnc="15" carrier="Vodafone UK Prepay" apn="pp.vodafone.co.uk" mmsc="http://mms.vodafone.co.uk/servlets/mms" mmsproxy="212.183.137.12" mmsport="8799" type="default,supl,mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="234" mnc="20" carrier="3" apn="three.co.uk" mmsc="http://mms.um.three.co.uk:10021/mmsc" mmsproxy="mms.three.co.uk" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="234" mnc="20" carrier="3" apn="three.co.uk" mmsc="http://mms.um.three.co.uk:10021/mmsc" mmsproxy="mms.three.co.uk" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="234" mnc="20" carrier="3 UK" apn="three.co.uk" mmsc="http://mms.um.three.co.uk:10021/mmsc" mmsproxy="217.171.129.2" mmsport="8799" type="default,mms" authtype="0"></apn>
+ <apn mcc="234" mnc="30" carrier="BT MMS" apn="mms.bt.com" mmsc="http://MMS/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="bt" password="bt" authtype="1" mvno_match_data="B3" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="30" carrier="BT MMS" apn="mms.bt.com" mmsc="http://MMS/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="bt" password="bt" authtype="1" mvno_match_data="C3" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="30" carrier="BT One Phone MMS" apn="mms.btonephone.com" mmsc="http://MMSC/" mmsproxy="proxy.btonephone.com" mmsport="8080" type="mms" mvno_match_data="B2" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="30" carrier="EE MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="30" carrier="MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="30" carrier="T-Mobile UK" apn="general.t-mobile.uk" mmsc="http://mmsc.t-mobile.co.uk:8002" mmsproxy="149.254.201.135" mmsport="8080"></apn>
+ <apn mcc="234" mnc="30" carrier="Virgin Media Mobile Internet" apn="goto.virginmobile.uk" mmsc="http://mms.virginmobile.co.uk:8002" mmsproxy="193.30.166.2" mmsport="8080" type="default,mms" user="user" authtype="0" mvno_match_data="Virgin" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="30" carrier="Virgin Media Mobile Internet" apn="goto.virginmobile.uk" mmsc="http://mms.virginmobile.co.uk:8002" mmsproxy="193.30.166.2" mmsport="8080" type="default,supl,mms" user="user" authtype="1" mvno_match_data="28" mvno_type="gid"></apn>
+ <apn mcc="234" mnc="31" carrier="EE MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="32" carrier="EE MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="33" carrier="EE MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="33" carrier="MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="33" carrier="MMS" apn="tslmms" mmsc="http://mms/" mmsproxy="193.35.133.194" mmsport="8080" type="mms" user="wap" password="wap" authtype="1" mvno_match_data="LIFE" mvno_type="spn"></apn>
+ <apn mcc="234" mnc="34" carrier="EE MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="234" mnc="50" carrier="Jersey Telecom" apn="mms" mmsc="http://mms.surfmail.com/mmsc" mmsproxy="212.9.19.199" mmsport="3130" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="234" mnc="55" carrier="Sure Picture Messaging" apn="mms" mmsc="http://mmsc.gprs.cw.com/" mmsproxy="10.0.3.101" mmsport="80" type="mms"></apn>
+ <apn mcc="234" mnc="58" carrier="Manx Telecom Contract MMS" apn="mms.manxpronto.net" mmsc="http://mms.manxpronto.net:8002" mmsproxy="195.10.99.46" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="234" mnc="58" carrier="Manx Telecom Prepay MMS" apn="mms.prontogo.net" mmsc="http://mms.manxpronto.net:8002" mmsproxy="195.10.99.41" mmsport="8080" type="mms" user="mmsgo" password="mmsgo"></apn>
+ <apn mcc="234" mnc="86" carrier="EE MMS" apn="eezone" mmsc="http://mms/" mmsproxy="149.254.201.135" mmsport="8080" type="mms" user="eesecure" password="secure" authtype="1"></apn>
+ <apn mcc="235" mnc="94" carrier="3" apn="three.co.uk" mmsc="http://mms.um.three.co.uk:10021/mmsc" mmsproxy="mms.three.co.uk" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="238" mnc="01" carrier="DK TDC mms" apn="mms" mmsc="http://192.168.241.114:8002" mmsproxy="194.182.251.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="238" mnc="01" carrier="TDC MMS" apn="mms" mmsc="http://mmsc.tdc.dk:8002" mmsproxy="194.182.251.15" mmsport="8080" type="mms" mvno_match_data="2380101xxxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="238" mnc="01" carrier="Telmore MMS" apn="mms" mmsc="http://192.168.241.114:8002" mmsproxy="194.182.251.15" mmsport="8080" type="mms" mvno_match_data="TELMORE" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="1" carrier="TDC MMS" apn="mms" mmsc="http://mmsc.tdc.dk:8002" mmsproxy="194.182.251.15" mmsport="8080" type="mms" authtype="0" mvno_match_data="2380101xxxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="238" mnc="1" carrier="TELMORE MMS" apn="mms" mmsc="http://192.168.241.114:8002" mmsproxy="194.182.251.15" mmsport="8080" type="mms" authtype="0" mvno_match_data="TELMORE" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="02" carrier="BiBoB" apn="mms.bibob.dk" mmsc="http://mms.telenor.dk" mmsproxy="212.88.64.8" mmsport="8080" type="mms" mvno_match_data="BiBoB" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="02" carrier="CBB MMS" apn="telenor" mmsc="http://mms.telenor.dk" mmsproxy="212.88.64.8" mmsport="8080" type="mms" mvno_match_data="CBB Mobil" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="02" carrier="Telenor MMS" apn="telenor" mmsc="http://mms.telenor.dk" mmsproxy="212.88.64.8" mmsport="8080" type="mms"></apn>
+ <apn mcc="238" mnc="2" carrier="Sonofon mms" apn="sonofon" mmsc="http://mms.sonofon.dk" mmsproxy="212.88.64.8" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="238" mnc="2" carrier="Telenor MMS" apn="telenor" mmsc="http://mms.telenor.dk" mmsproxy="212.88.64.8" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="238" mnc="06" carrier="3" apn="data.tre.dk" mmsc="http://mms.3.dk/" mmsproxy="mmsproxy.3.dk" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="238" mnc="6" carrier="3" apn="data.tre.dk" mmsc="http://mms.3.dk/" mmsproxy="mmsproxy.3.dk" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="238" mnc="6" carrier="3 DK" apn="data.tre.dk" mmsc="http://mms.3.dk" mmsproxy="172.16.1.25" mmsport="8799" type="default,mms" authtype="0" port="80"></apn>
+ <apn mcc="238" mnc="20" carrier="Call me MMS" apn="mmsSP" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="8080" type="mms" mvno_match_data="Call me" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="20" carrier="Call me MMS" apn="mmsSP" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="8080" type="mms" authtype="0" mvno_match_data="Call me" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="20" carrier="Call me MMS" apn="mmssp" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="80" type="mms" authtype="0" mvno_match_data="Call me" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="20" carrier="DLG MMS" apn="mmssp" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="80" type="mms" authtype="0" mvno_match_data="DLG" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="20" carrier="DLG Tele MMS" apn="mmsSP" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="8080" type="mms" mvno_match_data="DLG Tele" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="20" carrier="DLG Tele MMS" apn="mmsSP" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="8080" type="mms" authtype="0" mvno_match_data="DLG Tele" mvno_type="spn"></apn>
+ <apn mcc="238" mnc="20" carrier="Telia MMS" apn="www.mms.mtelia.dk" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="8080" type="mms" mvno_match_data="2382010x" mvno_type="imsi"></apn>
+ <apn mcc="238" mnc="20" carrier="Telia MMS" apn="www.mms.mtelia.dk" mmsc="http://mms.telia.dk" mmsproxy="193.209.134.131" mmsport="8080" type="mms" mvno_match_data="2382030x" mvno_type="imsi"></apn>
+ <apn mcc="238" mnc="77" carrier="Telenor MMS" apn="telenor" mmsc="http://mms.telenor.dk" mmsproxy="212.88.64.8" mmsport="8080" type="mms"></apn>
+ <apn mcc="240" mnc="01" carrier="Halebop MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="80" type="mms" mvno_match_data="240017xxxxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="240" mnc="01" carrier="Telia MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="80" type="mms"></apn>
+ <apn mcc="240" mnc="1" carrier="TELIA S mms" apn="mms.telia.se" mmsc="http://mmss/" mmsproxy="193.209.134.132" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="240" mnc="1" carrier="Telia MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="240" mnc="02" carrier="3" apn="data.tre.se" mmsc="http://mms.tre.se" mmsproxy="mmsproxy.tre.se" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="240" mnc="2" carrier="3" apn="data.tre.se" mmsc="http://mms.tre.se" mmsproxy="mmsproxy.tre.se" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="240" mnc="2" carrier="3 SE" apn="data.tre.se" mmsc="http://mms.tre.se" mmsproxy="172.16.1.25" mmsport="8799" type="default,mms" authtype="0"></apn>
+ <apn mcc="240" mnc="04" carrier="3" apn="data.tre.se" mmsc="http://mms.tre.se" mmsproxy="mmsproxy.tre.se" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="240" mnc="04" carrier="Telenor SE" apn="services.telenor.se" mmsc="http://mms" mmsproxy="172.30.253.241" mmsport="8799" type="default,supl,mms" mvno_match_data="Telenor SE" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="4" carrier="3" apn="data.tre.se" mmsc="http://mms.tre.se" mmsproxy="mmsproxy.tre.se" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="240" mnc="05" carrier="Halebop MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="80" type="mms" mvno_match_data="Halebop" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="05" carrier="Tele2 MMS" apn="4g.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" mvno_match_data="Tele2" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="05" carrier="Tele2 MMS 3G" apn="internet.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" mvno_match_data="Tele2" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="05" carrier="Telia MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="80" type="mms" mvno_match_data="Telia" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="5" carrier="Halebop MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="80" type="mms" mvno_match_data="Halebop" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="5" carrier="Tele2 MMS 3G" apn="internet.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" mvno_match_data="Tele2" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="06" carrier="Telenor MMS" apn="services.telenor.se" mmsc="http://mms" mmsproxy="173.30.253.241" mmsport="8799" type="mms"></apn>
+ <apn mcc="240" mnc="07" carrier="Tele2" apn="internet.tele2.no" mmsc="http://mmsc.tele2.no" mmsproxy="193.12.40.14" mmsport="8080" type="default,supl,mms" protocol="IP"></apn>
+ <apn mcc="240" mnc="07" carrier="Tele2 MMS" apn="4g.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms"></apn>
+ <apn mcc="240" mnc="07" carrier="Tele2 MMS" apn="internet.tele2.no" mmsc="http://mmsc.tele2.no" mmsproxy="193.12.40.14" mmsport="8080" type="mms" mvno_match_data="2400768xxxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="240" mnc="07" carrier="Tele2 MMS 3G" apn="internet.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms"></apn>
+ <apn mcc="240" mnc="7" carrier="Tele2 MMS" apn="4g.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="240" mnc="7" carrier="Tele2 MMS 3G" apn="internet.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="240" mnc="7" carrier="Tele2Comviq Internet" apn="4g.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="240" mnc="08" carrier="Telenor SE" apn="services.telenor.se" mmsc="http://mms" mmsproxy="172.30.253.241" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="240" mnc="8" carrier="Telenor MMS (240.08)" apn="services.telenor.se" mmsc="http://mms" mmsproxy="172.30.253.241" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="240" mnc="8" carrier="Telenor SE" apn="services.telenor.se" mmsc="http://mms" mmsproxy="172.30.253.241" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="240" mnc="09" carrier="Telenor MMS" apn="services.telenor.se" mmsc="http://mms" mmsproxy="173.30.253.241" mmsport="8799" type="mms"></apn>
+ <apn mcc="240" mnc="10" carrier="Spring MMS" apn="mms.springmobil.se" mmsc="http://mms.springmobil.se" mmsproxy="213.88.184.37" mmsport="8080" type="mms"></apn>
+ <apn mcc="240" mnc="017" carrier="Halebop MMS" apn="mms.telia.se" mmsc="http://mmss" mmsproxy="193.209.134.132" mmsport="9201" type="mms" user="mms" password="telia"></apn>
+ <apn mcc="240" mnc="24" carrier="Tele2 MMS" apn="4g.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" mvno_match_data="Tele2" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="24" carrier="Tele2 MMS 3G" apn="internet.tele2.se" mmsc="http://mmsc.tele2.se" mmsproxy="130.244.202.30" mmsport="8080" type="mms" mvno_match_data="Tele2" mvno_type="spn"></apn>
+ <apn mcc="240" mnc="24" carrier="Telenor SE" apn="services.telenor.se" mmsc="http://mms" mmsproxy="172.30.253.241" mmsport="8799" type="default,supl,mms" mvno_match_data="Telenor SE" mvno_type="spn"></apn>
+ <apn mcc="242" mnc="01" carrier="Telenor" apn="telenor" mmsc="http://mmsc" mmsproxy="10.10.10.11" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="242" mnc="01" carrier="Ventelo MMS" apn="mms.ventelo.no" mmsc="http://mmsc/" mmsproxy="10.10.10.11" mmsport="8080" type="mms" user="ventelo" password="1111" authtype="1" mvno_match_data="24201700xxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="242" mnc="1" carrier="N Telenor" apn="telenor" mmsc="http://mmsc/" mmsproxy="10.10.10.11" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="242" mnc="1" carrier="Telenor" apn="telenor" mmsc="http://mmsc" mmsproxy="10.10.10.11" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="242" mnc="02" carrier="Chess MMS" apn="netcom" mmsc="http://mm/" mmsproxy="212.169.66.4" mmsport="8080" type="mms" user="mms" password="netcom" authtype="1" mvno_match_data="2420256xxxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="242" mnc="02" carrier="NetCom" apn="netcom" mmsc="http://mm/" mmsproxy="212.169.66.4" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="242" mnc="2" carrier="N NetCom mms" apn="mms.netcom.no" mmsc="http://mms/" mmsproxy="193.209.134.133" mmsport="80" type="mms" user="mms" password="netcom" authtype="0"></apn>
+ <apn mcc="242" mnc="2" carrier="NetCom" apn="netcom" mmsc="http://mm/" mmsproxy="212.169.66.4" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="242" mnc="04" carrier="Tele2 Internet" apn="internet.tele2.no" mmsc="http://mmsc.tele2.no" mmsproxy="193.12.40.14" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="242" mnc="05" carrier="NwN MMS" apn="mms" mmsc="http://mms.nwn.no" mmsproxy="188.149.250.10" mmsport="80" type="mms"></apn>
+ <apn mcc="244" mnc="03" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dna.fi" mmsproxy="10.1.1.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="244" mnc="3" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dna.fi" mmsproxy="10.1.1.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="3" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="04" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms"></apn>
+ <apn mcc="244" mnc="4" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms" authtype="3"></apn>
+ <apn mcc="244" mnc="4" carrier="DNA MMS 1" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="05" carrier="Elisa MMS" apn="mms" mmsc="http://mms.elisa.fi" mmsproxy="213.161.41.57" mmsport="80" type="mms"></apn>
+ <apn mcc="244" mnc="05" carrier="Saunalahti MMS" apn="mms.saunalahti.fi" mmsc="http://mms.saunalahti.fi:8002/" mmsproxy="62.142.4.197" mmsport="8080" type="mms" mvno_match_data="2440541" mvno_type="imsi"></apn>
+ <apn mcc="244" mnc="5" carrier="Elisa MMS" apn="mms" mmsc="http://mms.elisa.fi" mmsproxy="213.161.41.57" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="10" carrier="TDC MMS" apn="mms.song.fi" mmsc="http://mms.song.fi" mmsproxy="213.161.41.58" mmsport="80" type="mms"></apn>
+ <apn mcc="244" mnc="12" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dna.fi" mmsproxy="10.1.1.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="244" mnc="12" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dna.fi" mmsproxy="10.1.1.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="12" carrier="DNA MMS 2" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="12" carrier="DNA Pro MMS" apn="mms.dnapro.fi" mmsc="http://mmsc.dnapro.fi/" mmsproxy="10.1.1.21" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="244" mnc="12" carrier="TDC MMS Finland" apn="mms.tdc.fi" mmsc="http://mmsc.tdc.fi" mmsproxy="10.1.12.2" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="244" mnc="13" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms"></apn>
+ <apn mcc="244" mnc="13" carrier="DNA MMS" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms" authtype="3"></apn>
+ <apn mcc="244" mnc="13" carrier="DNA MMS 3" apn="mms" mmsc="http://mmsc.dnafinland.fi/" mmsproxy="10.1.1.2" mmsport="8080" type="mms" user="dna" password="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="21" carrier="Saunalahti MMS" apn="mms.saunalahti.fi" mmsc="http://mms.saunalahti.fi:8002/" mmsproxy="62.142.4.197" mmsport="8080" type="mms"></apn>
+ <apn mcc="244" mnc="21" carrier="Saunalahti MMS" apn="mms.saunalahti.fi" mmsc="http://mms.saunalahti.fi:8002/" mmsproxy="62.142.4.197" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="91" carrier="SONERA MMS" apn="wap.sonera.net" mmsc="http://mms.sonera.fi:8002" mmsproxy="195.156.25.33" mmsport="80" type="mms"></apn>
+ <apn mcc="244" mnc="91" carrier="SONERA MMS" apn="wap.sonera.net" mmsc="http://mms.sonera.fi:8002" mmsproxy="195.156.25.33" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="244" mnc="91" carrier="TeliaSonera MMS" apn="wap.sonera.net" mmsc="http://mms.sonera.net:8002/" mmsproxy="195.156.25.33" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="246" mnc="01" carrier="Omnitel MMS" apn="gprs.mms.lt" mmsc="http://mms.omnitel.net:8002/" mmsproxy="194.176.32.149" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="246" mnc="1" carrier="OMNI MMS" apn="gprs.mms.lt" mmsc="http://mms.omnitel.net:8002/" mmsproxy="194.176.32.149" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="246" mnc="1" carrier="Omnitel MMS" apn="gprs.mms.lt" mmsc="http://mms.omnitel.net:8002/" mmsproxy="194.176.32.149" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="246" mnc="02" carrier="Bite MMS" apn="mms" mmsc="http://mmsc/servlets/mms" mmsproxy="192.168.150.2" mmsport="8080" type="mms" user="mms@mms" password="mms" authtype="1"></apn>
+ <apn mcc="246" mnc="2" carrier="Bite MMS" apn="mms" mmsc="http://mmsc" mmsproxy="192.168.150.2" mmsport="8080" type="mms" user="mms@mms" password="mms" authtype="0"></apn>
+ <apn mcc="246" mnc="2" carrier="Bite MMS" apn="mms" mmsc="http://mmsc/servlets/mms" mmsproxy="192.168.150.2" mmsport="8080" type="mms" user="mms@mms" password="mms" authtype="1"></apn>
+ <apn mcc="246" mnc="03" carrier="Tele2 Internet LT" apn="internet.tele2.lt" mmsc="http://mmsc.tele2.lt/" mmsproxy="193.12.40.29" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="246" mnc="3" carrier="MMS" apn="mms.tele2.lt" mmsc="http://mmsc.tele2.lt" mmsproxy="193.12.40.29" mmsport="8080" type="mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="246" mnc="3" carrier="Prepaid MMS" apn="mms.tele2.lt" mmsc="http://mmsc.tele2.lt" mmsproxy="193.12.40.29" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="246" mnc="3" carrier="Tele2 Internet LT" apn="internet.tele2.lt" mmsc="http://mmsc.tele2.lt/" mmsproxy="193.12.40.29" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="246" mnc="081" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="246" mnc="081" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="246" mnc="081" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="246" mnc="81" carrier="EHRPD - VZW Test CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="246" mnc="81" carrier="LTE - VZW Test CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="246" mnc="81" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="246" mnc="81" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="246" mnc="81" carrier="Test Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="247" mnc="01" carrier="LMT MMS" apn="internet.lmt.lv" mmsc="http://mmsc.lmt.lv/mmsc" type="mms"></apn>
+ <apn mcc="247" mnc="1" carrier="LMT MMS" apn="internet.lmt.lv" mmsc="http://mmsc.lmt.lv/mmsc" type="mms" authtype="0"></apn>
+ <apn mcc="247" mnc="1" carrier="LMT MMS" apn="mms.lmt.lv" mmsc="http://mmsc.lmt.lv/mmsc" mmsproxy="212.93.97.201" mmsport="8080" type="mms" user="lmt" password="lmt" authtype="0" port="8080"></apn>
+ <apn mcc="247" mnc="02" carrier="Tele2 LV MMS" apn="mms.tele2.lv" mmsc="http://mmsc.tele2.lv/" mmsproxy="193.12.40.38" mmsport="8080" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="247" mnc="2" carrier="MMS" apn="mms.tele2.lv" mmsc="http://mmsc.tele2.lv" mmsproxy="193.12.40.38" mmsport="8080" type="mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="247" mnc="2" carrier="Prepaid MMS" apn="mms.tele2.lv" mmsc="http://mmsc.tele2.lv" mmsproxy="193.12.40.38" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="247" mnc="05" carrier="Bite LV MMS" apn="mms" mmsc="http://mmsc/servlets/mms" mmsproxy="192.168.150.2" mmsport="8080" type="mms" user="mms@mms" password="mms" authtype="1"></apn>
+ <apn mcc="247" mnc="5" carrier="Bite MMS" apn="MMS" mmsc="http://mmsc" mmsproxy="192.168.150.2" mmsport="8080" type="mms" user="mms@mms" password="mms" authtype="0"></apn>
+ <apn mcc="248" mnc="01" carrier="EMT MMS" apn="mms.emt.ee" mmsc="http://mms.emt.ee/servlets/mms" mmsproxy="217.71.32.82" mmsport="8080" type="mms"></apn>
+ <apn mcc="248" mnc="01" carrier="Send" apn="send.ee" mmsc="http://mms.emt.ee/servlets/mms" mmsproxy="217.71.32.82" mmsport="8080" type="default,supl,mms" mvno_match_data="248010x2" mvno_type="imsi"></apn>
+ <apn mcc="248" mnc="01" carrier="Send" apn="send.ee" mmsc="http://mms.emt.ee/servlets/mms" mmsproxy="217.71.32.82" mmsport="8080" type="default,supl,mms" mvno_match_data="248010x3" mvno_type="imsi"></apn>
+ <apn mcc="248" mnc="1" carrier="EMT MMS" apn="mms.emt.ee" mmsc="http://mms.emt.ee/servlets/mms" mmsproxy="217.71.32.82" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="248" mnc="1" carrier="EMT MMS" apn="mms.emt.ee" mmsc="http://mms.emt.ee/servlets/mms" mmsproxy="217.71.32.82" mmsport="8080" type="mms" password="password" authtype="0"></apn>
+ <apn mcc="248" mnc="02" carrier="Elisa MMS" apn="mms" mmsc="http://194.204.2.10" mmsproxy="194.204.2.6" mmsport="8000" type="mms"></apn>
+ <apn mcc="248" mnc="2" carrier="Elisa MMS" apn="mms" mmsc="194.204.2.10" mmsproxy="194.204.2.6" mmsport="8000" type="mms" authtype="0"></apn>
+ <apn mcc="248" mnc="2" carrier="Elisa MMS" apn="mms" mmsc="http://194.204.2.10" mmsproxy="194.204.2.6" mmsport="8000" type="mms" authtype="0"></apn>
+ <apn mcc="248" mnc="03" carrier="Smart/Ultra MMS" apn="internet.tele2.ee" mmsc="http://mmsc.tele2.ee" mmsproxy="193.12.40.6" mmsport="8080" type="mms" mvno_match_data="24803000x" mvno_type="imsi"></apn>
+ <apn mcc="248" mnc="03" carrier="Smart/Ultra MMS" apn="internet.tele2.ee" mmsc="http://mmsc.tele2.ee" mmsproxy="193.12.40.6" mmsport="8080" type="mms" mvno_match_data="24803005x" mvno_type="imsi"></apn>
+ <apn mcc="248" mnc="03" carrier="Tele2 MMS" apn="mms.tele2.ee" mmsc="http://mmsc.tele2.ee" mmsproxy="193.12.40.6" mmsport="8080" type="mms"></apn>
+ <apn mcc="248" mnc="03" carrier="Tele2 MMS" apn="mms.tele2.ee" mmsc="http://mmsc.tele2.ee" mmsproxy="193.12.40.6" mmsport="8080" type="mms" mvno_match_data="24803005x" mvno_type="imsi"></apn>
+ <apn mcc="250" mnc="01" carrier="MTS MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms" user="mts" password="mts" authtype="1"></apn>
+ <apn mcc="250" mnc="01" carrier="МТS MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="1" carrier="MTS MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="9201" type="mms" user="mts" password="mts" authtype="0"></apn>
+ <apn mcc="250" mnc="002" carrier="Megafon MMS" apn="mms" mmsc="http://mmsc:8002" mmsproxy="10.10.10.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="250" mnc="02" carrier="Megafon MMS" apn="mms" mmsc="http://mmsc:8002" mmsproxy="10.10.10.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="250" mnc="2" carrier="MegaFon MMS" apn="mms" mmsc="http://mmsc:8002" mmsproxy="10.10.10.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="250" mnc="2" carrier="Megafon MMS" apn="mms" mmsc="http://mmsc:8002" mmsproxy="10.10.10.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="250" mnc="03" carrier="NCC" apn="mms" mmsc="http://10.0.3.50" mmsproxy="10.0.3.20" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="04" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="05" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="07" carrier="mms" apn="mms.smarts.ru" mmsc="http://172.24.120.135/mms/wapenc" mmsproxy="172.24.128.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="10" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="11" carrier="Beeline MMS" apn="mms.beeline.ru" mmsc="http://mms/" mmsproxy="192.168.094.023" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="12" carrier="BWC MMS" apn="mms.bwc.ru" mmsc="http://mmsc/mms" mmsproxy="10.10.17.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="13" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="16" carrier="MMS" apn="mms.ntc" mmsc="http://mmsc.vntc.ru/was" mmsproxy="80.243.64.68" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="17" carrier="MMS" apn="mms.usi.ru" mmsc="http://mms" mmsproxy="192.168.168.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="20" carrier="TELE2 MMS" apn="mms.tele2.ru" mmsc="http://mmsc.tele2.ru" mmsproxy="193.12.40.65" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="20" carrier="TELE2 MMS" apn="mms.tele2.ru" mmsc="http://mmsc.tele2.ru" mmsproxy="193.12.40.65" mmsport="9201" type="mms"></apn>
+ <apn mcc="250" mnc="39" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="44" carrier="Beeline MMS" apn="mms.beeline.ru" mmsc="http://mms/" mmsproxy="192.168.94.23" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="92" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="93" carrier="МТС Центр MMS" apn="mms.mts.ru" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="250" mnc="99" carrier="Beeline MMS" apn="mms.beeline.ru" mmsc="http://mms/" mmsproxy="192.168.94.23" mmsport="8080" type="mms" user="beeline" password="beeline" authtype="0"></apn>
+ <apn mcc="250" mnc="99" carrier="Beeline MMS" apn="mms.beeline.ru" mmsc="http://mms/" mmsproxy="192.168.94.23" mmsport="8080" type="mms" user="beeline" password="beeline" authtype="1"></apn>
+ <apn mcc="255" mnc="01" carrier="MTS MMS" apn="mms" mmsc="http://mmsc:8002/" mmsproxy="192.168.10.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="255" mnc="1" carrier="MTS MMS" apn="mms" mmsc="http://mmsc:8002/" mmsproxy="192.168.10.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="255" mnc="1" carrier="MTS UKR MMS" apn="mms" mmsc="http://mms/" mmsproxy="192.168.10.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="255" mnc="02" carrier="Beeline MMS" apn="mms.beeline.ua" mmsc="http://mms/" mmsproxy="172.29.18.192" mmsport="8080" type="mms"></apn>
+ <apn mcc="255" mnc="2" carrier="Beeline MMS" apn="mms.beeline.ua" mmsc="http://mms/" mmsproxy="172.29.18.192" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="255" mnc="2" carrier="Beeline UKR MMS" apn="mms.beeline.ua" mmsc="http://mms/" mmsproxy="172.29.18.192" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="255" mnc="03" carrier="Djuice MMS" apn="mms.djuice.com.ua" mmsc="http://mms.kyivstar.net" mmsproxy="10.10.10.10" mmsport="8080" type="mms" user="djuice" password="mms" authtype="1" mvno_match_data="DJUICE" mvno_type="spn"></apn>
+ <apn mcc="255" mnc="03" carrier="Kyivstar MMS" apn="mms.kyivstar.net" mmsc="http://mms.kyivstar.net" mmsproxy="10.10.10.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="255" mnc="3" carrier="UA-KYIVSTAR MMS" apn="mms.kyivstar.net" mmsc="http://mms.kyivstar.net" mmsproxy="10.10.10.10" mmsport="9201" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="255" mnc="06" carrier="Life:) MMS" apn="mms" mmsc="http://mms.life.com.ua/cmmsc/post" mmsproxy="212.58.162.230" mmsport="8080" type="mms"></apn>
+ <apn mcc="255" mnc="6" carrier="life:) MMS" apn="mms" mmsc="http://mms.life.com.ua/cmmsc/post" mmsproxy="212.58.162.230" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="255" mnc="07" carrier="Utel MMS" apn="3g.utel.ua" mmsc="http://10.212.1.4/mms/wapenc" mmsproxy="10.212.3.148" mmsport="8080" type="mms"></apn>
+ <apn mcc="257" mnc="01" carrier="Velcom MMS" apn="mms.velcom.by" mmsc="http://mmsc" mmsproxy="10.200.15.15" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="257" mnc="1" carrier="Velcom MMS" apn="mms.velcom.by" mmsc="http://mmsc" mmsproxy="10.200.15.15" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="257" mnc="02" carrier="MTS MMS" apn="mts" mmsc="http://mmsc" mmsproxy="192.168.192.192" mmsport="8080" type="mms" user="mts" password="mts" authtype="1"></apn>
+ <apn mcc="257" mnc="04" carrier="life:) MMS" apn="mms.life.com.by" mmsc="http://mms.life.com.by/mmsc/" mmsproxy="10.10.10.20" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="257" mnc="4" carrier="life:) MMS" apn="mms.life.com.by" mmsc="http://mms.life.com.by/mmsc/" mmsproxy="10.10.10.20" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="259" mnc="01" carrier="Orange MMS" apn="mms.orange.md" mmsc="http://mms/mms" mmsproxy="192.168.127.125" mmsport="3128" type="mms"></apn>
+ <apn mcc="259" mnc="01" carrier="Orange_MMS_GPRS" apn="mms.orange.md" mmsc="http://mms/mms" mmsproxy="192.168.127.125" mmsport="3128" type="mms"></apn>
+ <apn mcc="259" mnc="1" carrier="Orange_MMS_GPRS" apn="mms.orange.md" mmsc="http://mms/mms" mmsproxy="192.168.127.125" mmsport="3128" type="mms"></apn>
+ <apn mcc="259" mnc="02" carrier="Moldcell MMS" apn="mms" mmsc="http://mms.moldcell.md/cmmsc/post" mmsproxy="10.0.10.10" mmsport="9401" type="mms"></apn>
+ <apn mcc="259" mnc="2" carrier="Moldcell MMS" apn="mms" mmsc="http://mms.moldcell.md/cmmsc/post" mmsproxy="10.0.10.10" mmsport="9401" type="mms"></apn>
+ <apn mcc="259" mnc="05" carrier="Unite MMS" apn="mms.unite.md" mmsc="http://10.32.15.68:38090/was" mmsproxy="10.32.15.164" mmsport="8080" type="mms"></apn>
+ <apn mcc="260" mnc="01" carrier="Plus MMS" apn="mms" mmsc="http://mms.plusgsm.pl:8002" mmsproxy="212.2.96.16" mmsport="8080" type="mms"></apn>
+ <apn mcc="260" mnc="1" carrier="Plus mms" apn="mms" mmsc="http://mms.plusgsm.pl:8002" mmsproxy="212.2.96.16" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="260" mnc="02" carrier="T-mobile.pl" apn="mms" mmsc="http://mms/servlets/mms" mmsproxy="213.158.194.226" mmsport="8080" type="mms"></apn>
+ <apn mcc="260" mnc="02" carrier="heyahmms" apn="heyahmms" mmsc="http://mms.heyah.pl/servlets/mms" mmsproxy="213.158.194.226" mmsport="8080" type="mms"></apn>
+ <apn mcc="260" mnc="2" carrier="MMS" apn="mms" mmsc="http://mms/servlets/mms" mmsproxy="213.158.194.226" mmsport="8080" type="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="260" mnc="2" carrier="T-mobile.pl" apn="mms" mmsc="http://mms/servlets/mms" mmsproxy="213.158.194.226" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="260" mnc="2" carrier="heyahmms" apn="heyahmms" mmsc="http://mms.heyah.pl/servlets/mms" mmsproxy="213.158.194.226" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="260" mnc="03" carrier="Orange MMS" apn="mms" mmsc="http://mms.orange.pl" mmsproxy="192.168.6.104" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="260" mnc="3" carrier="Orange PL mms" apn="mms" mmsc="http://mms.orange.pl" mmsproxy="192.168.6.104" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="260" mnc="06" carrier="Play MMS" apn="mms" mmsc="http://10.10.28.164/mms/wapenc" mmsproxy="10.10.25.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="260" mnc="6" carrier="PLAY mms" apn="mms" mmsc="http://10.10.28.164/mms/wapenc" mmsproxy="10.10.25.5" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="260" mnc="6" carrier="Play MMS" apn="mms" mmsc="http://10.10.28.164/mms/wapenc" mmsproxy="10.10.25.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="262" mnc="01" carrier="T-Mobile D" apn="internet.t-mobile" mmsc="http://mms.t-mobile.de/servlets/mms" mmsproxy="172.028.023.131" mmsport="8008"></apn>
+ <apn mcc="262" mnc="01" carrier="Telekom Internet" apn="internet.telekom" mmsc="http://mms.t-mobile.de/servlets/mms" mmsproxy="172.28.23.131" mmsport="8008" user="telekom" password="telekom" authtype="1" mvno_match_data="debitel" mvno_type="spn" protocol="IP"></apn>
+ <apn mcc="262" mnc="01" carrier="Telekom Internet" apn="internet.telekom" mmsc="http://mms.t-mobile.de/servlets/mms" mmsproxy="172.28.23.131" mmsport="8008" type="default,supl,mms" user="telekom" password="telekom" authtype="1"></apn>
+ <apn mcc="262" mnc="1" carrier="T-Mobile Internet" apn="internet.t-mobile" mmsc="http://mms.t-mobile.de/servlets/mms" mmsproxy="172.28.23.131" mmsport="8008" type="default,mms" user="t-mobile" password="tm" authtype="1"></apn>
+ <apn mcc="262" mnc="1" carrier="Telekom Internet" apn="internet.telekom" mmsc="http://mms.t-mobile.de/servlets/mms" mmsproxy="172.28.23.131" mmsport="8008" type="default,mms" user="telekom" password="telekom" authtype="1" protocol="IP"></apn>
+ <apn mcc="262" mnc="1" carrier="Telekom Internet" apn="internet.telekom" mmsc="http://mms.t-mobile.de/servlets/mms" mmsproxy="172.28.23.131" mmsport="8008" type="default,supl,mms" user="telekom" password="telekom" authtype="1"></apn>
+ <apn mcc="262" mnc="02" carrier="Vodafone DE-MMS" apn="event.vodafone.de" mmsc="http://139.7.24.1/servlets/mms" mmsproxy="139.7.29.17" mmsport="80" type="mms"></apn>
+ <apn mcc="262" mnc="2" carrier="Vodafone DE-MMS" apn="event.vodafone.de" mmsc="http://139.7.24.1/servlets/mms" mmsproxy="139.7.29.17" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="262" mnc="2" carrier="Vodafone MMS (DE)" apn="event.vodafone.de" mmsc="http://139.7.24.1/servlets/mms" mmsproxy="139.7.29.17" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="262" mnc="03" carrier="E-Plus MMS" apn="mms.eplus.de" mmsc="http://mms/eplus/" mmsproxy="212.23.97.153" mmsport="5080" type="mms" user="mms" password="eplus" authtype="1"></apn>
+ <apn mcc="262" mnc="3" carrier="E-Plus MMS (DE)" apn="mms.eplus.de" mmsc="http://mms/eplus" mmsproxy="212.23.97.153" mmsport="5080" type="mms" user="mms" password="eplus" authtype="0"></apn>
+ <apn mcc="262" mnc="07" carrier="Alice" apn="internet.partner1" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.41" mmsport="8080" authtype="0" mvno_match_data="Alice" mvno_type="spn"></apn>
+ <apn mcc="262" mnc="07" carrier="Fonic" apn="pinternet.interkom.net" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.6" mmsport="8080" authtype="0" mvno_match_data="26207515" mvno_type="imsi"></apn>
+ <apn mcc="262" mnc="07" carrier="o2" apn="internet" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.5" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="262" mnc="07" carrier="o2 Internet" apn="internet" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.5" mmsport="8080" type="default,supl,mms" mvno_match_data="2620739" mvno_type="imsi"></apn>
+ <apn mcc="262" mnc="07" carrier="o2 Internet Prepaid" apn="pinternet.interkom.de" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.6" mmsport="8080" type="default,supl,mms" proxy="82.113.100.6" port="8080"></apn>
+ <apn mcc="262" mnc="07" carrier="o2 Internet Prepaid" apn="pinternet.interkom.de" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.6" mmsport="8080" type="default,supl,mms" mvno_match_data="2620749" mvno_type="imsi"></apn>
+ <apn mcc="262" mnc="7" carrier="o2" apn="internet" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.5" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="262" mnc="7" carrier="o2 (DE)" apn="internet" mmsc="http://10.81.0.7:8002/" mmsproxy="82.113.100.5" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="262" mnc="7" carrier="o2 Internet Prepaid" apn="pinternet.interkom.de" mmsc="http://10.81.0.7:8002" mmsproxy="82.113.100.6" mmsport="8080" type="default,supl,mms" authtype="0" proxy="82.113.100.6" port="8080"></apn>
+ <apn mcc="268" mnc="01" carrier="Vodafone Net2" apn="net2.vodafone.pt" mmsc="http://mms.vodafone.pt/servlets/mms" mmsproxy="iproxy.vodafone.pt" mmsport="80" type="default,supl,mms" user="vodafone" password="vodafone" authtype="1" proxy="iproxy.vodafone.pt" port="80"></apn>
+ <apn mcc="268" mnc="01" carrier="ZON MMS" apn="vas.zon.pt" mmsc="http://mms/servlets/mms" mmsproxy="213.30.27.63" mmsport="8799" type="mms" user="vas" password="vas" authtype="1" mvno_match_data="ZON" mvno_type="spn"></apn>
+ <apn mcc="268" mnc="1" carrier="Vodafone Net2" apn="net2.vodafone.pt" mmsc="http://mms.vodafone.pt/servlets/mms" mmsproxy="iproxy.vodafone.pt" mmsport="80" type="default,mms,supl,agps,fota" user="vodafone" password="vodafone" authtype="0" proxy="iproxy.vodafone.pt" port="80"></apn>
+ <apn mcc="268" mnc="1" carrier="Vodafone Net2" apn="net2.vodafone.pt" mmsc="http://mms.vodafone.pt/servlets/mms" mmsproxy="iproxy.vodafone.pt" mmsport="80" type="default,supl,mms" user="vodafone" password="vodafone" authtype="1" proxy="iproxy.vodafone.pt" port="80"></apn>
+ <apn mcc="268" mnc="03" carrier="PortalOptimus" apn="umts" mmsc="http://mmsc:10021/mmsc" mmsproxy="62.169.66.5" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="268" mnc="3" carrier="P OPTIMUS" apn="umts" mmsc="http://mmsc:10021/mmsc" mmsproxy="62.169.66.5" mmsport="8799" type="default,mms" authtype="0"></apn>
+ <apn mcc="268" mnc="3" carrier="PortalOptimus" apn="umts" mmsc="http://mmsc:10021/mmsc" mmsproxy="62.169.66.5" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="268" mnc="06" carrier="mms tmn" apn="mmsc.tmn.pt" mmsc="http://mmsc/" mmsproxy="10.111.2.16" mmsport="8080" type="mms" user="tmn" password="tmnnet" authtype="1"></apn>
+ <apn mcc="268" mnc="6" carrier="P TMN mms" apn="mmsc.tmn.pt" mmsc="http://mmsc" mmsproxy="10.111.2.16" mmsport="8080" type="mms" user="tmn" password="tmnnet" authtype="0"></apn>
+ <apn mcc="268" mnc="6" carrier="mms tmn" apn="mmsc.tmn.pt" mmsc="http://mmsc/" mmsproxy="10.111.2.16" mmsport="8080" type="mms" user="tmn" password="tmnnet" authtype="1"></apn>
+ <apn mcc="270" mnc="01" carrier="LUXGSM MMS" apn="mms.pt.lu" mmsc="http://mmsc.pt.lu" mmsproxy="194.154.192.88" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="270" mnc="1" carrier="LUXGSM MMS" apn="mms.pt.lu" mmsc="http://mmsc.pt.lu" mmsproxy="194.154.192.88" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="270" mnc="1" carrier="LUXGSM mms" apn="mms.pt.lu" mmsc="http://mmsc.pt.lu" mmsproxy="194.154.192.88" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="270" mnc="77" carrier="TANGO mms" apn="mms" mmsc="http://mms.tango.lu" mmsproxy="212.66.75.3" mmsport="8080" type="mms" user="tango" password="tango" authtype="1"></apn>
+ <apn mcc="270" mnc="77" carrier="Tango MMS" apn="mms" mmsc="http://mms.tango.lu" mmsproxy="212.66.75.3" mmsport="8080" type="mms" user="tango" password="tango"></apn>
+ <apn mcc="270" mnc="77" carrier="Tango MMS" apn="mms" mmsc="http://mms.tango.lu" mmsproxy="212.66.75.3" mmsport="8080" type="mms" user="tango" password="tango" authtype="3"></apn>
+ <apn mcc="270" mnc="99" carrier="Orange" apn="orange.lu" mmsc="http://mms.orange.lu" mmsproxy="212.88.139.44" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="270" mnc="99" carrier="Vox Mobile" apn="vox.lu" mmsc="http://mms.vox.lu" mmsproxy="212.88.139.44" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="270" mnc="99" carrier="Vox Mobile" apn="vox.lu" mmsc="http://mms.vox.lu" mmsproxy="212.88.139.44" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="272" mnc="01" carrier="Vodafone IE-MMS" apn="mms.vodafone.net" mmsc="http://www.vodafone.ie/mms" mmsproxy="10.24.59.200" mmsport="80" type="mms"></apn>
+ <apn mcc="272" mnc="1" carrier="Vodafone IE-MMS" apn="mms.vodafone.net" mmsc="http://www.vodafone.ie/mms" mmsproxy="10.24.59.200" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="272" mnc="1" carrier="Vodafone MMS" apn="mms.vodafone.net" mmsc="http://www.vodafone.ie/mms" mmsproxy="10.24.59.200" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="272" mnc="02" carrier="O2 Ireland" apn="internet" mmsc="http://mmsc.mms.o2.ie:8002" mmsproxy="62.40.32.40" mmsport="8080" type="default,supl,mms" proxy="62.40.32.40" port="8080"></apn>
+ <apn mcc="272" mnc="2" carrier="O2 Ireland" apn="internet" mmsc="http://mmsc.mms.o2.ie:8002" mmsproxy="62.40.32.40" mmsport="8080" type="default,supl,mms" authtype="0" proxy="62.40.32.40" port="8080"></apn>
+ <apn mcc="272" mnc="2" carrier="O2.ie Mobile Internet" apn="internet" mmsc="http://mmsc.mms.o2.ie:8002" mmsproxy="62.40.32.40" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="272" mnc="03" carrier="Meteor MMS" apn="mms.mymeteor.ie" mmsc="http://mms.mymeteor.ie" mmsproxy="10.85.85.85" mmsport="8799" type="mms" user="my" password="wap" authtype="1"></apn>
+ <apn mcc="272" mnc="3" carrier="Meteor MMS" apn="mms.mymeteor.ie" mmsc="http://mms.mymeteor.ie" mmsproxy="10.85.85.85" mmsport="8799" type="mms" user="my" password="mms" authtype="0"></apn>
+ <apn mcc="272" mnc="3" carrier="Meteor MMS" apn="mms.mymeteor.ie" mmsc="http://mms.mymeteor.ie" mmsproxy="10.85.85.85" mmsport="8799" type="mms" user="my" password="wap" authtype="1"></apn>
+ <apn mcc="272" mnc="05" carrier="3" apn="3ireland.ie" mmsc="http://mms.um.3ireland.ie:10021/mmsc" mmsproxy="mms.3ireland.ie" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="272" mnc="5" carrier="3" apn="3ireland.ie" mmsc="http://mms.um.3ireland.ie:10021/mmsc" mmsproxy="mms.3ireland.ie" mmsport="8799" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="272" mnc="5" carrier="3 Ireland" apn="3ireland.ie" mmsc="http://mms.um.3ireland.ie:10021/mmsc/" mmsproxy="mms.3ireland.ie" mmsport="8799" type="default,supl,agps,fota,dun,mms" authtype="0"></apn>
+ <apn mcc="272" mnc="11" carrier="Tesco" apn="tescomobile.liffeytelecom.com" mmsc="http://mmc1/servlets/mms" mmsproxy="10.1.11.19" mmsport="8080" type="default,supl,mms" mvno_match_data="0A" mvno_type="gid"></apn>
+ <apn mcc="274" mnc="01" carrier="Siminn MMS" apn="mms.simi.is" mmsc="http://mms.simi.is/servlets/mms" mmsproxy="213.167.138.200" mmsport="8080" type="mms"></apn>
+ <apn mcc="274" mnc="1" carrier="Siminn MMS" apn="mms.simi.is" mmsc="http://mms.simi.is/servlets/mms" mmsproxy="213.167.138.200" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="274" mnc="02" carrier="Vodafone MMS" apn="mms.gprs.is" mmsc="http://mmsc.vodafone.is" mmsproxy="10.22.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="274" mnc="2" carrier="Vodafone MMS" apn="mms.gprs.is" mmsc="http://mmsc.vodafone.is" mmsproxy="10.22.0.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="274" mnc="03" carrier="Vodafone MMS" apn="mms.gprs.is" mmsc="http://mmsc.vodafone.is" mmsproxy="10.22.0.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="274" mnc="3" carrier="Vodafone MMS" apn="mms.gprs.is" mmsc="http://mmsc.vodafone.is" mmsproxy="10.22.0.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="274" mnc="11" carrier="MMS Nova" apn="mms.nova.is" mmsc="http://mmsc.nova.is" mmsproxy="10.10.2.60" mmsport="8080" type="mms"></apn>
+ <apn mcc="274" mnc="11" carrier="MMS Nova" apn="mms.nova.is" mmsc="http://mmsc.nova.is" mmsproxy="10.10.2.60" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="274" mnc="11" carrier="Nova MMS" apn="mms.nova.is" mmsc="http://mmsc.nova.is" mmsproxy="10.10.2.60" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="278" mnc="01" carrier="Vodafone MT-MMS" apn="mms.vodafone.com.mt" mmsc="http://mms.vodafone.com.mt/servlets/mms" mmsproxy="10.12.0.3" mmsport="8080" type="mms"></apn>
+ <apn mcc="278" mnc="1" carrier="Vodafone MMS (MT)" apn="mms.vodafone.com.mt" mmsc="http://mms.vodafone.com.mt/servlets/mms" mmsproxy="10.12.0.3" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="278" mnc="1" carrier="Vodafone MT-MMS" apn="mms.vodafone.com.mt" mmsc="http://mms.vodafone.com.mt/servlets/mms" mmsproxy="10.12.0.3" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="280" mnc="01" carrier="CYTA" apn="cytamobile" mmsc="http://mmsc.cyta.com.cy" mmsproxy="212.31.96.161" mmsport="8080" type="default,supl,mms" user="user" password="pass"></apn>
+ <apn mcc="280" mnc="1" carrier="CYTA" apn="cytamobile" mmsc="http://mmsc.cyta.com.cy" mmsproxy="212.31.96.161" mmsport="8080" type="default,supl,mms" user="user" password="pass"></apn>
+ <apn mcc="280" mnc="10" carrier="MTN MMS" apn="mms" mmsc="http://mms.mtn.com.cy/mmsc" mmsproxy="172.24.97.1" mmsport="3130" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="283" mnc="10" carrier="Orange Armenia MMS" apn="mms" mmsc="http://mms/" mmsproxy="192.168.220.251" mmsport="3128" type="mms" authtype="1"></apn>
+ <apn mcc="284" mnc="01" carrier="MTel MMS" apn="mms-gprs.mtel.bg" mmsc="http://mmsc/" mmsproxy="10.150.0.33" mmsport="8080" type="mms" user="mtel" password="mtel" authtype="1"></apn>
+ <apn mcc="284" mnc="1" carrier="M-Tel BG MMS" apn="mms-gprs.mtel.bg" mmsc="http://mmsc/" mmsproxy="10.150.0.33" mmsport="8080" type="mms" user="mtel" password="mtel" authtype="0"></apn>
+ <apn mcc="284" mnc="03" carrier="Vivacom MMS" apn="mms.vivacom.bg" mmsc="http://mmsc.vivacom.bg" mmsproxy="192.168.123.123" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="284" mnc="3" carrier="VIVACOM MMS" apn="mms.vivacom.bg" mmsc="http://mmsc.vivacom.bg" mmsproxy="192.168.123.123" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="284" mnc="3" carrier="Vivacom MMS" apn="mms.vivacom.bg" mmsc="http://mmsc.vivacom.bg" mmsproxy="192.168.123.123" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="284" mnc="05" carrier="GLOBUL MMS GPRS" apn="mms.globul.bg" mmsc="http://mmsc1.mms.globul.bg:8002" mmsproxy="192.168.87.11" mmsport="8004" type="mms" user="mms"></apn>
+ <apn mcc="284" mnc="5" carrier="GLOBUL MMS GPRS" apn="mms.globul.bg" mmsc="http://mmsc1.mms.globul.bg:8002" mmsproxy="192.168.87.11" mmsport="8004" type="mms" user="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="284" mnc="5" carrier="GLOBUL MMS GPRS" apn="mms.globul.bg" mmsc="http://mmsc1.mms.globul.bg:8002" mmsproxy="192.168.87.11" mmsport="8004" type="mms" user="mms" authtype="3"></apn>
+ <apn mcc="286" mnc="01" carrier="TURKCELL MMS" apn="mms" mmsc="http://mms.turkcell.com.tr/servlets/mms" mmsproxy="212.252.169.217" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="286" mnc="1" carrier="TR TURKCELL mms" apn="mms" mmsc="http://mms.turkcell.com.tr/servlets/mms" mmsproxy="212.252.169.217" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="286" mnc="1" carrier="TURKCELL MMS" apn="mms" mmsc="http://mms.turkcell.com.tr/servlets/mms" mmsproxy="212.252.169.217" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="286" mnc="02" carrier="Vodafone MMS" apn="mms" mmsc="http://217.31.233.18:6001/MM1Servlet" mmsproxy="217.31.233.18" mmsport="9401" type="mms" user="vodafone" password="vodafone" authtype="1"></apn>
+ <apn mcc="286" mnc="2" carrier="VODAFONE TR mms" apn="mms" mmsc="http://217.31.233.18:6001/MM1Servlet" mmsproxy="217.31.233.18" mmsport="9401" type="mms" user="vodafone" password="vodafone" authtype="0"></apn>
+ <apn mcc="286" mnc="2" carrier="Vodafone MMS" apn="mms" mmsc="http://217.31.233.18:6001/MM1Servlet" mmsproxy="217.31.233.18" mmsport="9401" type="mms" user="vodafone" password="vodafone" authtype="1"></apn>
+ <apn mcc="286" mnc="03" carrier="AVEA MMS" apn="mms" mmsc="http://mms.avea.com.tr/servlets/mms" mmsproxy="213.161.151.201" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="286" mnc="3" carrier="AVEA MMS" apn="mms" mmsc="http://mms.avea.com.tr/servlets/mms" mmsproxy="213.161.151.201" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="286" mnc="3" carrier="AVEA-MMS" apn="mms" mmsc="http://mms.avea.com.tr/servlets/mms" mmsproxy="213.161.151.201" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="290" mnc="01" carrier="Tele MMS" apn="mms" mmsc="http://mms.tele.gl/mms/wapenc" mmsproxy="10.112.222.37" mmsport="8080" type="mms"></apn>
+ <apn mcc="293" mnc="40" carrier="Si.mobil MMS" apn="mms.simobil.si" mmsc="http://mmc/" mmsproxy="80.95.224.46" mmsport="9201" type="mms" user="simobil" password="internet" authtype="1"></apn>
+ <apn mcc="293" mnc="40" carrier="Si.mobil mms" apn="mms.simobil.si" mmsc="http://mmc/" mmsproxy="80.95.224.46" mmsport="9090" type="mms" user="simobil" password="internet" authtype="0"></apn>
+ <apn mcc="293" mnc="41" carrier="MOBITEL" apn="internet" mmsc="http://mms.mobitel.si/servlets/mms" mmsproxy="213.229.249.40" mmsport="8080" type="default,mms" user="mobitel" password="internet" authtype="0" proxy="213.229.249.40" port="8080"></apn>
+ <apn mcc="293" mnc="41" carrier="Mobilni Internet" apn="internet" mmsc="http://mms.mobitel.si/servlets/mms" mmsproxy="213.229.249.40" mmsport="8080" type="default,supl,mms" user="mobitel" password="internet" authtype="1"></apn>
+ <apn mcc="293" mnc="64" carrier="T2" apn="internet.t-2.net" mmsc="http://www.mms.t-2.net:8002" mmsproxy="172.20.18.137" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="293" mnc="70" carrier="SI TUSMOBIL mms" apn="mms.tusmobil.si" mmsc="http://mms.tusmobil.si:8002" mmsproxy="91.185.221.85" mmsport="8080" type="mms" user="tusmobil" password="mms" authtype="0"></apn>
+ <apn mcc="293" mnc="70" carrier="Telemach Internet" apn="telemach.net" mmsc="http://mms.telemach.net:8002" mmsproxy="91.185.221.85" mmsport="8080" type="default,supl,mms" mvno_match_data="29370029xxxxxxx" mvno_type="imsi"></apn>
+ <apn mcc="293" mnc="70" carrier="Tusmobil MMS" apn="mms.tusmobil.si" mmsc="http://mms.tusmobil.si:8002" mmsproxy="91.185.221.85" mmsport="8080" type="mms" user="tusmobil" password="mms" authtype="1"></apn>
+ <apn mcc="294" mnc="01" carrier="T-Mobile MK MMS" apn="mms" mmsc="http://mms.t-mobile.com.mk" mmsproxy="62.162.155.227" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="294" mnc="1" carrier="T-Mobile MK MMS" apn="mms" mmsc="http://mms.t-mobile.com.mk" mmsproxy="62.162.155.227" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="294" mnc="1" carrier="TM MK MMS" apn="mms" mmsc="http://mms.t-mobile.com.mk" mmsproxy="62.162.155.227" mmsport="8080" type="mms" user="mms" password="mms" authtype="1" protocol="IP"></apn>
+ <apn mcc="294" mnc="02" carrier="Cosmofon MMS" apn="mms" mmsc="http://195.167.65.220:8002" mmsproxy="10.10.10.20" mmsport="8080" type="mms"></apn>
+ <apn mcc="294" mnc="2" carrier="Cosmofon MMS" apn="mms" mmsc="http://195.167.65.220:8002" mmsproxy="10.10.10.20" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="294" mnc="2" carrier="ONE MK MMS" apn="mms" mmsc="http://195.167.65.220:8002" mmsproxy="10.10.10.20" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="294" mnc="03" carrier="MMS" apn="vipoperator.mms" mmsc="http://mmsc.vipoperator.com.mk" mmsproxy="78.40.0.1" mmsport="8080" type="mms" user="vipoperator" password="vipoperator"></apn>
+ <apn mcc="294" mnc="3" carrier="MMS" apn="vipoperator.mms" mmsc="http://mmsc.vipoperator.com.mk" mmsproxy="78.40.0.1" mmsport="8080" type="mms" user="vipoperator" password="vipoperator" authtype="3"></apn>
+ <apn mcc="294" mnc="3" carrier="VIP MK MMS" apn="vipoperator" mmsc="http://mmsc.vipoperator.com.mk" mmsproxy="78.40.0.1" mmsport="8080" type="mms" user="vipoperator" authtype="0"></apn>
+ <apn mcc="297" mnc="02" carrier="T-Mobile MMS" apn="mms" mmsc="http://192.168.180.100/servlets/mms" mmsproxy="10.0.5.19" mmsport="8080" type="mms" user="38267" password="38267"></apn>
+ <apn mcc="297" mnc="2" carrier="T-Mobile MMS" apn="mms" mmsc="http://192.168.180.100/servlets/mms" mmsproxy="10.0.5.19" mmsport="8080" type="mms" user="38267" password="38267" authtype="3"></apn>
+ <apn mcc="297" mnc="2" carrier="Telekom MMS" apn="mms" mmsc="http://192.168.180.100/servlets/mms" mmsproxy="10.0.5.19" mmsport="8080" type="mms" user="38267" password="38267" authtype="1" protocol="IP"></apn>
+ <apn mcc="302" mnc="220" carrier="Koodo" apn="sp.koodo.com" mmsc="http://aliasredirect.net/proxy/koodo/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl,dun" mvno_match_data="4B" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="Koodo" apn="sp.koodo.com" mmsc="http://aliasredirect.net/proxy/koodo/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl,dun" authtype="0" mvno_match_data="4B" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="Koodo SP" apn="sp.koodo.com" mmsc="http://aliasredirect.net/proxy/koodo/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,agps,supl,fota,hipri,dun" authtype="0" mvno_match_data="4B4F" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="Mobile Internet" apn="sp.mb.com" mmsc="http://aliasredirect.net/proxy/mb/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,agps,supl,fota,hipri" mvno_match_data="4D4F" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="Mobile Internet" apn="sp.mb.com" mmsc="http://aliasredirect.net/proxy/mb/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,agps,supl,fota,hipri" authtype="0" mvno_match_data="5043" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="Mobile Internet" apn="sp.mb.com" mmsc="http://aliasredirect.net/proxy/mb/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" mvno_match_data="50" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="PC mobile" apn="sp.mb.com" mmsc="http://aliasredirect.net/proxy/mb/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" mvno_match_data="50" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="PC mobile" apn="sp.mb.com" mmsc="http://aliasredirect.net/proxy/mb/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" authtype="0" mvno_match_data="50" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="TELUS" apn="sp.telus.com" mmsc="http://aliasredirect.net/proxy/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" mvno_match_data="54" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="TELUS" apn="sp.telus.com" mmsc="http://aliasredirect.net/proxy/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" authtype="0" mvno_match_data="54" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="220" carrier="TELUS SP" apn="sp.telus.com" mmsc="http://aliasredirect.net/proxy/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,agps,supl,fota,hipri" authtype="0" mvno_match_data="5455" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="221" carrier="Koodo" apn="sp.koodo.com" mmsc="http://aliasredirect.net/proxy/koodo/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" mvno_match_data="4B" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="221" carrier="Koodo" apn="sp.koodo.com" mmsc="http://aliasredirect.net/proxy/koodo/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl,dun" mvno_match_data="4B" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="221" carrier="PC mobile" apn="sp.mb.com" mmsc="http://aliasredirect.net/proxy/mb/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" mvno_match_data="50" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="221" carrier="TELUS" apn="sp.telus.com" mmsc="http://aliasredirect.net/proxy/mmsc" mmsproxy="74.49.0.18" mmsport="80" type="default,mms,supl" mvno_match_data="54" mvno_type="gid"></apn>
+ <apn mcc="302" mnc="270" carrier="Eastlink MMS" apn="mms.mobi.eastlink.ca" mmsc="http://mmss.mobi.eastlink.ca" mmsproxy="10.232.12.49" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="302" mnc="270" carrier="MMS" apn="mms.mobi.eastlink.ca" mmsc="http://mmss.mobi.eastlink.ca" mmsproxy="10.232.12.49" mmsport="8080" type="mms"></apn>
+ <apn mcc="302" mnc="270" carrier="MMS" apn="mms.mobi.eastlink.ca" mmsc="http://mmss.mobi.eastlink.ca" mmsproxy="10.232.12.49" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="302" mnc="320" carrier="MOMMS" apn="mms.davewireless.com" mmsc="http://mms.mobilicity.net" mmsproxy="10.100.3.4" mmsport="8080" type="mms"></apn>
+ <apn mcc="302" mnc="320" carrier="MOMMS" apn="mms.davewireless.com" mmsc="http://mms.mobilicity.net" mmsproxy="10.100.3.4" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="302" mnc="320" carrier="Mobilicity MMS" apn="mms.davewireless.com" mmsc="http://mms.mobilicity.net" mmsproxy="10.100.3.4" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="302" mnc="370" carrier="Fido Internet" apn="ltemobile.apn" mmsc="http://mms.fido.ca" mmsproxy="mmsproxy.fido.ca" mmsport="80" type="default,mms,agps,supl,fota,hipri" mvno_match_data="DD" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IP"></apn>
+ <apn mcc="302" mnc="370" carrier="Fido LTE" apn="ltemobile.apn" mmsc="http://mms.fido.ca" mmsproxy="mmsproxy.fido.ca" mmsport="80" type="default,mms,supl" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="370" carrier="MTS" apn="sp.mts" mmsc="http://mmsc2.mts.net/" mmsproxy="wapgw1.mts.net" mmsport="9201" type="default,mms,supl" mvno_match_data="2C" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="370" carrier="MTS" apn="sp.mts" mmsc="http://mmsc2.mts.net/" mmsproxy="wapgw1.mts.net" mmsport="9201" type="default,mms,supl" authtype="0" mvno_match_data="2C" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="370" carrier="MTS Internet S" apn="sp.mts" mmsc="http://mmsc2.mts.net/" mmsproxy="wapgw1.mts.net" mmsport="9201" type="default,mms,agps,supl,fota,hipri" authtype="0" mvno_match_data="MTS" mvno_type="spn" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="490" carrier="MMS" apn="mms.windmobile.ca" mmsc="http://mms.windmobile.ca" mmsproxy="74.115.197.70" mmsport="8080" type="mms"></apn>
+ <apn mcc="302" mnc="490" carrier="MMS" apn="mms.windmobile.ca" mmsc="http://mms.windmobile.ca" mmsproxy="74.115.197.70" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="302" mnc="490" carrier="WIND MMS" apn="mms.windmobile.ca" mmsc="http://mms.windmobile.ca" mmsproxy="74.115.197.70" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="302" mnc="500" carrier="Media" apn="media.ng" mmsc="http://media.videotron.com" type="default,supl,mms"></apn>
+ <apn mcc="302" mnc="500" carrier="Videotron" apn="media.videotron" mmsc="http://media.videotron.com" type="default,mms,dun,agps,supl,fota,hipri" authtype="0"></apn>
+ <apn mcc="302" mnc="500" carrier="Videotron" apn="media.videotron" mmsc="http://media.videotron.com" type="default,supl,mms"></apn>
+ <apn mcc="302" mnc="500" carrier="Videotron" apn="media.videotron" mmsc="http://media.videotron.com" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="302" mnc="510" carrier="Media" apn="media.ng" mmsc="http://media.videotron.com" type="default,supl,mms"></apn>
+ <apn mcc="302" mnc="510" carrier="Videotron" apn="media.videotron" mmsc="http://media.videotron.com" type="default,supl,mms"></apn>
+ <apn mcc="302" mnc="520" carrier="Media" apn="media.ng" mmsc="http://media.videotron.com" type="default,supl,mms"></apn>
+ <apn mcc="302" mnc="610" carrier="Bell Mobility" apn="pda.bell.ca" mmsc="http://mms.bell.ca/mms/wapenc" type="default,mms,dun,supl"></apn>
+ <apn mcc="302" mnc="610" carrier="Bell Mobility" apn="pda.bell.ca" mmsc="http://mms.bell.ca/mms/wapenc" type="default,mms,dun,supl" authtype="0"></apn>
+ <apn mcc="302" mnc="610" carrier="Bell Mobility" apn="pda.bell.ca" mmsc="http://mms.bell.ca/mms/wapenc" type="default,mms,supl"></apn>
+ <apn mcc="302" mnc="610" carrier="Bell-Internet" apn="pda.bell.ca" mmsc="http://mms.bell.ca/mms/wapenc" mmsproxy="web.wireless.bell.ca" mmsport="80" type="default,mms" authtype="0"></apn>
+ <apn mcc="302" mnc="660" carrier="MTS" apn="sp.mts" mmsc="http://mmsc2.mts.net/" mmsproxy="wapgw1.mts.net" mmsport="9201" type="default,mms,supl" mvno_match_data="2C" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="660" carrier="MTS" apn="sp.mts" mmsc="http://mmsc2.mts.net/" mmsproxy="wapgw1.mts.net" mmsport="9201" type="default,mms,supl" authtype="0" mvno_match_data="2C" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="660" carrier="MTS Internet" apn="sp.mts" mmsc="http://mmsc2.mts.net/" mmsproxy="wapgw1.mts.net" mmsport="9201" type="default,mms,agps,supl,fota,hipri" authtype="0" mvno_match_data="MTS" mvno_type="spn" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="720" carrier="Chatr Internet" apn="chatrweb.apn" mmsc="http://mms.chatrwireless.com" mmsproxy="205.151.11.11" mmsport="80" type="default,mms,agps,supl,fota,hipri" authtype="0" mvno_match_data="chatr" mvno_type="spn" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="720" carrier="Cityfone Internet" apn="ltemobile.apn" mmsc="http://mms.gprs.rogers.com" mmsproxy="mmsproxy.rogers.com" mmsport="80" type="default,mms,agps,supl,fota,hipri" mvno_match_data="CITYFONE" mvno_type="spn" protocol="IPV4V6" roaming_protocol="IP"></apn>
+ <apn mcc="302" mnc="720" carrier="Rogers Internet" apn="ltemobile.apn" mmsc="http://mms.gprs.rogers.com" mmsproxy="mmsproxy.rogers.com" mmsport="80" type="default,mms,agps,supl,fota,hipri" mvno_match_data="ROGERS" mvno_type="spn" protocol="IPV4V6" roaming_protocol="IP"></apn>
+ <apn mcc="302" mnc="720" carrier="Rogers LTE" apn="ltemobile.apn" mmsc="http://mms.gprs.rogers.com" mmsproxy="mmsproxy.rogers.com" mmsport="80" type="default,mms,supl" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="302" mnc="720" carrier="Tbaytel Internet" apn="ltemobile.apn" mmsc="http://mms.gprs.rogers.com" mmsproxy="mmsproxy.rogers.com" mmsport="80" type="default,mms,agps,supl,fota,hipri" mvno_match_data="BA" mvno_type="gid" protocol="IPV4V6" roaming_protocol="IP"></apn>
+ <apn mcc="302" mnc="720" carrier="chatr" apn="chatrweb.apn" mmsc="http://mms.chatrwireless.com" mmsproxy="205.151.11.11" mmsport="80" type="default,mms,supl" mvno_match_data="302720x94" mvno_type="imsi" protocol="IPV4V6" roaming_protocol="IPV4V6" proxy="205.151.11.11" port="80"></apn>
+ <apn mcc="302" mnc="720" carrier="chatr" apn="chatrweb.apn" mmsc="http://mms.chatrwireless.com" mmsproxy="205.151.11.11" mmsport="80" type="default,mms,supl" authtype="0" mvno_match_data="302720x94" mvno_type="imsi" protocol="IPV4V6" roaming_protocol="IPV4V6" proxy="205.151.11.11" port="80"></apn>
+ <apn mcc="302" mnc="780" carrier="SaskTel" apn="pda.stm.sk.ca" mmsc="http://mms.sasktel.com/" mmsproxy="mig.sasktel.com" mmsport="80" type="default,mms,supl"></apn>
+ <apn mcc="302" mnc="780" carrier="SaskTel" apn="pda.stm.sk.ca" mmsc="http://mms.sasktel.com/" mmsproxy="mig.sasktel.com" mmsport="80" type="default,mms,supl,dun"></apn>
+ <apn mcc="310" mnc="0" carrier="MetroPCS" apn="internet" mmsc="http://mms.metropcs.net:3128/mmsc" user="*" password="*" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="1" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="10" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="11" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="15" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="16" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="2" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="3" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="9" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint" apn="mms_over_wifi" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="wifi" user="*" password="*" server="*"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="310" mnc="0" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="000" carrier="Verizon CDMA HRPD" mmsc="http://mms.vzwreseller.com/servlets/mms" type="default,mms,hipri,dun,supl" authtype="3" mvno_match_data="Tracfone" mvno_type="spn"></apn>
+ <apn mcc="310" mnc="000" carrier="Bluewire" apn="CdmaNai" mmsc="http://mms.blueunlimited.com" mmsport="8514" type="mms" mvno_match_data="bluewire" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="Bluewire" apn="VZWINTERNET" mmsc="http://mms.blueunlimited.com" mmsport="8514" type="default,mms,dun,supl" mvno_match_data="bluewire" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Bluewire" apn="VZWINTERNET" mmsc="http://mms.blueunlimited.com" mmsport="8514" type="default,mms,dun,supl" mvno_match_data="bluewire" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Commnet" apn="CdmaNai" mmsc="http://mmsc.cccomm.csky.us" mmsport="6672" type="mms" mvno_match_data="commnet" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="Distribution" apn="VZWINTERNET" mmsc="http://mms.dst.com/mms/" type="default,mms,dun,supl" mvno_match_data="distribution" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Distribution" apn="VZWINTERNET" mmsc="http://mms.dst.com/mms/" type="default,mms,dun,supl" mvno_match_data="distribution" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Flatwire" apn="VZWINTERNET" mmsc="http://mmsc.cleartalk.csky.us/" type="default,mms,dun,supl" mvno_match_data="flatwire" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Flatwire" apn="VZWINTERNET" mmsc="http://mmsc.cleartalk.csky.us/" type="default,mms,dun,supl" mvno_match_data="flatwire" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Internet" apn="n.ispsn" mmsc="http://mms.plspictures.com" mmsproxy="68.28.31.7" mmsport="80" type="default" protocol="IPV4V6" bearer="0"></apn>
+ <apn mcc="310" mnc="000" carrier="Mobilenation" apn="VZWINTERNET" mmsc="http://mms.mymn3g.net" mmsproxy="mms.mymn3g.net" mmsport="8081" type="default,mms,dun,supl" mvno_match_data="mobilenation" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Mobilenation" apn="VZWINTERNET" mmsc="http://mms.mymn3g.net" mmsproxy="mms.mymn3g.net" mmsport="8081" type="default,mms,dun,supl" mvno_match_data="mobilenation" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Mobipcs" apn="VZWINTERNET" mmsc="http://mms.mobipcs.com" type="default,mms,dun,supl" mvno_match_data="mobipcs" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Mobipcs" apn="VZWINTERNET" mmsc="http://mms.mobipcs.com" type="default,mms,dun,supl" mvno_match_data="mobipcs" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Mohave" apn="VZWINTERNET" mmsc="http://mms.mohavewireless.com" type="default,mms,dun,supl" mvno_match_data="mohave" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Mohave" apn="VZWINTERNET" mmsc="http://mms.mohavewireless.com" type="default,mms,dun,supl" mvno_match_data="mohave" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Peopleswire" apn="VZWINTERNET" mmsc="http://172.16.16.130/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="Peopleswire" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Peopleswire" apn="VZWINTERNET" mmsc="http://172.16.16.130/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="Peopleswire" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Revol" apn="VZWINTERNET" mmsc="http://mms.revol.us/revol/mms.php" type="default,mms,dun,supl" mvno_match_data="revol" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="Revol" apn="VZWINTERNET" mmsc="http://mms.revol.us/revol/mms.php" type="default,mms,dun,supl" mvno_match_data="revol" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="000" carrier="distribution" apn="CdmaNai" mmsc="http://mms.dst.com/mms/" type="mms" mvno_match_data="distribution" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="flatwire" apn="CdmaNai" mmsc="http://mmsc.cleartalk.csky.us/" type="mms" mvno_match_data="flatwire" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="mobilenation" apn="CdmaNai" mmsc="http://mms.mymn3g.net" mmsproxy="mms.mymn3g.net" mmsport="8081" type="mms" mvno_match_data="mobilenation" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="mobipcs" apn="CdmaNai" mmsc="http://mms.mobipcs.com" type="mms" mvno_match_data="mobipcs" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="mohave" apn="CdmaNai" mmsc="http://mms.mohavewireless.com" type="mms" mvno_match_data="mohave" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="peopleswire" apn="CdmaNai" mmsc="http://172.16.16.130/mms/" mmsport="80" type="mms" mvno_match_data="peopleswire" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="000" carrier="revol" apn="CdmaNai" mmsc="http://mms.revol.us/revol/mms.php" type="mms" mvno_match_data="revol" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun" protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="12"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="4"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="5"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="6"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="7"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="8"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="12"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="4"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="5"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="6"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="7"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="8"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="004" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="4" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="4" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="4" carrier="Verizon CDMA HRPD" apn="CdmaNai" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,hipri,dun,stdhipri,supl" protocol="IPV4V6" bearer="6" carrier_enabled="FALSE"></apn>
+ <apn mcc="310" mnc="4" carrier="Verizon CDMA HRPD" apn="CdmaNai" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,hipri,dun,stdhipri,supl" protocol="IPV4V6" bearer="6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="4" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri,supl" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="4" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri,supl" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="016" carrier="Cricket" apn="wap.mycricket.com" mmsc="http://mms.mycricket.com/servlets/mms" user="\@mycricket.com" password="cricket" authtype="2"></apn>
+ <apn mcc="310" mnc="020" carrier="Union Wireless MMS" apn="union.mms.com" mmsc="http://mmsc/01" mmsproxy="166.230.4.83" mmsport="8799" type="mms"></apn>
+ <apn mcc="310" mnc="028" carrier=" ALU Test-SIM CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="028" carrier=" ALU Test-SIM Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="28" carrier="ALU Test-SIM CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="28" carrier="ALU Test-SIM Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="030" carrier="myBlue Pix" apn="mmswap.centennialwireless.com" mmsc="http://mms.myblue.com/servlets/mms" mmsproxy="63.99.231.135" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="30" carrier="myBlue Pix" apn="mmswap.centennialwireless.com" mmsc="http://mms.myblue.com/servlets/mms" mmsproxy="63.99.231.135" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="035" carrier="Etex" apn="VZWINTERNET" mmsc="http://mmsi.etex.mobi" type="default,mms,dun,supl" mvno_match_data="etex" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="035" carrier="Etex" apn="VZWINTERNET" mmsc="http://mmsi.etex.mobi" type="default,mms,dun,supl" mvno_match_data="etex" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="035" carrier="etex" apn="CdmaNai" mmsc="http://mmsi.etex.mobi" type="mms" mvno_match_data="etex" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="040" carrier="Mta" apn="VZWINTERNET" mmsc="http://mmsc.mta.dataonair.net/" mmsproxy="209.4.229.85" mmsport="6672" type="default,mms,dun,supl" mvno_match_data="mta" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="040" carrier="Mta" apn="VZWINTERNET" mmsc="http://mmsc.mta.dataonair.net/" mmsproxy="209.4.229.85" mmsport="6672" type="default,mms,dun,supl" mvno_match_data="mta" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="040" carrier="mta" apn="CdmaNai" mmsc="http://mmsc.mta.dataonair.net/" mmsproxy="209.4.229.85" mmsport="6672" type="mms" mvno_match_data="mta" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="050" carrier="Alaskacomm" apn="VZWINTERNET" mmsc="http://mmsc1.acsalaska.net/servlets/mms" mvno_match_data="alaskacomm" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="050" carrier="Alaskacomm" apn="VZWINTERNET" mmsc="http://mmsc1.acsalaska.net/servlets/mms" type="default,mms,dun,supl" mvno_match_data="alaskacomm" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="050" carrier="alaskacomm" apn="CdmaNai" mmsc="http://mmsc1.acsalaska.net/servlets/mms" type="mms" mvno_match_data="alaskacomm" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="066" carrier="U.S.Cellular" apn="internet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" user="*" password="*" server="*"></apn>
+ <apn mcc="310" mnc="080" carrier="CorrMMS" apn="corrmms" mmsc="http://mms.iot1.com/corr/mms.php" mmsproxy="66.255.55.23" mmsport="80" type="mms"></apn>
+ <apn mcc="310" mnc="090" carrier="Edge MMS Prepay" apn="ppmms" mmsc="http://mms.edgemobile.net/mmsc" mmsproxy="12.108.12.13" mmsport="3128" type="mms"></apn>
+ <apn mcc="310" mnc="090" carrier="LTE DNSADMIN" apn="apndnsota.4g.mycricket.com" mmsc="http://mms.mycricket.com/servlets/mms" type="fota" authtype="0"></apn>
+ <apn mcc="310" mnc="090" carrier="LTE INTERNET" apn="4g.mycricket.com" mmsc="http://mms.mycricket.com/servlets/mms" type="default,dun,mms" authtype="0"></apn>
+ <apn mcc="310" mnc="090" carrier="MMS" apn="mms" mmsc="http://mms.edgemobile.net/mmsc" mmsproxy="12.108.12.13" mmsport="3128" type="mms"></apn>
+ <apn mcc="310" mnc="90" carrier="LTE ADMIN" apn="Apnota.4g.mycricket.com" mmsc="http://mms.mycricket.com/servlets/mms" type="default,dun,mms" authtype="0"></apn>
+ <apn mcc="310" mnc="100" carrier="PLAT-OTA-MMS" apn="plateaumms" mmsc="208.254.124.11:8514" mmsproxy="208.254.124.11" mmsport="8080" type="mms" password="mmsc"></apn>
+ <apn mcc="310" mnc="100" carrier="PLATMMS" apn="mms.plateau" mmsc="http://mms" mmsproxy="172.23.253.206" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="1"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="10"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="11"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="15"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="16"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="2"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="3"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" bearer="9"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="1"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="10"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="11"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="15"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="16"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="2"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="3"></apn>
+ <apn mcc="310" mnc="120" carrier="SPCS Global" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms" authtype="0" bearer="9"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="1" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="10" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="11" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="15" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="16" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="2" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="3" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" authtype="3" bearer="9" server="*"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" bearer="12"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" bearer="4"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" bearer="5"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" bearer="6"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" bearer="7"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" bearer="8"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" authtype="0" bearer="12"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" authtype="0" bearer="4"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" authtype="0" bearer="5"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" authtype="0" bearer="6"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" authtype="0" bearer="7"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs" authtype="0" bearer="8"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs,dun" bearer="12"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs,dun" bearer="4"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs,dun" bearer="5"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs,dun" bearer="6"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs,dun" bearer="7"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint" apn="sprint" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,supl,mms,ims,cbs,dun" bearer="8"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" authtype="0" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="310" mnc="120" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" authtype="0" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="130" carrier="Carolina west" apn="VZWINTERNET" mmsc="http://mms.cwwmms.com/cww/mms.php" mmsproxy="204.117.091.161" mmsport="80" type="default,mms,dun,supl" mvno_match_data="carolinawest" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="130" carrier="Carolina west" apn="VZWINTERNET" mmsc="http://mms.cwwmms.com/cww/mms.php" mmsproxy="204.117.091.161" mmsport="80" type="default,mms,dun,supl" mvno_match_data="carolinawest" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="130" carrier="My Multi Media" apn="mms.c1.ama" mmsc="http://mms.iot1.com/amarillo/mms.php" type="mms" user="cell1mms" password="cell1"></apn>
+ <apn mcc="310" mnc="130" carrier="carolinawest" apn="CdmaNai" mmsc="http://mms.cwwmms.com/cww/mms.php" mmsproxy="204.117.091.161" mmsport="80" type="mms" mvno_match_data="carolinawest" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="150" carrier="internet" apn="ndo" mmsc="http://mmsc.aiowireless.net" mmsproxy="proxy.aiowireless.net" mmsport="80" type="default,mms,fota,hipri,supl"></apn>
+ <apn mcc="310" mnc="160" carrier="MetroPCS 160" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="160" carrier="T-Mobile US 160" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="160" carrier="T-Mobile US 160" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="160" carrier="T-Mobile US 160" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="160" carrier="T-Mobile US 160" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="170" carrier="Cingular MMS" apn="wap.cingular" mmsc="http://mmsc.cingular.com" mmsproxy="66.209.11.32" mmsport="8080" type="mms" user="WAP@CINGULARGPRS.COM" password="CINGULAR1"></apn>
+ <apn mcc="310" mnc="180" carrier="WCW-MMS" apn="mms.wcc.net" mmsc="http://mms.wcc.net" mmsproxy="209.55.70.246" mmsport="80" type="mms" user="13257630000" password="mmsc" authtype="3"></apn>
+ <apn mcc="310" mnc="200" carrier="MetroPCS 200" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="200" carrier="T-Mobile US 200" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="200" carrier="T-Mobile US 200" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="200" carrier="T-Mobile US 200" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="200" carrier="T-Mobile US 200" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="210" carrier="MetroPCS 210" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="210" carrier="T-Mobile US 210" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="210" carrier="T-Mobile US 210" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="210" carrier="T-Mobile US 210" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="210" carrier="T-Mobile US 210" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="220" carrier="MetroPCS 220" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="220" carrier="T-Mobile US 220" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="220" carrier="T-Mobile US 220" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="220" carrier="T-Mobile US 220" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="220" carrier="T-Mobile US 220" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="230" carrier="MetroPCS 230" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="230" carrier="T-Mobile US 230" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="230" carrier="T-Mobile US 230" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="230" carrier="T-Mobile US 230" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="230" carrier="T-Mobile US 230" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="240" carrier="MetroPCS 240" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="240" carrier="T-Mobile US 240" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="240" carrier="T-Mobile US 240" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="240" carrier="T-Mobile US 240" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="240" carrier="T-Mobile US 240" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="250" carrier="MetroPCS 250" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="250" carrier="T-Mobile US 250" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="250" carrier="T-Mobile US 250" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="250" carrier="T-Mobile US 250" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="250" carrier="T-Mobile US 250" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="260" carrier="T-Mobile GPRS" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="260" carrier="T-Mobile GPRS" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="260" carrier="T-Mobile US" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="260" carrier="T-Mobile US" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="260" carrier="SIMPLE" apn="simple" mmsc="http://smpl.mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" mvno_match_data="534D" mvno_type="gid" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="260" carrier="Consumer Cellular" apn="wholesale" mmsc="http://wholesale.mmsmvno.com/mms/wapenc" mmsport="80" type="default,mms,supl,hipri" mvno_match_data="2AC9" mvno_type="gid"></apn>
+ <apn mcc="310" mnc="260" carrier="MetroPCS" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="260" carrier="TFWAP" apn="wap.tracfone" mmsc="http://mms.tracfone.com" type="default,mms,supl,hipri,fota" mvno_match_data="ddff" mvno_type="gid" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="260" carrier="TFWAP" apn="wap.tracfone" mmsc="http://mms.tracfone.com" type="default,mms,supl,hipri,fota" mvno_match_data="deff" mvno_type="gid" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="270" carrier="MetroPCS 270" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="270" carrier="T-Mobile US 270" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="270" carrier="T-Mobile US 270" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="270" carrier="T-Mobile US 270" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="270" carrier="T-Mobile US 270" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="300" carrier="MetroPCS 300" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="300" carrier="T-Mobile US 300" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="300" carrier="T-Mobile US 300" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="300" carrier="T-Mobile US 300" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="310" carrier="MetroPCS 310" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="310" carrier="T-Mobile US 310" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="310" carrier="T-Mobile US 310" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="310" carrier="T-Mobile US 310" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="310" carrier="T-Mobile US 310" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="320" carrier="Cellular One ClearSky MMS" apn="wap.c1csky.net" mmsc="http://mmsc.c1neaz.csky.us:6672" mmsproxy="209.4.229.94" mmsport="9401" type="mms"></apn>
+ <apn mcc="310" mnc="330" carrier="Alltel2" apn="VZWINTERNET" mmsc="http://mms.alltel.com/servlets/mms" mmsproxy="mms.alltel.com" mmsport="8080" type="default,mms,dun,supl" mvno_match_data="alltel2" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="330" carrier="Alltel2" apn="VZWINTERNET" mmsc="http://mms.alltel.com/servlets/mms" mmsproxy="mms.alltel.com" mmsport="8080" type="default,mms,dun,supl" mvno_match_data="alltel2" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="330" carrier="alltel2" apn="CdmaNai" mmsc="http://mms.alltel.com/servlets/mms" mmsproxy="mms.alltel.com" mmsport="8080" type="mms" mvno_match_data="alltel2" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="360" carrier="Pioneer" apn="VZWINTERNET" mmsc="http://mms1.zsend.com" type="default,mms,dun,supl" mvno_match_data="pioneer" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="360" carrier="Pioneer" apn="VZWINTERNET" mmsc="http://mms1.zsend.com" type="default,mms,dun,supl" mvno_match_data="pioneer" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="360" carrier="pioneer" apn="CdmaNai" mmsc="http://mms1.zsend.com" type="mms" mvno_match_data="pioneer" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="380" carrier="AWS MMS" apn="proxy" mmsc="http://mmsc.mymmode.com" mmsproxy="10.250.250.55" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="380" carrier="Cingular 380 ATT" apn="proxy" mmsc="http://mmsc.cingular.com/" mmsproxy="wireless.cingular.com" type="default,supl,mms"></apn>
+ <apn mcc="310" mnc="390" carrier="Celloneet MMS" apn="mms.celloneet.com" mmsc="http://mms.celloneet.com/servlets/mms" mmsproxy="63.99.231.135" mmsport="8080" type="mms" user="user1@mms.celloneet.com" password="celloneet"></apn>
+ <apn mcc="310" mnc="410" carrier="AT&amp;T PTA" apn="pta" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="default,mms,supl,hipri,dun" authtype="0"></apn>
+ <apn mcc="310" mnc="410" carrier="AT&amp;T US MMS" apn="phone" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="310" mnc="410" carrier="AT&amp;T US MMS" apn="wap.cingular" mmsc="http://mmsc.cingular.com" mmsproxy="wireless.cingular.com" mmsport="80" type="mms" authtype="0" proxy="wireless.cingular.com" port="80"></apn>
+ <apn mcc="310" mnc="410" carrier="ATT Nextgenphone" apn="nxtgenphone" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="default,mms,supl,fota,hipri"></apn>
+ <apn mcc="310" mnc="410" carrier="ATT Phone" apn="phone" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="default,mms,supl,fota"></apn>
+ <apn mcc="310" mnc="410" carrier="ATT Phone" apn="phone" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="default,mms,supl,fota" authtype="0"></apn>
+ <apn mcc="310" mnc="410" carrier="ATT WAP" apn="wap.cingular" mmsc="http://mmsc.cingular.com/" mmsproxy="wireless.cingular.com" type="default,supl,mms" server="cingulargprs.com" proxy="wireless.cingular.com" port="80"></apn>
+ <apn mcc="310" mnc="410" carrier="ATT WAP" apn="wap.cingular" mmsc="http://mmsc.cingular.com/" mmsproxy="wireless.cingular.com" type="default,supl,mms" authtype="0" server="cingulargprs.com" proxy="wireless.cingular.com" port="80"></apn>
+ <apn mcc="310" mnc="410" carrier="Defense Mobile" apn="PRODATA" mmsc="http://mmsc.mobile.att.net" mmsproxy="proxy.mobile.att.net" mmsport="80" type="default,mms,supl" mvno_match_data="60FF" mvno_type="gid" protocol="IP"></apn>
+ <apn mcc="310" mnc="410" carrier="TFDATA" apn="tfdata" mmsc="http://mms-tf.net" mmsproxy="mms3.tracfone.com" mmsport="80" type="default,mms,supl,hipri,fota" mvno_match_data="ddff" mvno_type="gid" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="410" carrier="TFDATA" apn="tfdata" mmsc="http://mms-tf.net" mmsproxy="mms3.tracfone.com" mmsport="80" type="default,mms,supl,hipri,fota" mvno_match_data="deff" mvno_type="gid" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="420" carrier="CBW Data" apn="wap.gocbw.com" mmsc="http://mms.gocbw.com:8088/mms" mmsproxy="216.68.79.202" mmsport="80" type="default,supl,mms"></apn>
+ <apn mcc="310" mnc="430" carrier="Gci" apn="VZWINTERNET" mmsc="http://mmsc.akdt.dataonair.net:6672/" type="default,mms,dun,supl" mvno_match_data="gci" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="430" carrier="Gci" apn="VZWINTERNET" mmsc="http://mmsc.akdt.dataonair.net:6672/" type="default,mms,dun,supl" mvno_match_data="gci" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="430" carrier="gci" apn="CdmaNai" mmsc="http://mmsc.akdt.dataonair.net:6672/" type="mms" mvno_match_data="gci" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="450" carrier="Viaero MMS" apn="mms" mmsc="http://mms.viaero.com" mmsproxy="10.168.3.23" mmsport="9401" type="mms"></apn>
+ <apn mcc="310" mnc="470" carrier="MediaNet" apn="wap.cingular" mmsc="http://mmsc.cingular.com" mmsproxy="66.209.11.32" mmsport="8080" type="default,supl,mms" user="WAP@CINGULARGPRS.COM" password="CINGULAR1"></apn>
+ <apn mcc="310" mnc="470" carrier="MediaNet" apn="wap.cingular" mmsc="http://mmsc.cingular.com" mmsproxy="66.209.11.32" mmsport="8080" type="default,supl,mms" user="WAP@CINGULARGPRS.COM" password="CINGULAR1" authtype="3"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Ota" apn="admin.4g.ntelos.com" mmsc="http://mms.ntelospcs.net" type="admin,fota,ota" protocol="IPV4V6" bearer="13" server="*"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Ota" apn="admin.4g.ntelos.com" mmsc="http://mms.ntelospcs.net" type="admin,fota,ota" protocol="IPV4V6" bearer="14" server="*"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Tether" apn="tethering.4g.ntelos.com" mmsc="http://mms.ntelospcs.net" type="dun,pam" protocol="IPV4V6" bearer="13" server="*"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Tether" apn="tethering.4g.ntelos.com" mmsc="http://mms.ntelospcs.net" type="dun,pam" protocol="IPV4V6" bearer="14" server="*"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Wireless" apn="CdmaNai" mmsc="http://mms.ntelospcs.net/" type="mms" mvno_match_data="ntelos" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Wireless" apn="internet.4g.ntelos.com" mmsc="http://mms.ntelospcs.net" type="default,internet,supl,hipri,mms" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="310" mnc="470" carrier="nTelos Wireless" apn="internet.4g.ntelos.com" mmsc="http://mms.ntelospcs.net" type="default,internet,supl,hipri,mms" authtype="0" protocol="IPV4V6" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="310" mnc="480" carrier="MediaNet" apn="wap.cingular" mmsc="http://mmsc.cingular.com" mmsproxy="66.209.11.32" mmsport="8080" type="default,supl,mms" user="WAP@CINGULARGPRS.COM" password="CINGULAR1"></apn>
+ <apn mcc="310" mnc="490" carrier="GoodCall Picture Message" apn="good.call" mmsc="http://mms.suncom.net:8088/mms" mmsproxy="66.150.33.125" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="490" carrier="GoodCall Picture Message" apn="good.call" mmsc="http://mms.suncom.net:8088/mms" mmsproxy="66.150.33.125" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="310" mnc="490" carrier="MetroPCS 490" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="490" carrier="Suncom MMS" apn="mms" mmsc="http://mms.suncom.net:8088/mms" mmsproxy="66.150.33.125" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="490" carrier="Suncom MMS" apn="mms" mmsc="http://mms.suncom.net:8088/mms" mmsproxy="66.150.33.125" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="310" mnc="490" carrier="T-Mobile US 490" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="490" carrier="T-Mobile US 490" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="490" carrier="T-Mobile US 490" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="490" carrier="T-Mobile US 490" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="530" carrier="MetroPCS 530" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="530" carrier="T-Mobile US 530" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="530" carrier="T-Mobile US 530" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="530" carrier="T-Mobile US 530" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="560" carrier="DobsonMMS" apn="dobsoncellularwap" mmsc="http://mmsc" mmsproxy="172.23.1.252" mmsport="8799" type="mms"></apn>
+ <apn mcc="310" mnc="570" carrier="Cellular One MMS" apn="clearsky" mmsc="http://mmsc.mtpcs.csky.us:6672/" mmsproxy="209.4.229.229" mmsport="9201" type="mms"></apn>
+ <apn mcc="310" mnc="570" carrier="ChinookMMS" apn="wapgw.chinookwireless.net" mmsc="http://mms.cellonenation.net/mms/" mmsproxy="204.181.155.195" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="580" carrier="Inland" apn="VZWINTERNET" mmsc="http://mms.inland3g.com/inland/mms.php" type="default,mms,dun,supl" mvno_match_data="inland" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="580" carrier="Inland" apn="VZWINTERNET" mmsc="http://mms.inland3g.com/inland/mms.php" type="default,mms,dun,supl" mvno_match_data="inland" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="580" carrier="MetroPCS 580" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="580" carrier="T-Mobile US 580" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="580" carrier="T-Mobile US 580" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="580" carrier="T-Mobile US 580" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="580" carrier="T-Mobile US 580" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="580" carrier="inland" apn="CdmaNai" mmsc="http://mms.inland3g.com/inland/mms.php" type="mms" mvno_match_data="inland" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="590" carrier="CellularOne MMS" apn="cellular1wap" mmsc="http://mmsc" mmsproxy="172.23.1.252" mmsport="8799" type="mms"></apn>
+ <apn mcc="310" mnc="590" carrier="CellularOne MMS" apn="cellular1wap" mmsc="http://mmsc" mmsproxy="172.23.1.252" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="310" mnc="590" carrier="MetroPCS 590" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="590" carrier="T-Mobile US 590" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="590" carrier="T-Mobile US 590" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="590" carrier="T-Mobile US 590" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="600" carrier="Cellcom" apn="Cellcom mms" mmsc="http://mms.cellcom.com:9201/cellcom/mms.php" type="mms" protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="600" carrier="Cellcom" apn="VZWINTERNET" mmsc="http://mms.cellcom.com/cellcom/mms.php" type="default,mms,dun,supl" mvno_match_data="cellcom" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="600" carrier="Cellcom" apn="VZWINTERNET" mmsc="http://mms.cellcom.com/cellcom/mms.php" type="default,mms,dun,supl" mvno_match_data="cellcom" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="600" carrier="cellcom" apn="CdmaNai" mmsc="http://mms.cellcom.com/cellcom/mms.php" type="mms" mvno_match_data="cellcom" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="610" carrier="EpicMMS" apn="mms.epictouch" mmsc="http://mmsc.westlinkcom.com/servlets/mms" mmsproxy="63.99.231.135" mmsport="8080" type="mms"></apn>
+ <apn mcc="310" mnc="640" carrier="MetroPCS 640" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="640" carrier="T-Mobile US 640" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="640" carrier="T-Mobile US 640" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="640" carrier="T-Mobile US 640" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="660" carrier="MetroPCS 660" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="660" carrier="T-Mobile US 660" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="660" carrier="T-Mobile US 660" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="660" carrier="T-Mobile US 660" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="660" carrier="T-Mobile US 660" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="750" carrier="Appalachian" apn="VZWINTERNET" mmsc="http://mms.ekn.com" mmsport="80" type="default,mms,dun,supl" mvno_match_data="appalachian" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="750" carrier="Appalachian" apn="VZWINTERNET" mmsc="http://mms.ekn.com" mmsport="80" type="default,mms,dun,supl" mvno_match_data="appalachian" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="750" carrier="appalachian" apn="CdmaNai" mmsc="http://mms.ekn.com" mmsport="80" type="mms" mvno_match_data="appalachian" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="770" carrier="Picture Messaging" apn="wap1.iwireless.com" mmsc="http://mmsc.iwireless.dataonair.net:6672" mmsproxy="209.4.229.31" mmsport="9401" type="mms"></apn>
+ <apn mcc="310" mnc="770" carrier="Picture Messaging" apn="wap1.iwireless.com" mmsc="http://mmsc.iwireless.dataonair.net:6672" mmsproxy="209.4.229.32" mmsport="9401" type="mms"></apn>
+ <apn mcc="310" mnc="800" carrier="MetroPCS 800" apn="fast.metropcs.com" mmsc="http://metropcs.mmsmvno.com/mms/wapenc" type="default,supl,mms" mvno_match_data="6D" mvno_type="gid" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="800" carrier="T-Mobile US 800" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"></apn>
+ <apn mcc="310" mnc="800" carrier="T-Mobile US 800" apn="epc.tmobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,mms,supl,hipri,fota" user="none" password="none" authtype="3" protocol="IP" server="*"></apn>
+ <apn mcc="310" mnc="800" carrier="T-Mobile US 800" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="800" carrier="T-Mobile US 800" apn="fast.t-mobile.com" mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc" type="default,supl,mms" authtype="0" protocol="IPV6" roaming_protocol="IP"></apn>
+ <apn mcc="310" mnc="820" carrier="Nepa" apn="VZWINTERNET" mmsc="http://mmsc.c1nepa.csky.us:6672/" type="default,mms,dun,supl" mvno_match_data="nepa" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="820" carrier="Nepa" apn="VZWINTERNET" mmsc="http://mmsc.c1nepa.csky.us:6672/" type="default,mms,dun,supl" mvno_match_data="nepa" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="820" carrier="nepa" apn="CdmaNai" mmsc="http://mmsc.c1nepa.csky.us:6672/" type="mms" mvno_match_data="nepa" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="840" carrier="Edge MMS Prepay" apn="ppmms" mmsc="http://mms.edgemobile.net/mmsc" mmsproxy="12.108.12.13" mmsport="3128" type="mms"></apn>
+ <apn mcc="310" mnc="840" carrier="MMS" apn="mms" mmsc="http://mms.edgemobile.net/mmsc" mmsproxy="12.108.12.13" mmsport="3128" type="mms"></apn>
+ <apn mcc="310" mnc="880" carrier="DTC MMS" apn="mms.adv.com" mmsc="http://mms.iot1.com/advantage/mms.php" type="mms"></apn>
+ <apn mcc="310" mnc="900" carrier="Midrivers" apn="VZWINTERNET" mmsc="http://mmsc.midrivers.csky.us:6672/" mmsproxy="209.4.229.27" mmsport="9401" type="default,mms,dun,supl" mvno_match_data="midrivers" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="900" carrier="Midrivers" apn="VZWINTERNET" mmsc="http://mmsc.midrivers.csky.us:6672/" mmsproxy="209.4.229.27" mmsport="9401" type="default,mms,dun,supl" mvno_match_data="midrivers" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="900" carrier="midrivers" apn="CdmaNai" mmsc="http://mmsc.midrivers.csky.us:6672/" mmsproxy="209.4.229.27" mmsport="9401" type="mms" mvno_match_data="midrivers" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="910" carrier="WOW_WAP" apn="wap.firstcellular.com" mmsc="mms.firstcellular.net/mmsc" mmsproxy="10.101.1.5" mmsport="3128" type="default,supl,mms"></apn>
+ <apn mcc="310" mnc="920" carrier="Jamesvalley" apn="VZWINTERNET" mmsc="http://m.iot1.com/jamesvalley/mms.php" mmsproxy="m.iot1.com" mmsport="9201" type="default,mms,dun,supl" mvno_match_data="jamesvalley" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="920" carrier="Jamesvalley" apn="VZWINTERNET" mmsc="http://m.iot1.com/jamesvalley/mms.php" mmsproxy="m.iot1.com" mmsport="9201" type="default,mms,dun,supl" mvno_match_data="jamesvalley" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="920" carrier="jamesvalley" apn="CdmaNai" mmsc="http://m.iot1.com/jamesvalley/mms.php" mmsproxy="m.iot1.com" mmsport="9201" type="mms" mvno_match_data="jamesvalley" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="930" carrier="Copper Valley" apn="VZWINTERNET" mmsc="http://cvwmms.com/servlets/mms" type="default,mms,dun,supl" mvno_match_data="coppervalley" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="930" carrier="Copper Valley" apn="VZWINTERNET" mmsc="http://cvwmms.com/servlets/mms" type="default,mms,dun,supl" mvno_match_data="coppervalley" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="930" carrier="coppervalley" apn="CdmaNai" mmsc="http://cvwmms.com/servlets/mms" type="mms" mvno_match_data="coppervalley" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="960" carrier="Nntcwire" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="nntcwire" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Nntcwire" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="nntcwire" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Silverstar" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="silverstar" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Silverstar" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="silverstar" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Snakeriver" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="snakeriver" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Snakeriver" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="snakeriver" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Southcentral" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="southcentral" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Southcentral" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="southcentral" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Syringa" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="syringa" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="Syringa" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="syringa" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="nntcwire" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="nntcwire" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="960" carrier="silverstar" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="silverstar" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="960" carrier="snakeriver" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="snakeriver" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="960" carrier="southcentral" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="southcentral" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="960" carrier="strata" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="strata" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="310" mnc="960" carrier="strata" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="strata" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="strata" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="strata" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="310" mnc="960" carrier="syringa" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="syringa" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="050" carrier="Thumbcellular" apn="VZWINTERNET" mmsc="http://mms.thumbcell.com/thumb/mms.php" type="default,mms,dun,supl" mvno_match_data="thumbcellular" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="050" carrier="Thumbcellular" apn="VZWINTERNET" mmsc="http://mms.thumbcell.com/thumb/mms.php" type="default,mms,dun,supl" mvno_match_data="thumbcellular" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="050" carrier="thumbcellular" apn="CdmaNai" mmsc="http://mms.thumbcell.com/thumb/mms.php" type="mms" mvno_match_data="thumbcellular" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="070" carrier="Elementmobile" apn="VZWINTERNET" mmsc="http://mms.elementmobile.net" mmsport="8080" type="default,mms,dun,supl" mvno_match_data="elementmobile" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="070" carrier="Elementmobile" apn="VZWINTERNET" mmsc="http://mms.elementmobile.net" mmsport="8080" type="default,mms,dun,supl" mvno_match_data="elementmobile" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="070" carrier="elementmobile" apn="CdmaNai" mmsc="http://mms.elementmobile.net" mmsport="8080" type="mms" mvno_match_data="elementmobile" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="080" carrier="PINE WAP" apn="PINE" mmsc="http://69.8.34.146/mms/" mmsproxy="69.8.34.146" mmsport="9401" type="default,mms"></apn>
+ <apn mcc="311" mnc="100" carrier="NexTech Wireless" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="mms" authtype="3" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="311" mnc="100" carrier="NexTech Wireless" apn="CdmaNai" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="mms" protocol="IPV4V6" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="140" carrier="Sprocket" apn="VZWINTERNET" mmsc="http://mms.sprocketwireless.com" type="default,mms,dun,supl" mvno_match_data="sprocket" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="140" carrier="Sprocket" apn="VZWINTERNET" mmsc="http://mms.sprocketwireless.com" type="default,mms,dun,supl" mvno_match_data="sprocket" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="140" carrier="sprocket" apn="CdmaNai" mmsc="http://mms.sprocketwireless.com" type="mms" mvno_match_data="sprocket" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="190" carrier="MMS" apn="mms.cellular1.net" mmsc="http://mms.cellular1.net" mmsproxy="10.10.0.97" mmsport="9201" type="mms"></apn>
+ <apn mcc="311" mnc="190" carrier="MMS" apn="wap.cellular1.net" mmsc="http://mms.cellular1.net/ecit/mms.php" type="mms"></apn>
+ <apn mcc="311" mnc="210" carrier="Farmers MMS" apn="mms.farmers.com" mmsc="172.16.0.37:8514" type="mms"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="220" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="220" carrier="USCC" apn="internet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" mmsport="80" type="mms" authtype="3"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="221" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="222" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="223" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="224" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="true"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="225" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="true"></apn>
+ <apn mcc="311" mnc="225" carrier="USCC INTERNET 2" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,dun,mms,fota"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="226" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="227" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="228" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="229" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire" apn="CdmaNai" mmsc="http://pix.cspire.com/servlets/mms" mmsproxy="66.175.144.91" mmsport="80" type="mms" mvno_match_data="cspire" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire" apn="Cspire mms" mmsc="http://pix.cspire.com/servlets/mms" type="mms" protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire internet" apn="internet.cs4glte.com" mmsc="http://pix.cspire.com" type="default,internet,mms" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire internet" apn="internet.cs4glte.com" mmsc="http://pix.cspire.com" type="default,internet,mms" protocol="IPV4V6" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire ota" apn="admin.cs4glte.com" mmsc="http://pix.cspire.com" type="admin,fota,ota" protocol="IPV4V6" bearer="13" server="*"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire ota" apn="admin.cs4glte.com" mmsc="http://pix.cspire.com" type="admin,fota,ota" protocol="IPV4V6" bearer="13" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire ota" apn="admin.cs4glte.com" mmsc="http://pix.cspire.com" type="admin,fota,ota" protocol="IPV4V6" bearer="14" server="*"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire ota" apn="admin.cs4glte.com" mmsc="http://pix.cspire.com" type="admin,fota,ota" protocol="IPV4V6" bearer="14" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire tether" apn="tethering.cs4glte.com" mmsc="http://pix.cspire.com" type="dun,pam" protocol="IPV4V6" bearer="13" server="*"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire tether" apn="tethering.cs4glte.com" mmsc="http://pix.cspire.com" type="dun,pam" protocol="IPV4V6" bearer="13" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire tether" apn="tethering.cs4glte.com" mmsc="http://pix.cspire.com" type="dun,pam" protocol="IPV4V6" bearer="14" server="*"></apn>
+ <apn mcc="311" mnc="230" carrier="CSpire tether" apn="tethering.cs4glte.com" mmsc="http://pix.cspire.com" type="dun,pam" protocol="IPV4V6" bearer="14" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="310" carrier="Leaco" apn="VZWINTERNET" mmsc="http://204.181.155.217/mms/" type="default,mms,dun,supl" mvno_match_data="leaco" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="310" carrier="Leaco" apn="VZWINTERNET" mmsc="http://204.181.155.217/mms/" type="default,mms,dun,supl" mvno_match_data="leaco" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="310" carrier="leaco" apn="CdmaNai" mmsc="http://204.181.155.217/mms/" type="mms" mvno_match_data="leaco" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="340" carrier="Illinois valley" apn="VZWINTERNET" mmsc="http://mms.iot1.com/ivc/mms.php" type="default,mms,dun,supl" mvno_match_data="illinoisvalley" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="340" carrier="Illinois valley" apn="VZWINTERNET" mmsc="http://mms.iot1.com/ivc/mms.php" type="default,mms,dun,supl" mvno_match_data="illinoisvalley" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="340" carrier="illinoisvalley" apn="CdmaNai" mmsc="http://mms.iot1.com/ivc/mms.php" type="mms" mvno_match_data="illinoisvalley" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="350" carrier="Nemont" apn="VZWINTERNET" mmsc="http://mms.nemont.mobi/mms/" type="default,mms,dun,supl" mvno_match_data="nemont" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="350" carrier="Nemont" apn="VZWINTERNET" mmsc="http://mms.nemont.mobi/mms/" type="default,mms,dun,supl" mvno_match_data="nemont" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="350" carrier="nemont" apn="CdmaNai" mmsc="http://mms.nemont.mobi/mms/" type="mms" mvno_match_data="nemont" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="370" carrier="ACS MMS" apn="mms.acs" mmsc="http://mmsc.acsalaska.net" mmsproxy="209.4.229.92" mmsport="9201" type="mms" mvno_match_data="3113702" mvno_type="imsi"></apn>
+ <apn mcc="311" mnc="370" carrier="GCI MMS" apn="mms.gci" mmsc="http://mmsc.gci.csky.us:6672" mmsproxy="209.4.229.92" mmsport="9201" type="mms" proxy="209.4.229.92" port="9201"></apn>
+ <apn mcc="311" mnc="390" carrier="Ericsson Test-SIM CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="390" carrier="Ericsson Test-SIM CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="390" carrier="Ericsson Test-SIM CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="390" carrier="Ericsson Test-SIM Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="390" carrier="Ericsson Test-SIM Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,dun" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="390" carrier="Ericsson Test-SIM Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="410" carrier="Chatmobrsa2" apn="VZWINTERNET" mmsc="http://mmsc.hawkeyeswitch.net/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="chatmobrsa2" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="410" carrier="Chatmobrsa2" apn="VZWINTERNET" mmsc="http://mmsc.hawkeyeswitch.net/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="chatmobrsa2" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="410" carrier="chatmobrsa2" apn="CdmaNai" mmsc="http://mmsc.hawkeyeswitch.net/mms/" mmsport="80" type="mms" mvno_match_data="chatmobrsa2" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="420" carrier="Northwestcell" apn="VZWINTERNET" mmsc="http://mms.nwmcell.com/mms/" type="default,mms,dun,supl" mvno_match_data="northwestcell" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="420" carrier="Northwestcell" apn="VZWINTERNET" mmsc="http://mms.nwmcell.com/mms/" type="default,mms,dun,supl" mvno_match_data="northwestcell" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="420" carrier="northwestcell" apn="CdmaNai" mmsc="http://mms.nwmcell.com/mms/" type="mms" mvno_match_data="northwestcell" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="430" carrier="Chatmobrsa1" apn="VZWINTERNET" mmsc="http://mmsc.hawkeyeswitch.net/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="chatmobrsa1" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="430" carrier="Chatmobrsa1" apn="VZWINTERNET" mmsc="http://mmsc.hawkeyeswitch.net/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="chatmobrsa1" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="430" carrier="chatmobrsa1" apn="CdmaNai" mmsc="http://mmsc.hawkeyeswitch.net/mms/" mmsport="80" type="mms" mvno_match_data="chatmobrsa1" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="440" carrier="Bluegrass" apn="Bluegrass mms" mmsc="http://m.iot1.com/bluegrass/mms.php" type="mms" protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="440" carrier="Bluegrass" apn="VZWINTERNET" mmsc="http://mms.iot1.com/bluegrass/mms.php" type="default,mms,dun,supl" mvno_match_data="bluegrass" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="440" carrier="Bluegrass" apn="VZWINTERNET" mmsc="http://mms.iot1.com/bluegrass/mms.php" type="default,mms,dun,supl" mvno_match_data="bluegrass" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="440" carrier="bluegrass" apn="CdmaNai" mmsc="http://mms.iot1.com/bluegrass/mms.php" type="mms" mvno_match_data="bluegrass" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="450" carrier="Ptci" apn="VZWINTERNET" mmsc="http://mmsc.ptci.net" mmsport="6672" type="default,mms,dun,supl" mvno_match_data="ptci" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="450" carrier="Ptci" apn="VZWINTERNET" mmsc="http://mmsc.ptci.net" mmsport="6672" type="default,mms,dun,supl" mvno_match_data="ptci" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="450" carrier="ptci" apn="CdmaNai" mmsc="http://mmsc.ptci.net" mmsport="6672" type="mms" mvno_match_data="ptci" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="480" carrier="EHRPD - Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="LTE - Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="1" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="10" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="11" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="15" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="2" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="3" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,mms" protocol="IPV4V6" bearer="9" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="12"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="4"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="5"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="7"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs" authtype="3" protocol="IPV4V6" bearer="8"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="12"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="4"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="5"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="6"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="7"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon" apn="internet" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,supl,fota,ims,cbs,dun" authtype="3" protocol="IPV4V6" bearer="8"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="1" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="10" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="11" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="15" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="2" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="3" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CBS" apn="VZWAPP" mmsc="http://mms.vtext.com/servlets/mms" type="cbs,hipri" protocol="IPV4V6" bearer="9" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CDMA HRPD" apn="CdmaNai" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,hipri,dun,stdhipri,supl" protocol="IPV4V6" bearer="6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon CDMA HRPD" apn="CdmaNai" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,hipri,dun,stdhipri,supl" protocol="IPV4V6" bearer="6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="1" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="10" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="11" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="15" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="2" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="3" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri" protocol="IPV4V6" bearer="9" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri,supl" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="480" carrier="Verizon Internet" apn="VZWINTERNET" mmsc="http://mms.vtext.com/servlets/mms" type="default,mms,dun,stdhipri,supl" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="1" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="10" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="11" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="15" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="16" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="2" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="3" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="cinet.spcs" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" user="none" password="none" bearer="9" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint" apn="mms_over_wifi" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="wifi" user="*" password="*" server="*"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint CdmaNai" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="default,mms" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="490" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mmsc.vmobl.com:8088" mmsproxy="68.28.31.2" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="490" carrier="Virgin Mobile US" apn="0" mmsc="http://mmsc.vmobl.com:8088/mms?" mmsproxy="205.239.233.136" mmsport="81" authtype="0"></apn>
+ <apn mcc="311" mnc="500" carrier="24-7 WAP" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672" mmsproxy="09.4.229.46" mmsport="9201" mvno_match_data="24-7 Wireless" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="24-7 WAP AT&amp;T" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672" mmsproxy="09.4.229.46" mmsport="9201" mvno_match_data="24-7 Wireless RPA" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="24-7 WAP Other networks" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672" mmsproxy="09.4.229.46" mmsport="9201" mvno_match_data="24-7 Wireless RPO" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="24-7 WAP T-Mobile" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672" mmsproxy="09.4.229.46" mmsport="9201" mvno_match_data="24-7 Wireless RPT" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Mosaic WAP" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Mosaic Mobile" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Mosaic WAP AT&amp;T" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Mosaic RPA" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Mosaic WAP Other networks" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Mosaic RPO" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Mosaic WAP T-Mobile" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Mosaic RPT" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Norvado" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Norvado Wireless" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Norvado AT&amp;T" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Norvado Wireless RPA" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Norvado Other networks" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Norvado Wireless RPO" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="500" carrier="Norvado T-Mobile" apn="wap" mmsc="http://mmsc.ctc.csky.us:6672/" mmsproxy="209.4.229.46" mmsport="9201" type="default,mms" mvno_match_data="Norvado Wireless RPT" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="530" carrier="Blaze" apn="mms.mymobiletxt.com" mmsc="http://mms2.mymobiletxt.net" type="default,mms" mvno_match_data="Blaze Wireless" mvno_type="spn"></apn>
+ <apn mcc="311" mnc="530" carrier="Duet Internet" apn="wap.mymobiletxt.com" mmsc="http://172.16.16.103/mms/" mmsproxy="172.16.16.102" mmsport="8080" type="default,mms" protocol="IP"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="580" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="580" carrier="USCC INTERNET" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,dun,mms,fota"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="581" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="582" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="583" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="584" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="585" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="586" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="587" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="588" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="12" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="4" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="5" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="7" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" authtype="3" protocol="IPV4V6" bearer="8" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" apn="CdmaNai" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="589" carrier="U.S. Cellular" apn="usccinternet" mmsc="http://mmsc1.uscc.net/mmsc/MMS" type="default,mms,dun,hipri,fota" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="590" carrier="Gsc" apn="VZWINTERNET" mmsc="http://mmsc1.gscdata.com" type="default,mms,dun,supl" mvno_match_data="gsc" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="590" carrier="Gsc" apn="VZWINTERNET" mmsc="http://mmsc1.gscdata.com" type="default,mms,dun,supl" mvno_match_data="gsc" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="590" carrier="gsc" apn="CdmaNai" mmsc="http://mmsc1.gscdata.com" type="mms" mvno_match_data="gsc" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="600" carrier="Cox" apn="VZWINTERNET" mmsc="http://mms.cox.net/cox/mms.php" type="default,mms,dun,supl" mvno_match_data="cox" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="600" carrier="Cox" apn="VZWINTERNET" mmsc="http://mms.cox.net/cox/mms.php" type="default,mms,dun,supl" mvno_match_data="cox" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="600" carrier="cox" apn="CdmaNai" mmsc="http://mms.cox.net/cox/mms.php" type="mms" mvno_match_data="cox" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="610" carrier="Srtcomm" apn="VZWINTERNET" mmsc="http://mms.iot1.com/srt/mms.php" mmsproxy="mms.iot1.com" mmsport="9201" type="default,mms,dun,supl" mvno_match_data="srtcomm" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="610" carrier="Srtcomm" apn="VZWINTERNET" mmsc="http://mms.iot1.com/srt/mms.php" mmsproxy="mms.iot1.com" mmsport="9201" type="default,mms,dun,supl" mvno_match_data="srtcomm" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="610" carrier="srtcomm" apn="CdmaNai" mmsc="http://mms.iot1.com/srt/mms.php" mmsproxy="mms.iot1.com" mmsport="9201" type="mms" mvno_match_data="srtcomm" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="650" carrier="Unitedwireless" apn="VZWINTERNET" mmsc="http://mms.unitedwireless.com/united/mms.php" type="default,mms,dun,supl" mvno_match_data="unitedwireless" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="650" carrier="Unitedwireless" apn="VZWINTERNET" mmsc="http://mms.unitedwireless.com/united/mms.php" type="default,mms,dun,supl" mvno_match_data="unitedwireless" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="650" carrier="unitedwireless" apn="CdmaNai" mmsc="http://mms.unitedwireless.com/united/mms.php" type="mms" mvno_match_data="unitedwireless" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="660" carrier="MetroPCS IMS" apn="ims.metropcs" mmsc="http://mms.metropcs.net:3128/mmsc" type="ims" authtype="0"></apn>
+ <apn mcc="311" mnc="660" carrier="MetroPCS LTE" apn="internet.metropcs" mmsc="http://mms.metropcs.net:3128/mmsc" type="default,hipri,admin,mms" authtype="0"></apn>
+ <apn mcc="311" mnc="670" carrier="Pinebelt" apn="VZWINTERNET" mmsc="http://mmsc.pinebelt.csky.us:6672/" type="default,mms,dun,supl" mvno_match_data="pinebelt" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="670" carrier="Pinebelt" apn="VZWINTERNET" mmsc="http://mmsc.pinebelt.csky.us:6672/" type="default,mms,dun,supl" mvno_match_data="pinebelt" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="670" carrier="pinebelt" apn="CdmaNai" mmsc="http://mmsc.pinebelt.csky.us:6672/" type="mms" mvno_match_data="pinebelt" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="870" carrier="Internet" apn="n.ispsn" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default" protocol="IPV4V6" bearer="0"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="1" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="10" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="11" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="15" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="16" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="2" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="3" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="cinet.spcs" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="9" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint" apn="mms_over_wifi" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="wifi" user="*" password="*" server="*"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint CdmaNai" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="311" mnc="870" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mm.myboostmobile.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="311" mnc="910" carrier="MobileNation" apn="mymn4g.net" mmsc="http://mms.mymn3g.net" mmsproxy="mms.mymn3g.net" mmsport="8081" type="default,internet,admin,fota,dun,mms" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="311" mnc="920" carrier="Chariton valley" apn="VZWINTERNET" mmsc="http://mms.cvalley.net" mmsport="80" type="default,mms,dun,supl" mvno_match_data="charitonvalley" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="920" carrier="Chariton valley" apn="VZWINTERNET" mmsc="http://mms.cvalley.net" mmsport="80" type="default,mms,dun,supl" mvno_match_data="charitonvalley" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="311" mnc="920" carrier="charitonvalley" apn="CdmaNai" mmsc="http://mms.cvalley.net" mmsport="80" type="mms" mvno_match_data="charitonvalley" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="311" mnc="930" carrier="Syringa Wireless" apn="internet.syringawireless.com" mmsc="http://mms.rinawireless.com" mmsport="80" type="default,internet,admin,fota,dun,mms" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="312" mnc="040" carrier="Custer" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="custer" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="312" mnc="040" carrier="Custer" apn="VZWINTERNET" mmsc="http://mms.rinawireless.com" type="default,mms,dun,supl" mvno_match_data="custer" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="312" mnc="040" carrier="custer" apn="CdmaNai" mmsc="http://mms.rinawireless.com" type="mms" mvno_match_data="custer" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="312" mnc="160" carrier="chatmobility" apn="CdmaNai" mmsc="http://mms.chatmobility.com/mms/" mmsport="80" type="mms" mvno_match_data="chatmobility" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="312" mnc="160" carrier="chatmobility" apn="VZWINTERNET" mmsc="http://mms.chatmobility.com/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="chatmobility" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="312" mnc="160" carrier="chatmobility" apn="VZWINTERNET" mmsc="http://mms.chatmobility.com/mms/" mmsport="80" type="default,mms,dun,supl" mvno_match_data="chatmobility" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Ota" apn="admin.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="admin,fota,ota" protocol="IP" bearer="13" server="*"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Ota" apn="admin.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="admin,fota,ota" protocol="IP" bearer="13" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Ota" apn="admin.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="admin,fota,ota" protocol="IP" bearer="14" server="*"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Ota" apn="admin.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="admin,fota,ota" protocol="IP" bearer="14" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Tether" apn="modem.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="dun,pam" protocol="IPV4V6" bearer="13" server="*"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Tether" apn="modem.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="dun,pam" protocol="IPV4V6" bearer="13" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Tether" apn="modem.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="dun,pam" protocol="IPV4V6" bearer="14" server="*"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Tether" apn="modem.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="dun,pam" protocol="IPV4V6" bearer="14" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Wireless" apn="internet.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="default,internet,supl,hipri,mms" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="312" mnc="420" carrier="NexTech Wireless" apn="internet.lte.ntwls.com" mmsc="http://mms.ntwls.net/nex-tech/mms.php" type="default,internet,supl,hipri,mms" protocol="IPV4V6" server="*" carrier_enabled="TRUE"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="1" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="10" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="11" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="15" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="16" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="2" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="3" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" user="none" password="none" bearer="9" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint" apn="mms_over_wifi" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="wifi" user="*" password="*" server="*"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default,mms" authtype="3" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="FALSE"></apn>
+ <apn mcc="312" mnc="530" carrier="Sprint CdmaNai" apn="CdmaNai" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="mms" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="TRUE"></apn>
+ <apn mcc="312" mnc="570" carrier="Blue Wireless" apn="Blue Hotspot" mmsc="http://mms.blueunlimited.com" mmsport="8514" type="default,dun,mms" user="%M@dun.bluehandset.com" password="wirelessblue" protocol="IPV4V6" server="*"></apn>
+ <apn mcc="316" mnc="010" carrier="Sprint APN" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default" user="none" password="none" server="*"></apn>
+ <apn mcc="316" mnc="10" carrier="Sprint" apn="mms_over_wifi" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="wifi" user="*" password="*" server="*"></apn>
+ <apn mcc="316" mnc="10" carrier="Sprint APN" apn="cinet.spcs" mmsc="http://mms.sprintpcs.com" mmsproxy="68.28.31.7" mmsport="80" type="default" user="none" password="none" server="*"></apn>
+ <apn mcc="330" mnc="000" carrier="Openmobile" apn="VZWINTERNET" mmsc="http://mms.openmobilepr.com:1981/" type="default,mms,dun,supl" mvno_match_data="openmobile" mvno_type="spn" protocol="IPV4V6" bearer="13" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="330" mnc="000" carrier="Openmobile" apn="VZWINTERNET" mmsc="http://mms.openmobilepr.com:1981/" type="default,mms,dun,supl" mvno_match_data="openmobile" mvno_type="spn" protocol="IPV4V6" bearer="14" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="330" mnc="000" carrier="openmobile" apn="CdmaNai" mmsc="http://mms.openmobilepr.com:1981/" type="mms" mvno_match_data="openmobile" mvno_type="spn" protocol="IPV4V6" bearer="6" roaming_protocol="IPV4V6" carrier_enabled="false"></apn>
+ <apn mcc="330" mnc="110" carrier="MMS CLARO" apn="mms.claropr.com" mmsc="http://mmsg.claropr.com:10021/mmsc" mmsproxy="10.50.38.3" mmsport="8799" type="mms"></apn>
+ <apn mcc="330" mnc="110" carrier="MMS CLARO" apn="mms.claropr.com" mmsc="http://mmsg.claropr.com:10021/mmsc" mmsproxy="10.50.38.3" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="330" mnc="110" carrier="Puerto Rico:Claro:MMS" apn="mms.claropr.com" mmsc="http://mmsg.claropr.com:10021/mmsc" mmsproxy="10.50.38.3" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="330" mnc="110" carrier="Puerto Rico:Claro:MMS" apn="mmslte.claropr.com" mmsc="http://mmsg.claropr.com:10021/mmsc" mmsproxy="10.50.38.3" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="334" mnc="03" carrier="Movistar MMS" apn="mms.movistar.mx" mmsc="http://mms.movistar.mx" mmsproxy="10.2.20.1" mmsport="80" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="334" mnc="3" carrier="Movistar MMS" apn="mms.movistar.mx" mmsc="http://mms.movistar.mx" mmsproxy="10.2.20.1" mmsport="80" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="334" mnc="020" carrier="Mensajes Multimedia" apn="mms.itelcel.com" mmsc="http://mms.itelcel.com/servlets/mms" mmsproxy="148.233.151.240" mmsport="8080" type="mms" user="mmsgprs" password="mmsgprs2003" authtype="1"></apn>
+ <apn mcc="334" mnc="20" carrier="Mexico:Telcel:Mensajes Multimedia" apn="mms.itelcel.com" mmsc="http://mms.itelcel.com/servlets/mms" mmsproxy="148.233.151.240" mmsport="8080" type="mms" user="mmsgprs" password="mmsgprs2003" authtype="1"></apn>
+ <apn mcc="334" mnc="030" carrier="Movistar MMS" apn="mms.movistar.mx" mmsc="http://mms.movistar.mx" mmsproxy="10.2.20.1" mmsport="80" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="334" mnc="30" carrier="Movistar MMS" apn="mms.movistar.mx" mmsc="http://mms.movistar.mx" mmsproxy="10.2.20.1" mmsport="80" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="334" mnc="050" carrier="Iusacell MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm"></apn>
+ <apn mcc="334" mnc="50" carrier="Iusacell MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm" authtype="3"></apn>
+ <apn mcc="334" mnc="50" carrier="Mexico:Iusacell MMS" apn="mms.iusacellgsm.mx" mmsc="http://mms.iusacell3g.com/" type="mms" user="mmsiusacellgsm" password="mmsiusacellgsm" authtype="0"></apn>
+ <apn mcc="334" mnc="090" carrier="Nextel MMS" apn="mms.nexteldata.com.mx" mmsc="http://3gmms.nexteldata.com.mx" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="334" mnc="90" carrier="Nextel MMS" apn="mms.nexteldata.com.mx" mmsc="http://3gmms.nexteldata.com.mx" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="338" mnc="05" carrier="MMS Digicel" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="8080" type="mms"></apn>
+ <apn mcc="338" mnc="5" carrier="Jamaica:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="338" mnc="5" carrier="MMS Digicel" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="338" mnc="18" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="338" mnc="18" carrier="Lime Prepaid MMS" apn="ppmms" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="338" mnc="050" carrier="MMS Digicel" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="8080" type="mms"></apn>
+ <apn mcc="338" mnc="50" carrier="MMS Digicel" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="8080" type="mms"></apn>
+ <apn mcc="338" mnc="070" carrier="Claro MMS" apn="mms.ideasclaro.com.jm" mmsc="http://mms.ideasclaro.com.jm/mms/wapenc" mmsproxy="190.80.147.118" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="338" mnc="70" carrier="Jamaica:Claro:MMS" apn="mms.ideasclaro.com.jm" mmsc="http://mms.ideasclaro.com.jm/mms/wapenc" mmsproxy="190.80.147.118" mmsport="8080" type="mms" user="claro" password="claro" authtype="1"></apn>
+ <apn mcc="338" mnc="180" carrier="Jamaica:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="338" mnc="180" carrier="Jamaica:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="338" mnc="180" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="338" mnc="180" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="338" mnc="180" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="338" mnc="180" carrier="Lime Prepaid MMS" apn="ppmms" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="338" mnc="180" carrier="Lime Prepaid MMS" apn="ppmms" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="340" mnc="01" carrier="Orange MMS Caraïbe" apn="orangewap" mmsc="http://193.251.160.246/servlets/mms" mmsproxy="10.0.0.10" mmsport="8082" type="mms" user="orange" password="orange"></apn>
+ <apn mcc="340" mnc="1" carrier="Orange MMS Caraïbe" apn="orangewap" mmsc="http://193.251.160.246/servlets/mms" mmsproxy="10.0.0.10" mmsport="8082" type="mms" user="orange" password="orange"></apn>
+ <apn mcc="340" mnc="20" carrier="Digicel Web" apn="web.digicelfr.com" mmsc="http://mmc.digiceltt.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="340" mnc="20" carrier="Digicel Web" apn="web.digicelfr.com" mmsc="http://mmc.digiceltt.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="340" mnc="20" carrier="French West Indies:Digicel:Mms" apn="wap.digicelfr.com" mmsc="http://mmc.digiceltt.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="9201" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="340" mnc="20" carrier="French West Indies:Digicel:Modem" apn="wap.digicelfr.com" mmsc="http://wapdigicel.com" type="dun" user="wap" password="wap" authtype="1" proxy="172.20.6.12" port="8080"></apn>
+ <apn mcc="342" mnc="60" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="342" mnc="600" carrier="Barbado:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="342" mnc="600" carrier="Barbado:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="342" mnc="600" carrier="Barbados:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="344" mnc="92" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="344" mnc="920" carrier="Antigua:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="344" mnc="920" carrier="Antigua:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="344" mnc="920" carrier="Antigua:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="346" mnc="14" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="346" mnc="140" carrier="Cayman Islands:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="346" mnc="140" carrier="Cayman Islands:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="346" mnc="140" carrier="Cayman Islands:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="348" mnc="17" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="348" mnc="77" carrier="Bvi:Digicel:Mms" apn="wap.digicelbvi.com" mmsc="http://mmc.digiceljamaica.com/servlets/mms" mmsproxy="172.16.7.12" mmsport="9201" type="mms" user="wapbvi" password="wapbvi" authtype="1"></apn>
+ <apn mcc="348" mnc="77" carrier="Bvi:Digicel:Modem" apn="wap.digicelbvi.com" mmsc="http://wapdigicel.com" type="dun" user="wapbvi" password="wapbvi" authtype="1" proxy="172.16.7.12" port="8080"></apn>
+ <apn mcc="348" mnc="170" carrier="Bvi:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="348" mnc="170" carrier="Bvi:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="348" mnc="170" carrier="Bvi:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="352" mnc="11" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="352" mnc="110" carrier="Grenada:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="352" mnc="110" carrier="Grenada:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="352" mnc="110" carrier="Grenada:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="354" mnc="86" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="354" mnc="860" carrier="Monserrat:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="354" mnc="860" carrier="Monserrat:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="354" mnc="860" carrier="Monserrat:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="356" mnc="11" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="356" mnc="110" carrier="St Kitts And Nevis:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="356" mnc="110" carrier="St Kitts And Nevis:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="356" mnc="110" carrier="St Kitts And Nevis:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="358" mnc="11" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="358" mnc="110" carrier="St Lucia:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="358" mnc="110" carrier="St Lucia:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="358" mnc="110" carrier="St Lucia:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="360" mnc="11" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="360" mnc="110" carrier="St Vincent:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="360" mnc="110" carrier="St Vincent:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="360" mnc="110" carrier="St Vincent:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="362" mnc="69" carrier="Curacao:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="362" mnc="69" carrier="Curacao:Digicel:Modem" apn="wap" mmsc="http://wapdigicel.com" type="dun" authtype="1" proxy="172.16.7.12" port="8080"></apn>
+ <apn mcc="363" mnc="02" carrier="MMS Digicel" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="363" mnc="2" carrier="Aruba:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="363" mnc="020" carrier="MMS Digicel" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="363" mnc="20" carrier="Aruba:Digicel:Mms:2" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="365" mnc="84" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="365" mnc="840" carrier="Anguilla:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="365" mnc="840" carrier="Anguilla:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="365" mnc="840" carrier="Anguilla:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="366" mnc="11" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="366" mnc="110" carrier="Dominica:Lime:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="366" mnc="110" carrier="Dominica:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="366" mnc="110" carrier="Dominica:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="368" mnc="01" carrier="Cubacel MMS" apn="mms" mmsc="http://mms.cubacel.cu" mmsproxy="200.13.145.52" mmsport="8080" type="mms"></apn>
+ <apn mcc="368" mnc="1" carrier="Cubacel MMS" apn="mms" mmsc="http://mms.cubacel.cu" mmsproxy="200.13.145.52" mmsport="8080" type="mms"></apn>
+ <apn mcc="370" mnc="01" carrier="Orange MMS" apn="orangeworld" mmsc="http://mms.orange.com.do/servlets/mms" mmsproxy="172.16.126.70" mmsport="8080" type="mms" user="orange" password="orange" authtype="1"></apn>
+ <apn mcc="370" mnc="1" carrier="Dominican Rep:Orange:MMS" apn="orangeworld" mmsc="http://mms.orange.com/servlets/mms" mmsproxy="172.16.126.70" mmsport="8080" type="mms" user="orange" password="orange" authtype="1" port="8080"></apn>
+ <apn mcc="370" mnc="1" carrier="Orange MMS" apn="orangeworld" mmsc="http://mms.orange.com.do/servlets/mms" mmsproxy="172.16.126.70" mmsport="8080" type="mms" user="orange" password="orange" authtype="1"></apn>
+ <apn mcc="370" mnc="02" carrier="MMS" apn="internet.ideasclaro.com.do" mmsc="http://mms.ideasclaro.com.do/mms/wapenc" type="mms"></apn>
+ <apn mcc="370" mnc="2" carrier="Dominican Republic:Claro:INTERNET" apn="internet.ideasclaro.com.do" mmsc="http://mms.ideasclaro.com.do/mms/wapenc" type="default" authtype="1"></apn>
+ <apn mcc="370" mnc="2" carrier="MMS" apn="internet.ideasclaro.com.do" mmsc="http://mms.ideasclaro.com.do/mms/wapenc" type="mms" authtype="0"></apn>
+ <apn mcc="370" mnc="04" carrier="Viva MMS" apn="mms.viva.net.do" mmsc="http://10.200.16.4/mms/wapenc" mmsproxy="192.168.16.10" mmsport="9401" type="mms" user="viva" password="viva" authtype="1"></apn>
+ <apn mcc="370" mnc="4" carrier="Dominican Rep:Viva:Internet" apn="edge.viva.net.do" mmsc="http://wap.viva.net.do/WapDeck/" type="default" user="viva" password="viva" authtype="1" proxy="192.168.16.10" port="9401"></apn>
+ <apn mcc="370" mnc="4" carrier="Dominican Rep:Viva:MMS" apn="mms.viva.net.do" mmsc="http://10.200.16.4/mms/wapenc" mmsproxy="192.168.16.10" mmsport="9401" type="mms" user="viva" password="viva" authtype="1"></apn>
+ <apn mcc="370" mnc="4" carrier="Viva MMS" apn="mms.viva.net.do" mmsc="http://10.200.16.4/mms/wapenc" mmsproxy="192.168.16.10" mmsport="9401" type="mms" user="viva" password="viva" authtype="1"></apn>
+ <apn mcc="370" mnc="020" carrier="MMS" apn="internet.ideasclaro.com.do" mmsc="http://mms.ideasclaro.com.do/mms/wapenc" type="mms"></apn>
+ <apn mcc="372" mnc="02" carrier="Haiti:Digicel:Mms" apn="wap.digicelha.com" mmsc="http://mmc.digicelhaiti.com/servlets/mms" mmsproxy="172.20.200.12" mmsport="9201" type="mms" user="wapha" password="wap01ha" authtype="1"></apn>
+ <apn mcc="372" mnc="02" carrier="Haiti:Digicel:Modem" apn="wap.digicelha.com" mmsc="http://wapdigicel.com" type="dun" user="wapha" password="wap01ha" authtype="1" proxy="172.20.200.12" port="8080"></apn>
+ <apn mcc="372" mnc="2" carrier="Haiti:Digicel:Mms" apn="wap.digicelha.com" mmsc="http://mmc.digicelhaiti.com/servlets/mms" mmsproxy="172.20.200.12" mmsport="9201" type="mms" user="wapha" password="wap01ha" authtype="1"></apn>
+ <apn mcc="372" mnc="2" carrier="Haiti:Digicel:Modem" apn="wap.digicelha.com" mmsc="http://wapdigicel.com" type="dun" user="wapha" password="wap01ha" authtype="1" proxy="172.20.200.12" port="8080"></apn>
+ <apn mcc="374" mnc="12" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="12" carrier="Trinidad And Tobago:Bmobile:Internet" apn="internet" mmsc="http://www.time4lime.com/" type="default" authtype="1"></apn>
+ <apn mcc="374" mnc="12" carrier="Trinidad And Tobago:Bmobile:MMS" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.105" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="13" carrier="MMS Trinidad" apn="wap.digiceltt.com" mmsc="http://mmc.digiceltt.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="8080" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="374" mnc="120" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="121" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="122" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="123" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="124" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="125" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="126" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="127" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="128" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="129" carrier="Bmobile mms" apn="mms" mmsc="http://192.168.210.104/mmrelay.app" mmsproxy="192.168.210.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="374" mnc="130" carrier="MMS Trinidad" apn="wap.digiceltt.com" mmsc="http://mmc.digiceltt.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="8080" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="374" mnc="130" carrier="Trinidad And Tobago:Digicel:Mms" apn="wap.digiceltt.com" mmsc="http://mmc.digiceltt.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="9201" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="376" mnc="35" carrier="Lime Postpaid MMS" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms"></apn>
+ <apn mcc="376" mnc="350" carrier="Turks And Caicos:Lime:Mms" apn="multimedia" mmsc="http://mmsc" mmsproxy="10.20.5.34" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="376" mnc="350" carrier="Turks And Caicos:Lime:Modem" apn="internet" mmsc="http://www.time4lime.com/" type="dun" authtype="1"></apn>
+ <apn mcc="401" mnc="01" carrier="Beeline MMS" apn="mms.beeline.kz" mmsc="http://mms.beeline.kz/mms/wapenc" mmsproxy="172.27.6.93" mmsport="8080" type="mms" user="\@mms.beeline" password="beeline" authtype="1"></apn>
+ <apn mcc="401" mnc="02" carrier="Kcell MMS" apn="mms" mmsc="http://mms.kcell.kz/post" mmsproxy="195.47.255.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="401" mnc="77" carrier="Tele2 MMS" apn="mms" mmsc="http://mms.tele2.kz/mms/wapenc" mmsproxy="10.1.26.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="001" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="01" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="01" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="1" carrier="Vodafone MMS (40401)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="1" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="002" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="02" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="02" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="2" carrier="Airtel MMS (40402)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="003" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="03" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="03" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="3" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="3" carrier="Airtel MMS (40403)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="004" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="04" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="04" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="04" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="4" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="4" carrier="IDEA MMS (40404)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="005" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="05" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="05" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="5" carrier="Vodafone MMS (40405)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="5" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="006" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="06" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="007" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="07" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="07" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="07" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="7" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="7" carrier="IDEA MMS (40407)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="009" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="09" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="09" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="09" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="9" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="9" carrier="Reliance MMS (40409)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="010" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="10" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="10" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="10" carrier="Airtel MMS (40410)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="011" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="11" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="11" carrier="Vodafone MMS (40411)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="11" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="11" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="012" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="12" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="12" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="12" carrier="IDEA MMS (40412)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="12" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="12" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="013" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="13" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="13" carrier="Vodafone MMS (40413)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="13" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="13" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="014" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="14" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.11.12.180" mmsproxy="10.11.12.13" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="14" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.11.12.180" mmsproxy="10.11.12.13" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="14" carrier="IDEA Punjab MMS (40414)" apn="mmsc" mmsc="http://10.11.12.180" mmsproxy="10.11.12.13" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="14" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="14" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="015" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="15" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="15" carrier="Vodafone MMS (40415)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="15" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="15" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="016" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="16" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="16" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="16" carrier="Airtel MMS (40416)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="017" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="17" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="17" carrier="Aircel MMS (40417)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="17" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="17" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="018" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="18" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="18" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="18" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="18" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="18" carrier="Reliance MMS (40418)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="019" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="19" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="19" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="19" carrier="IDEA MMS (40419)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="19" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="19" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="020" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="20" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="20" carrier="Vodafone MMS (40420)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="20" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="20" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="21" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="404" mnc="21" carrier="Loop MMS (40421)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="21" carrier="Loop Mobile MMS" apn="mizone" mmsc="http://mms.loopmobile.in:8080" mmsproxy="10.0.0.10" mmsport="9401" type="mms" password="mmsc"></apn>
+ <apn mcc="404" mnc="21" carrier="Loop Mobile MMS" apn="mizone" mmsc="http://mms.loopmobile.in:8080" mmsproxy="10.0.0.10" mmsport="9401" type="mms" password="mmsc" authtype="0"></apn>
+ <apn mcc="404" mnc="022" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="22" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="22" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="22" carrier="IDEA MMS (40422)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="22" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="22" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="024" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="24" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="24" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="24" carrier="IDEA MMS (40424)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="24" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="24" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="025" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="25" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="25" carrier="Aircel MMS (40425)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="25" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="25" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="027" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="27" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="27" carrier="Vodafone MMS (40427)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="27" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="27" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="028" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="28" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="28" carrier="Aircel MMS (40428)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="28" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="28" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="029" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="29" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="29" carrier="Aircel MMS (40429)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="29" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="29" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="030" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="30" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="30" carrier="Vodafone MMS (40430)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="30" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="30" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="031" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="31" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="31" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="31" carrier="Airtel MMS (40431)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="033" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="33" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="33" carrier="Aircel MMS (40433)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="33" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="33" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="034" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="34" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="34" carrier="BSNL MMS (40434)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="34" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="34" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="035" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="35" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="35" carrier="Aircel MMS (40435)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="35" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="35" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="036" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="36" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="36" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="36" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="36" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="36" carrier="Reliance MMS (40436)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="037" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="37" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="37" carrier="Aircel MMS (40437)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="37" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="37" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="038" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="38" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="38" carrier="BSNL MMS (40438)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="38" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="38" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="040" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="40" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="40" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="40" carrier="Airtel MMS (40440)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="041" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel MMS (40441)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel MMS (40441)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel-MMS-Postpaid" apn="aircelmms.po" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel-MMS-Postpaid" apn="aircelmms.po" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel-MMS-Prepaid" apn="aircelmms.pr" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="404" mnc="41" carrier="Aircel-MMS-Prepaid" apn="aircelmms.pr" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="042" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="42" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="42" carrier="Aircel MMS (40442)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="42" carrier="Aircel-MMS-Postpaid" apn="aircelmms.po" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="404" mnc="42" carrier="Aircel-MMS-Postpaid" apn="aircelmms.po" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="42" carrier="Aircel-MMS-Prepaid" apn="aircelmms.pr" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="404" mnc="42" carrier="Aircel-MMS-Prepaid" apn="aircelmms.pr" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="043" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="43" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="43" carrier="Vodafone MMS (40443)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="43" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="43" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="044" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="44" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="44" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="44" carrier="IDEA MMS (40444)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="44" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="045" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="45" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="45" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="45" carrier="Airtel MMS (40445)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="046" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="46" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="46" carrier="Vodafone MMS (40446)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="46" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="46" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="049" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="49" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="49" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="49" carrier="Airtel MMS (40449)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="050" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="50" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="50" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="50" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="50" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="50" carrier="Reliance MMS (40450)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="051" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="51" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="51" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="052" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="52" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="52" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="52" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="52" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="52" carrier="Reliance MMS (40452)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="053" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="53" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="53" carrier="BSNL MMS (40453)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="53" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="53" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="054" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="54" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="54" carrier="BSNL MMS (40454)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="54" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="54" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="055" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="55" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="55" carrier="BSNL MMS (40455)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="55" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="55" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="056" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="56" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="56" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="56" carrier="IDEA MMS (40456)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="56" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="56" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="057" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="57" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="57" carrier="BSNL MMS (40457)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="57" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="57" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="058" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="58" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="58" carrier="BSNL MMS (40458)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="58" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="58" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="059" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="59" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="59" carrier="BSNL MMS (40459)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="59" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="59" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="060" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="60" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="60" carrier="Vodafone MMS (40460)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="60" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="60" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="062" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="62" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="62" carrier="BSNL MMS (40462)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="62" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="62" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="064" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="64" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="64" carrier="BSNL MMS (40464)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="64" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="64" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="066" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="66" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="66" carrier="BSNL MMS (40466)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="66" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="66" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="067" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="67" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="67" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="67" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="67" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="67" carrier="Reliance MMS (40467)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="068" carrier="MTNL MMS" apn="mtnl.net" mmsc="http://mtnlmms/" mmsproxy="10.10.10.10" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="68" carrier="Dolphin_Delhi MMS" apn="gprsmtnldel" mmsc="http://172.16.31.136/mms/" mmsproxy="172.16.31.10" mmsport="80" type="mms" user="mtnl" password="mtnl123"></apn>
+ <apn mcc="404" mnc="68" carrier="Dolphin_Delhi MMS" apn="gprsmtnldel" mmsc="http://172.16.31.136/mms/" mmsproxy="172.16.31.10" mmsport="80" type="mms" user="mtnl" password="mtnl123" authtype="3"></apn>
+ <apn mcc="404" mnc="68" carrier="Dolphin_Delhi_3G MMS" apn="mtnl3g" mmsc="http://172.16.31.165/mms/" mmsproxy="172.16.31.10" mmsport="9401" type="mms" user="mtnl" password="mtnl123"></apn>
+ <apn mcc="404" mnc="68" carrier="Dolphin_Delhi_3G MMS" apn="mtnl3g" mmsc="http://172.16.31.165/mms/" mmsproxy="172.16.31.10" mmsport="9401" type="mms" user="mtnl" password="mtnl123" authtype="3"></apn>
+ <apn mcc="404" mnc="68" carrier="MTNL" apn="mtnl.net" mmsc="http://mtnlmms/" mmsproxy="10.10.10.10" mmsport="9401" type="default,mms,supl,agps,fota,dun" user="mtnl" password="mtnl123" authtype="0"></apn>
+ <apn mcc="404" mnc="68" carrier="MTNL Delhi MMS (40468)" apn="gprsmtnldel" mmsc="http://172.16.31.136/mms/" mmsproxy="172.16.31.10" mmsport="8080" type="mms" user="mtnl" password="mtnl123" authtype="1" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="68" carrier="MTNL MMS" apn="mtnl.net" mmsc="http://mtnlmms/" mmsproxy="10.10.10.10" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="68" carrier="MTNL MMS (40468)" apn="mtnl3g" mmsc="http://172.16.31.165/mms/" mmsproxy="172.16.31.10" mmsport="9401" type="mms" user="mtnl" password="mtnl123" authtype="1" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="069" carrier="MTNL MMS" apn="mtnl.net" mmsc="http://mtnlmms/" mmsproxy="10.10.10.10" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="69" carrier="Dolphin_Mumbai MMS" apn="gprsmtnlmum" mmsc="http://172.16.39.140/mms/" mmsproxy="172.16.39.10" mmsport="80" type="mms" user="mtnl" password="mtnl123"></apn>
+ <apn mcc="404" mnc="69" carrier="Dolphin_Mumbai MMS" apn="gprsmtnlmum" mmsc="http://172.16.39.140/mms/" mmsproxy="172.16.39.10" mmsport="80" type="mms" user="mtnl" password="mtnl123" authtype="3"></apn>
+ <apn mcc="404" mnc="69" carrier="Dolphin_Mumbai_3G MMS" apn="mtnl3g" mmsc="http://172.16.31.165/mms/" mmsproxy="172.16.39.10" mmsport="9401" type="mms" user="mtnl" password="mtnl123"></apn>
+ <apn mcc="404" mnc="69" carrier="Dolphin_Mumbai_3G MMS" apn="mtnl3g" mmsc="http://172.16.31.165/mms/" mmsproxy="172.16.39.10" mmsport="9401" type="mms" user="mtnl" password="mtnl123" authtype="3"></apn>
+ <apn mcc="404" mnc="69" carrier="MTNL" apn="mtnl.net" mmsc="http://mtnlmms/" mmsproxy="10.10.10.10" mmsport="9401" type="default,mms,supl,agps,fota,dun" user="mtnl" password="mtnl123" authtype="0" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="404" mnc="69" carrier="MTNL MMS" apn="mtnl.net" mmsc="http://mtnlmms/" mmsproxy="10.10.10.10" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="69" carrier="MTNL MMS (40469)" apn="mtnl3g" mmsc="http://172.16.31.165/mms/" mmsproxy="172.16.39.10" mmsport="9401" type="mms" user="mtnl" password="mtnl123" authtype="1" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="69" carrier="MTNL Mumbai MMS" apn="gprsmtnlmum" mmsc="http://172.16.39.140/mms/" mmsproxy="172.16.39.10" mmsport="8080" type="mms" user="mtnl" password="mtnl123" authtype="1" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="070" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="70" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="70" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="70" carrier="Airtel MMS (40470)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="071" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="71" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="71" carrier="BSNL MMS (40471)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="71" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="71" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="072" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="72" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="72" carrier="BSNL MMS (40472)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="72" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="72" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="073" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="73" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="73" carrier="BSNL MMS (40473)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="73" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="73" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="074" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="74" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="74" carrier="BSNL MMS (40474)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="74" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="74" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="075" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="75" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="75" carrier="BSNL MMS (40475)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="75" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="75" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="076" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="76" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="76" carrier="BSNL MMS (40476)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="76" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="76" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="077" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="77" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="77" carrier="BSNL MMS (40477)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="77" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="77" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="078" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="78" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="78" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="78" carrier="IDEA MMS (40478)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="78" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="78" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="079" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="79" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="79" carrier="BSNL MMS (40479)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="79" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="79" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="080" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="80" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="80" carrier="BSNL MMS (40480)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="80" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="80" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="081" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="81" carrier="BSNL MMS" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="81" carrier="BSNL MMS (40481)" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="81" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="81" carrier="bsnlmms" apn="bsnlmms" mmsc="http://bsnlmmsc.in:8514" mmsproxy="10.210.10.11" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="082" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="82" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="82" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="82" carrier="IDEA MMS (40482)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="82" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="82" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="083" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="83" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="83" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="83" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="83" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="83" carrier="Reliance MMS (40483)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="084" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="84" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="84" carrier="Vodafone MMS (40484)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="84" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="84" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="085" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="85" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="85" carrier="Reliance MMS" apn="mms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="85" carrier="Reliance MMS" apn="mms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.7" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="85" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="85" carrier="Reliance MMS (40485)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="086" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="86" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="86" carrier="Vodafone MMS (40486)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="86" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="86" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="087" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="87" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="87" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="87" carrier="IDEA MMS (40487)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="87" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="87" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="088" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="88" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="88" carrier="Vodafone MMS (40488)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="88" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="404" mnc="88" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="089" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="89" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="89" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="89" carrier="IDEA MMS (40489)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="89" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="89" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="404" mnc="090" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="90" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="90" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="90" carrier="Airtel MMS (40490)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="091" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="91" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="91" carrier="Aircel MMS (40491)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="91" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="404" mnc="91" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="404" mnc="092" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="92" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="92" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="92" carrier="Airtel MMS (40492)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="093" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="93" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="93" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="93" carrier="Airtel MMS (40493)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="094" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="94" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="94" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="94" carrier="Airtel MMS (40494)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="095" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="95" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="95" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="95" carrier="Airtel MMS (40495)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="096" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="96" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="96" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="96" carrier="Airtel MMS (40496)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="097" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="97" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="97" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="97" carrier="Airtel MMS (40497)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="404" mnc="098" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="98" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="404" mnc="98" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="404" mnc="98" carrier="Airtel MMS (40498)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="001" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="01" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="1" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="1" carrier="Reliance MMS (40501)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="03" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="3" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="3" carrier="Reliance MMS (40503)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="04" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="4" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="4" carrier="Reliance MMS (40504)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="005" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="05" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="5" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="5" carrier="Reliance MMS (40505)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="006" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="06" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="6" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="6" carrier="Reliance MMS (40506)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="007" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="07" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="7" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="7" carrier="Reliance MMS (40507)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="08" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="8" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="8" carrier="Reliance MMS (40508)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="009" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="09" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="9" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="9" carrier="Reliance MMS (40509)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="010" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="10" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="10" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="10" carrier="Reliance MMS (40510)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="011" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="11" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="11" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="11" carrier="Reliance MMS (40511)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="12" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="12" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="12" carrier="Reliance MMS (40512)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="013" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="13" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="13" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="13" carrier="Reliance MMS (40513)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="14" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="14" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="14" carrier="Reliance MMS (40514)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="015" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="15" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="15" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="15" carrier="Reliance MMS (40515)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="17" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="17" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="17" carrier="Reliance MMS (40517)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="018" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="18" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="18" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="18" carrier="Reliance MMS (40518)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="019" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="19" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="19" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="19" carrier="Reliance MMS (40519)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="020" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="20" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="20" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="20" carrier="Reliance MMS (40520)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="021" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="21" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="21" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="21" carrier="Reliance MMS (40521)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="022" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="22" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="22" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="22" carrier="Reliance MMS (40522)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="23" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="23" carrier="Reliance MMS" apn="rcommms" mmsc="http://mmsc.rcom.co.in/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="23" carrier="Reliance MMS (40523)" apn="rcommms" mmsc="http://10.239.221.47/mms/" mmsproxy="10.239.221.5" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="025" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="25" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="25" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="25" carrier="TATA DOCOMO MMS (40525)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="026" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="26" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="26" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="26" carrier="TATA DOCOMO MMS (40526)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="027" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="27" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="27" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="27" carrier="TATA DOCOMO MMS (40527)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="028" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="28" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="28" carrier="TATA DOCOMO MMS (40528)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="029" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="29" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="29" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="29" carrier="TATA DOCOMO MMS (40529)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="030" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="30" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="30" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="30" carrier="TATA DOCOMO MMS (40530)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="031" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="31" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="31" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="31" carrier="TATA DOCOMO MMS (40531)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="032" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="32" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="32" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="32" carrier="TATA DOCOMO MMS (40532)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="033" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="33" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="33" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="33" carrier="TATA DOCOMO MMS (40533)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="405" mnc="034" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="34" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="34" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="34" carrier="TATA DOCOMO MMS (40534)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="035" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="35" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="35" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="35" carrier="TATA DOCOMO MMS (40535)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="036" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="36" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="36" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="36" carrier="TATA DOCOMO MMS (40536)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="037" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="37" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="37" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="37" carrier="TATA DOCOMO MMS (40537)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="038" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="38" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="38" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="38" carrier="TATA DOCOMO MMS (405238" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="039" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="39" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="39" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="39" carrier="TATA DOCOMO MMS (40539)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="040" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="40" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="40" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="40" carrier="TATA DOCOMO MMS (40540)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="405" mnc="041" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="41" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="41" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="41" carrier="TATA DOCOMO MMS (40541)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="042" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="42" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="42" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="42" carrier="TATA DOCOMO MMS (40542)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="043" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="43" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="43" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="43" carrier="TATA DOCOMO MMS (40543)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="044" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="44" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="44" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="44" carrier="TATA DOCOMO MMS (40544)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="045" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="45" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="45" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="45" carrier="TATA DOCOMO MMS (40545)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="046" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="46" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="46" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="46" carrier="TATA DOCOMO MMS (40546)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="047" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="47" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="47" carrier="TATA DOCOMO MMS" apn="TATA.DOCOMO.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="47" carrier="TATA DOCOMO MMS (40547)" apn="Tata.Docomo.MMS" mmsc="http://mmsc/" mmsproxy="10.124.26.94" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="051" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="51" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="405" mnc="51" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="51" carrier="Airtel MMS (40551)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="052" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="52" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="405" mnc="52" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="52" carrier="Airtel MMS (40552)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="053" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="53" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="405" mnc="53" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="53" carrier="Airtel MMS (40553)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="054" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="54" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="405" mnc="54" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="54" carrier="Airtel MMS (40554)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="055" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="55" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="405" mnc="55" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="55" carrier="Airtel MMS (40555)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="056" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="56" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="405" mnc="56" carrier="Airtel MMS" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc/" mmsproxy="100.1.201.172" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="56" carrier="Airtel MMS (40556)" apn="airtelmms.com" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="066" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="66" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="66" carrier="Vodafone MMS (40566)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="66" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="66" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="067" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="67" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="67" carrier="Vodafone MMS (40567)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="67" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="67" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="070" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="70" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="70" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="70" carrier="IDEA MMS (40570)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="70" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="70" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="750" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="750" carrier="Vodafone MMS (405750)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="750" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="750" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="751" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="751" carrier="Vodafone MMS (405751)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="751" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="751" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="752" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="752" carrier="Vodafone MMS (405752)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="752" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="752" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="753" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="753" carrier="Vodafone MMS (405753)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="753" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="753" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="754" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="754" carrier="Vodafone MMS (405754)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="754" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="754" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="755" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="755" carrier="Vodafone MMS (405755)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="755" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="755" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="756" carrier="Vodafone MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="756" carrier="Vodafone MMS (405756)" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="756" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms"></apn>
+ <apn mcc="405" mnc="756" carrier="Vodafone_MMS" apn="portalnmms" mmsc="http://mms1.live.vodafone.in/mms/" mmsproxy="10.10.1.100" mmsport="9401" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="799" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="799" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="799" carrier="IDEA MMS (405799)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="799" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="799" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="800" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="800" carrier="Aircel MMS (405800)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="800" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="800" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="801" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="801" carrier="Aircel MMS (405801)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="801" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="405" mnc="801" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="802" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="802" carrier="Aircel MMS (405802)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="802" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="802" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="803" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="803" carrier="Aircel MMS (405803)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="803" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="405" mnc="803" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="804" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="804" carrier="Aircel MMS (405804)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="804" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="804" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="805" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="805" carrier="Aircel MMS (405805)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="805" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="805" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="806" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="806" carrier="Aircel MMS (405806)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="806" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="806" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="807" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="807" carrier="Aircel MMS (405807)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="807" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="807" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="808" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="808" carrier="Aircel MMS (405808)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="808" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="808" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="809" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="809" carrier="Aircel MMS (405809)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="809" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms"></apn>
+ <apn mcc="405" mnc="809" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://mmsc/mmrelay.app" mmsproxy="192.168.35.196" mmsport="8081" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="810" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="810" carrier="Aircel MMS (405810)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="810" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="810" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="811" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="811" carrier="Aircel MMS (405811)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="811" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="811" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="812" carrier="Aircel MMS" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.017.083.069" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="812" carrier="Aircel MMS (405812)" apn="aircelmms" mmsc="http://172.17.83.67/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="812" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="812" carrier="Aircel-MMS" apn="aircelmms" mmsc="http://10.50.1.166/servlets/mms" mmsproxy="172.17.83.69" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="813" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="813" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="814" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="814" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="815" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="815" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="816" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="816" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="817" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="817" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="818" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="818" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="819" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="819" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="820" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="820" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="821" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="821" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="822" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="822" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="823" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="823" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="823" carrier="Videocon MMS (405823)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="824" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="824" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="824" carrier="Videocon MMS (405824)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="825" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="825" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="825" carrier="Videocon MMS (405825)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="826" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="826" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="826" carrier="Videocon MMS (405826)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="827" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="827" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="827" carrier="Videocon MMS (405827)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="828" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="828" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="828" carrier="Videocon MMS (405828)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="829" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="829" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="829" carrier="Videocon MMS (405829)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="830" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="830" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="830" carrier="Videocon MMS (405830)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="831" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="831" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="831" carrier="Videocon MMS (405831)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="832" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="832" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="832" carrier="Videocon MMS (405832)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="833" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="833" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="833" carrier="Videocon MMS (405833)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="834" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="834" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="834" carrier="Videocon MMS (405834)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="835" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="835" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="835" carrier="Videocon MMS (405835)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="836" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="836" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="836" carrier="Videocon MMS (405836)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="837" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="837" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="837" carrier="Videocon MMS (405837)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="838" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="838" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="838" carrier="Videocon MMS (405838)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="839" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="839" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="839" carrier="Videocon MMS (405839)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="840" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="840" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="840" carrier="Videocon MMS (405840)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="841" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="841" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="841" carrier="Videocon MMS (405841)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="842" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="842" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="842" carrier="Videocon MMS (405842)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="843" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="843" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="843" carrier="Videocon MMS (405843)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="844" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="844" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="845" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="845" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="845" carrier="IDEA MMS (405845)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="845" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="845" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="846" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="846" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="846" carrier="IDEA MMS (405846)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="846" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="846" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="847" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="848" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="848" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="848" carrier="IDEA MMS (405848)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="848" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="848" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="849" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="849" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="849" carrier="IDEA MMS (405849)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="849" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="849" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="850" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="850" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="850" carrier="IDEA MMS (405850)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="850" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="850" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="851" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="852" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="852" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="852" carrier="IDEA MMS (405852)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="852" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="852" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="853" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="853" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="853" carrier="IDEA MMS (405853)" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="853" carrier="Idea MMS" apn="mmsc" mmsc="http://10.4.42.21:8002" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="853" carrier="Idea-MMS_Karnatak" apn="spicemms" mmsc="http://10.200.200.3:8514" proxy="10.200.200.3" port="8080"></apn>
+ <apn mcc="405" mnc="854" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="854" carrier="Loop MMS (405854)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="854" carrier="Loop MMS (405854)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="855" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="855" carrier="Loop MMS (405855)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="855" carrier="Loop MMS (405855)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="856" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="856" carrier="Loop MMS (405856)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="856" carrier="Loop MMS (405856)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="857" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="857" carrier="Loop MMS (405857)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="857" carrier="Loop MMS (405857)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="858" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="858" carrier="Loop MMS (405858)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="858" carrier="Loop MMS (405858)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="859" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="859" carrier="Loop MMS (405859)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="859" carrier="Loop MMS (405859)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="860" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="860" carrier="Loop MMS (405860)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="860" carrier="Loop MMS (405860)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="861" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="861" carrier="Loop MMS (405861)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="861" carrier="Loop MMS (405861)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="862" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="862" carrier="Loop MMS (405862)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="862" carrier="Loop MMS (405862)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="863" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="863" carrier="Loop MMS (405863)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="863" carrier="Loop MMS (405863)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="864" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="864" carrier="Loop MMS (405864)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="864" carrier="Loop MMS (405864)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="865" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="865" carrier="Loop MMS (405865)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="865" carrier="Loop MMS (405865)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="866" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="866" carrier="Loop MMS (405866)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="866" carrier="Loop MMS (405866)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="867" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="867" carrier="Loop MMS (405867)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="867" carrier="Loop MMS (405867)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="868" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="868" carrier="Loop MMS (405868)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="868" carrier="Loop MMS (405868)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="869" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="869" carrier="Loop MMS (405869)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="869" carrier="Loop MMS (405869)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="870" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="870" carrier="Loop MMS (405870)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="870" carrier="Loop MMS (405870)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="871" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="871" carrier="Loop MMS (405871)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="871" carrier="Loop MMS (405871)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="872" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="872" carrier="Loop MMS (405872)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="872" carrier="Loop MMS (405872)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="873" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="873" carrier="Loop MMS (405873)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="873" carrier="Loop MMS (405873)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="874" carrier="BPL MMS" apn="mizone" mmsc="http://mms.bplmobile.com:8080 " mmsproxy="010.000.000.010 " mmsport="9401"></apn>
+ <apn mcc="405" mnc="874" carrier="Loop MMS (405874)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="405" mnc="874" carrier="Loop MMS (405874)" apn="mizone" mmsc="http://mms.loopmobile.in:8080/" mmsproxy="10.0.0.10" mmsport="9401" type="mms" protocol="IP" roaming_protocol="IP" carrier_enabled="true"></apn>
+ <apn mcc="405" mnc="875" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="875" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="876" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="876" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="877" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="877" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="878" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="878" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="879" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="879" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="880" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="880" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="908" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="909" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="910" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="911" carrier="IDEA MMS" apn="mmsc" mmsc="http://10.4.42.21:8002/" mmsproxy="10.4.42.15" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="925" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="925" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="926" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="926" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="927" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="927" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="928" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="928" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="929" carrier="UNINOR MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.2.101" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="929" carrier="Uninor MMS" apn="uninor" mmsc="http://10.58.2.120" mmsproxy="10.58.10.59" mmsport="8080" type="mms"></apn>
+ <apn mcc="405" mnc="932" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms"></apn>
+ <apn mcc="405" mnc="932" carrier="Videocon MMS" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="405" mnc="932" carrier="Videocon MMS (405932)" apn="vgprs.com" mmsc="http://10.202.4.119:10021/mmsc/" mmsproxy="10.202.5.145" mmsport="8799" type="mms" authtype="0" protocol="IP" roaming_protocol="IP" carrier_enabled="TRUE"></apn>
+ <apn mcc="410" mnc="01" carrier="Mobilink MMS" apn="mms.mobilinkworld.com" mmsc="http://mms/" mmsproxy="172.25.20.12" mmsport="8080" type="mms" user="Mobilink" password="Mobilink"></apn>
+ <apn mcc="410" mnc="1" carrier="Mobilink MMS" apn="mms.mobilinkworld.com" mmsc="http://mms/" mmsproxy="172.25.20.12" mmsport="8080" type="mms" user="Mobilink" password="Mobilink" authtype="3"></apn>
+ <apn mcc="410" mnc="1" carrier="Mobilink MMS" apn="mms.mobilinkworld.com" mmsc="http://mms/" mmsproxy="172.25.20.12" mmsport="8080" type="mms" user="mobilink" password="mobilink" authtype="0"></apn>
+ <apn mcc="410" mnc="03" carrier="Ufone MMS" apn="Ufone.mms" mmsc="http://www.ufonemms.com:80/" mmsproxy="172.16.13.27" mmsport="8080" type="mms"></apn>
+ <apn mcc="410" mnc="3" carrier="Ufone MMS" apn="Ufone.mms" mmsc="http://www.ufonemms.com:80/" mmsproxy="172.16.13.27" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="410" mnc="3" carrier="Ufone MMS" apn="ufone.mms" mmsc="http://www.ufonemms.com:80/" mmsproxy="172.16.13.27" mmsport="9201" type="mms" user="ufone" password="ufone" authtype="0"></apn>
+ <apn mcc="410" mnc="04" carrier="ZONG MMS" apn="zongmms" mmsc="http://10.81.6.11:8080" mmsproxy="10.81.6.33" mmsport="8000" type="mms"></apn>
+ <apn mcc="410" mnc="4" carrier="Warid MMS" apn="mms.warid" mmsc="http://10.4.0.132/servlets/mms" mmsproxy="10.4.2.1" mmsport="9201" type="mms" authtype="0"></apn>
+ <apn mcc="410" mnc="4" carrier="ZONG MMS" apn="zongmms" mmsc="http://10.81.6.11:8080" mmsproxy="10.81.6.33" mmsport="8000" type="mms" authtype="0"></apn>
+ <apn mcc="410" mnc="06" carrier="Telenor MMS" apn="mms" mmsc="http://mmstelenor" mmsproxy="172.18.19.11" mmsport="8080" type="mms" user="Telenor" password="Telenor"></apn>
+ <apn mcc="410" mnc="6" carrier="Telenor MMS" apn="mms" mmsc="http://mmstelenor" mmsproxy="172.18.19.11" mmsport="8080" type="mms" user="Telenor" password="Telenor" authtype="3"></apn>
+ <apn mcc="410" mnc="6" carrier="Telenor MMS (410.06)" apn="mms" mmsc="http://mmstelenor" mmsproxy="172.18.19.11" mmsport="8080" type="mms" user="Telenor" password="Telenor" authtype="0"></apn>
+ <apn mcc="410" mnc="07" carrier="Warid MMS" apn="mms.warid" mmsc="http://10.4.0.132/servlets/MMS" mmsproxy="10.4.2.1" mmsport="8080" type="mms"></apn>
+ <apn mcc="410" mnc="7" carrier="Warid MMS" apn="mms.warid" mmsc="http://10.4.0.132/servlets/MMS" mmsproxy="10.4.2.1" mmsport="8080" type="mms"></apn>
+ <apn mcc="410" mnc="310" carrier="Consumer Cellular" apn="att.mvno" mmsc="http://mms.fido.ca" mmsproxy="mmsproxy.fido.ca" mmsport="80" type="default,mms,supl,hipri,fota" mvno_match_data="2AC9" mvno_type="gid" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="413" mnc="01" carrier="Mobitel MMS" apn="wapmms" mmsc="http://192.168.050.165" mmsproxy="192.168.050.163" mmsport="8080" type="mms"></apn>
+ <apn mcc="413" mnc="03" carrier="Etisalat MMS" apn="mms" mmsc="http://mms.etisalat.lk:8085" mmsproxy="192.168.104.004" mmsport="9401" type="mms"></apn>
+ <apn mcc="413" mnc="05" carrier="Airtel MMS SL" apn="AirtelLive" mmsc="http://mms.Airtel.lk" mmsproxy="10.200.184.086" mmsport="8080" type="mms"></apn>
+ <apn mcc="415" mnc="01" carrier="Alfa MMS" apn="mms.mic1.com.lb" mmsc="http://mms.mic1.com.lb" mmsproxy="192.168.23.51" mmsport="8080" type="mms" user="mic1" password="mic1"></apn>
+ <apn mcc="415" mnc="1" carrier="Alfa MMS" apn="mms.mic1.com.lb" mmsc="http://mms.mic1.com.lb" mmsproxy="192.168.23.51" mmsport="8080" type="mms" user="mic1" password="mic1" authtype="0"></apn>
+ <apn mcc="415" mnc="1" carrier="Alfa MMS" apn="mms.mic1.com.lb" mmsc="http://mms.mic1.com.lb" mmsproxy="192.168.23.51" mmsport="8080" type="mms" user="mic1" password="mic1" authtype="3"></apn>
+ <apn mcc="415" mnc="03" carrier="touch_MMS" apn="mms" mmsc="http://mms:8088/mms/" mmsproxy="192.168.4.103" mmsport="80" type="mms" user="touch"></apn>
+ <apn mcc="415" mnc="3" carrier="MTC touch MMS" apn="mms.mtctouch.com.lb" mmsc="http://mms:8088/mms/" mmsproxy="192.168.4.103" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="415" mnc="3" carrier="touch_MMS" apn="mms" mmsc="http://mms:8088/mms/" mmsproxy="192.168.4.103" mmsport="80" type="mms" user="touch" authtype="3"></apn>
+ <apn mcc="416" mnc="01" carrier="Zain JO MMS" apn="zain" mmsc="http://mms.jo.zain.com" mmsproxy="192.168.55.10" mmsport="80" type="mms" user="zain" password="zain" authtype="1"></apn>
+ <apn mcc="416" mnc="1" carrier="zain JO mms" apn="zain" mmsc="http://mms.jo.zain.com" mmsproxy="192.168.55.10" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="416" mnc="1" carrier="zain JO mms" apn="zain" mmsc="http://mms.jo.zain.com" mmsproxy="192.168.55.10" mmsport="80" type="mms" user="zain" password="zain" authtype="1"></apn>
+ <apn mcc="416" mnc="03" carrier="Umniah MMS" apn="mms" mmsc="http://mms.umniah.com/" mmsproxy="10.1.1.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="416" mnc="3" carrier="UMNIAH mms" apn="mms" mmsc="http://mms.umniah.com/" mmsproxy="10.1.1.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="416" mnc="3" carrier="UMNIAH mms" apn="mms" mmsc="http://mms.umniah.com/" mmsproxy="10.1.1.10" mmsport="8080" type="mms" authtype="0" server="http://mms.umniah.com/"></apn>
+ <apn mcc="416" mnc="77" carrier="Orange JO MMS" apn="mms.orange.jo" mmsc="http://172.16.1.96/servlets/mms" mmsproxy="172.16.1.2" mmsport="8080" type="mms" user="mmc" password="mmc" authtype="1" server="http://172.16.1.96/servlets/mms" proxy="172.16.1.2" port="8080"></apn>
+ <apn mcc="416" mnc="77" carrier="Orange MMS" apn="mms.orange.jo" mmsc="http://172.16.1.96/servlets/mms" mmsproxy="172.16.1.2" mmsport="8080" type="mms" user="mmc" password="mmc" authtype="1"></apn>
+ <apn mcc="416" mnc="770" carrier="Orange JO MMS" apn="mms.orange.jo" mmsc="http://172.16.1.96/servlets/mms" mmsproxy="172.16.1.2" mmsport="8080" type="mms" user="mmc" password="mmc" authtype="1" server="http://172.16.1.96/servlets/mms" proxy="172.16.1.2" port="8080"></apn>
+ <apn mcc="416" mnc="770" carrier="Orange MMS" apn="mms.orange.jo" mmsc="http://172.16.1.96/servlets/mms" mmsproxy="172.16.1.2" mmsport="8080" type="mms" user="mmc" password="mmc" authtype="1"></apn>
+ <apn mcc="417" mnc="01" carrier="Syriatel MMS" apn="mms.syriatel.com" mmsc="http://mymms.syriatel.com/" mmsproxy="172.20.5.6" mmsport="80" type="mms"></apn>
+ <apn mcc="417" mnc="02" carrier="MTN MMS" apn="mms" mmsc="http://mms/" mmsproxy="10.110.0.134" mmsport="8799" type="mms"></apn>
+ <apn mcc="418" mnc="05" carrier="Asiacell MMS" apn="wap.asiacell.com" mmsc="http://192.168.107.10:19090/was" mmsproxy="192.168.107.50" mmsport="8080" type="mms"></apn>
+ <apn mcc="418" mnc="5" carrier="Asiacell MMS" apn="wap.asiacell.com" mmsc="http://192.168.107.10:19090/was" mmsproxy="192.168.107.50" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="418" mnc="20" carrier="Zain-MMS" apn="MMS" mmsc="http://mms:8002/" mmsproxy="172.29.11.12" mmsport="8080" type="mms" user="atheer" password="atheer" authtype="1"></apn>
+ <apn mcc="418" mnc="30" carrier="Zain-MMS" apn="MMS" mmsc="http://mms:8002/" mmsproxy="172.29.11.12" mmsport="8080" type="mms" user="atheer" password="atheer" authtype="1"></apn>
+ <apn mcc="418" mnc="40" carrier="KOREK MMS" apn="mms.korek.com" mmsc="http://mms.korektel.com/mms/wapenc" mmsproxy="192.168.18.187" mmsport="8080" type="mms" user="korek" password="korek" authtype="1"></apn>
+ <apn mcc="419" mnc="02" carrier="MMS" apn="pps" mmsc="http://mms.zain" mmsproxy="176.0.0.65" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="419" mnc="2" carrier="MMS" apn="pps" mmsc="http://mms.zain" mmsproxy="176.0.0.65" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="419" mnc="2" carrier="Zain KW MMS" apn="pps" mmsc="http://176.0.0.21" mmsproxy="176.0.0.65" mmsport="8080" type="mms" user="annyway" password="online" authtype="0"></apn>
+ <apn mcc="419" mnc="03" carrier="Wataniya MMS" apn="mms.wataniya.com" mmsc="http://action.wataniya.com" mmsproxy="194.126.53.64" mmsport="8080" type="mms"></apn>
+ <apn mcc="419" mnc="03" carrier="Wataniya MMS" apn="mms.wataniya.com" mmsc="http://action.wataniya.com" mmsproxy="194.126.53.64" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="419" mnc="3" carrier="KT WATANIYA mms" apn="mms.wataniya.com" mmsc="http://action.wataniya.com" mmsproxy="194.126.53.64" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="419" mnc="3" carrier="Wataniya MMS" apn="mms.wataniya.com" mmsc="http://action.wataniya.com" mmsproxy="194.126.53.64" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="419" mnc="04" carrier="VIVA - KW" apn="viva" mmsc="http://172.16.128.80:38090/was" mmsproxy="172.16.128.228" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="419" mnc="04" carrier="VIVA MMS" apn="VIVA" mmsc="http://172.16.128.80:38090/was" mmsproxy="172.16.128.228" mmsport="8080" type="mms"></apn>
+ <apn mcc="419" mnc="4" carrier="VIVA - KW" apn="viva" mmsc="http://172.16.128.80:38090/was" mmsproxy="172.16.128.228" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="419" mnc="4" carrier="VIVA MMS" apn="VIVA" mmsc="http://172.16.128.80:38090/was" mmsproxy="172.16.128.228" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="01" carrier="STC MMS" apn="mms.net.sa" mmsc="http://mms.net.sa:8002/" mmsproxy="10.1.1.1" mmsport="8080" type="mms"></apn>
+ <apn mcc="420" mnc="1" carrier="STC MMS" apn="mms.net.sa" mmsc="http://mms.net.sa:8002/" mmsproxy="10.1.1.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="1" carrier="STC mms" apn="mms.net.sa" mmsc="http://mms.net.sa:8002" mmsproxy="10.1.1.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="03" carrier="Mobily MMS Postpaid" apn="mms1" mmsc="http://10.3.3.133:9090/was" mmsproxy="10.3.2.133" mmsport="8080" type="mms"></apn>
+ <apn mcc="420" mnc="03" carrier="Mobily MMS Prepaid" apn="mms2" mmsc="http://10.3.3.133:9090/was" mmsproxy="10.3.2.133" mmsport="8080" type="mms"></apn>
+ <apn mcc="420" mnc="3" carrier="Mobily MMS Postpaid" apn="mms1" mmsc="http://10.3.3.133:9090/was" mmsproxy="10.3.2.133" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="3" carrier="Mobily MMS Prepaid" apn="mms2" mmsc="http://10.3.3.133:9090/was" mmsproxy="10.3.2.133" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="3" carrier="mobily-KSA mms" apn="mms1" mmsc="http://10.3.3.133:9090/was" mmsproxy="10.3.2.133" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="3" carrier="mobily-KSA mms pre-paid" apn="mms2" mmsc="http://10.3.3.133:9090/was" mmsproxy="10.3.2.133" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="420" mnc="04" carrier="zain-mms" apn="zain" mmsc="http://10.122.200.12:8002" mmsproxy="10.122.200.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="420" mnc="04" carrier="zain-mms" apn="zainmms" mmsc="http://10.122.200.12:8002" mmsproxy="10.122.200.10" mmsport="8080" type="mms" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="420" mnc="4" carrier="Zain SA" apn="zain" mmsc="http://10.122.200.12:8002/" mmsproxy="10.122.200.10" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="420" mnc="4" carrier="zain-mms" apn="zain" mmsc="http://10.122.200.12:8002" mmsproxy="10.122.200.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="421" mnc="01" carrier="Sabafon MMS" apn="mms" mmsc="http://mms.sabafon.com/" mmsproxy="192.168.30.174" mmsport="8080" type="mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="421" mnc="1" carrier="Sabafon MMS" apn="mms" mmsc="http://mms.sabafon.com/" mmsproxy="192.168.30.174" mmsport="8080" type="mms" user="wap" password="wap" authtype="0"></apn>
+ <apn mcc="421" mnc="02" carrier="MTN MMS YE" apn="fast-mms" mmsc="http://192.168.97.1/mmsc" mmsproxy="192.168.97.1" mmsport="3130" type="mms" user="mms" authtype="0"></apn>
+ <apn mcc="421" mnc="2" carrier="MTN MMS YE" apn="fast-mms" mmsc="http://192.168.97.1/mmsc" mmsproxy="192.168.97.1" mmsport="3130" type="mms" user="mms" authtype="0"></apn>
+ <apn mcc="422" mnc="02" carrier="Oman Mobile MMS" apn="mms" mmsc="http://mmsc.omanmobile.om:10021/mmsc" mmsproxy="192.168.203.35" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="422" mnc="2" carrier="Oman Mobile MMS" apn="mms" mmsc="http://mmsc.omanmobile.om:10021/mmsc" mmsproxy="192.168.203.35" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="422" mnc="03" carrier="Nawras MMS" apn="mms.nawras.com.om" mmsc="http://10.128.240.16/servlets/mms" mmsproxy="10.128.240.19" mmsport="8080" type="mms" user="test" password="test" authtype="1"></apn>
+ <apn mcc="422" mnc="3" carrier="Nawras MMS" apn="mms.nawras.com.om" mmsc="http://10.128.240.16" mmsproxy="10.128.240.16" mmsport="9201" type="mms" user="test" password="test" authtype="0"></apn>
+ <apn mcc="424" mnc="02" carrier="Etisalat MMS" apn="etisalat" mmsc="http://mms/servlets/mms" mmsproxy="10.12.0.32" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="424" mnc="2" carrier="ETISALAT AE mms" apn="mms" mmsc="http://mms/servlets/mms" mmsproxy="10.12.0.30" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="424" mnc="2" carrier="Etisalat MMS" apn="etisalat" mmsc="http://mms/servlets/mms" mmsproxy="10.12.0.32" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="424" mnc="03" carrier="du MMS" apn="du" mmsc="http://mms.du.ae:8002" mmsproxy="10.19.18.4" mmsport="8080" type="mms"></apn>
+ <apn mcc="424" mnc="3" carrier="du" apn="du" mmsc="http://mms.du.ae" mmsproxy="10.19.18.4" mmsport="8080" type="default,mms" authtype="0" port="8080"></apn>
+ <apn mcc="424" mnc="3" carrier="du MMS" apn="du" mmsc="http://mms.du.ae:8002" mmsproxy="10.19.18.4" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="425" mnc="01" carrier="3G Portal" apn="uwap.orange.co.il" mmsc="http://192.168.220.15/servlets/mms" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="425" mnc="1" carrier="3G Portal" apn="uwap.orange.co.il" mmsc="http://192.168.220.15/servlets/mms" type="default,mms" authtype="0"></apn>
+ <apn mcc="425" mnc="1" carrier="3G Portal" apn="uwap.orange.co.il" mmsc="http://192.168.220.15/servlets/mms" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="425" mnc="02" carrier="Cellcom MMS" apn="mms" mmsc="http://mms.cellcom.co.il" mmsproxy="vwapm2.ain.co.il" mmsport="8080" type="mms"></apn>
+ <apn mcc="425" mnc="2" carrier="Cellcom MMS" apn="mms" mmsc="http://mms.cellcom.co.il" mmsproxy="vwapm2.ain.co.il" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="425" mnc="2" carrier="Cellcom_mms" apn="mms" mmsc="http://mms.cellcom.co.il" mmsproxy="172.31.29.38" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="425" mnc="03" carrier="Multimedia Pelephone" apn="mms.pelephone.net.il" mmsc="http://mmsu.pelephone.net.il" mmsproxy="10.170.252.104" mmsport="9093" type="mms" user="pcl@3g" password="pcl" authtype="3"></apn>
+ <apn mcc="425" mnc="3" carrier="Multimedia Pelephone" apn="mms.pelephone.net.il" mmsc="http://mmsu.pelephone.net.il" mmsproxy="10.170.9.54" mmsport="9093" type="mms" user="pcl@3g" password="pcl" authtype="0"></apn>
+ <apn mcc="425" mnc="05" carrier="Jawwal MMS" apn="mms" mmsc="http://mms.jawwal.ps/servlets/mms" mmsproxy="213.244.118.129" mmsport="8080" type="mms"></apn>
+ <apn mcc="425" mnc="5" carrier="Jawwal MMS" apn="mms" mmsc="http://mms.jawwal.ps/servlets/mms" mmsproxy="213.244.118.129" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="425" mnc="06" carrier="Wataniya_mms" apn="mms" mmsc="http://mms.wataniya.ps/servlets/mms" mmsproxy="10.100.129.111" mmsport="8080" type="mms"></apn>
+ <apn mcc="425" mnc="6" carrier="Wataniya_mms" apn="mms" mmsc="http://mms.wataniya.ps/servlets/mms" mmsproxy="10.100.129.111" mmsport="8080" type="mms"></apn>
+ <apn mcc="425" mnc="07" carrier="MMS HOT mobile" apn="mms.hotm" mmsc="http://mms.hotmobile.co.il" mmsproxy="80.246.131.99" mmsport="80" type="mms"></apn>
+ <apn mcc="425" mnc="7" carrier="MMS HOT mobile" apn="mms.hotm" mmsc="http://mms.hotmobile.co.il" mmsproxy="80.246.131.99" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="425" mnc="08" carrier="GolanTelecom MMS" apn="mms.golantelecom.net.il" mmsc="http://mmsc.golantelecom.co.il" mmsproxy="10.224.228.81" mmsport="80" type="mms"></apn>
+ <apn mcc="425" mnc="8" carrier="GolanTelecom MMS" apn="mms.golantelecom.net.il" mmsc="http://mmsc.golantelecom.co.il" mmsproxy="10.224.228.81" mmsport="80" type="mms"></apn>
+ <apn mcc="425" mnc="10" carrier="3G Portal" apn="uwap.orange.co.il" mmsc="http://192.168.220.15/servlets/mms" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="425" mnc="14" carrier="Alon Internet" apn="data.youphone.co.il" mmsc="http://192.168.220.15/servlets/mms" type="default,supl,mms"></apn>
+ <apn mcc="425" mnc="15" carrier="Home Cellular MMS" apn="hcmMMS" mmsc="http://82.166.164.229:9000/mmsc" mmsproxy="82.166.164.229" mmsport="8898" type="mms"></apn>
+ <apn mcc="425" mnc="16" carrier="Rami Levi Multimedia" apn="mms.pelephone.net.il" mmsc="http://mmsu.pelephone.net.il" mmsproxy="10.170.252.104" mmsport="9093" type="mms"></apn>
+ <apn mcc="426" mnc="01" carrier="Batelco MMS" apn="mms.batelco.com" mmsc="http://192.168.36.10/servlets/mms" mmsproxy="192.168.1.2" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="426" mnc="1" carrier="Batelco MMS" apn="mms.batelco.com" mmsc="http://192.168.36.10/servlets/mms" mmsproxy="192.168.1.2" type="mms" authtype="0"></apn>
+ <apn mcc="426" mnc="1" carrier="Batelco MMS" apn="mms.batelco.com" mmsc="http://192.168.36.10/servlets/mms" mmsproxy="192.168.1.2" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="426" mnc="02" carrier="Zain BH MMS" apn="mms" mmsc="http://172.18.83.129:80/" mmsproxy="172.18.85.34" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="426" mnc="2" carrier="Zain BH MMS" apn="mms" mmsc="http://172.18.83.129" mmsproxy="172.18.85.34" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="426" mnc="04" carrier="VIVAMMS" apn="vivawap.bh" mmsc="http://mms.viva.com.bh:38090" mmsproxy="172.18.142.36" mmsport="8080" type="mms"></apn>
+ <apn mcc="426" mnc="4" carrier="VIVA MMS" apn="vivawap.bh" mmsc="http://mms.viva.com.bh:38090" mmsproxy="172.18.142.36" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="426" mnc="4" carrier="VIVAMMS" apn="vivawap.bh" mmsc="http://mms.viva.com.bh:38090" mmsproxy="172.18.142.36" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="427" mnc="01" carrier="Qtel MMS" apn="mms.qtel" mmsc="http://mmsr.qtelmms.qa" mmsproxy="10.23.8.3" mmsport="8080" type="mms" user="10" password="11" authtype="1"></apn>
+ <apn mcc="427" mnc="1" carrier="Qat-Qtel mms" apn="mms.qtel" mmsc="http://mmsr.qtelmms.qa" mmsproxy="10.23.8.3" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="427" mnc="02" carrier="VFQ MMS" apn="vodafone.com.qa" mmsc="http://mms.vodafone.com.qa/mmsc" mmsproxy="10.101.97.102" mmsport="80" type="mms"></apn>
+ <apn mcc="427" mnc="2" carrier="VFQ MMS" apn="vodafone.com.qa" mmsc="http://mms.vodafone.com.qa/mmsc" mmsproxy="10.101.97.102" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="427" mnc="2" carrier="Vodafone Qatar MMS" apn="Vodafone.com.qa" mmsc="http://mms.vodafone.com.qa/mmsc" mmsproxy="10.101.97.102" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="432" mnc="11" carrier="MCCI MMS" apn="mcinet" mmsc="http://192.168.193.134:38090/was" mmsproxy="192.168.194.73" mmsport="8080" type="mms"></apn>
+ <apn mcc="432" mnc="14" carrier="MCCI MMS" apn="MCI-GPRS" mmsc="http://192.168.193.134" mmsport="38090" type="mms"></apn>
+ <apn mcc="432" mnc="19" carrier="MCCI MMS" apn="MCI-GPRS" mmsc="http://192.168.193.134" mmsport="38090" type="mms"></apn>
+ <apn mcc="432" mnc="20" carrier="RighTel-MMS" apn="RighTel-WAP" mmsc="http://10.200.40.55:38090/was" mmsproxy="10.200.39.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="432" mnc="35" carrier="Irancell-MMS" apn="mtnirancell" mmsc="http://mms:8002" mmsproxy="10.131.26.138" mmsport="8080" type="mms"></apn>
+ <apn mcc="432" mnc="70" carrier="MCCI MMS" apn="mcinet" mmsc="http://192.168.193.134:38090/was" mmsproxy="192.168.194.73" mmsport="8080" type="mms"></apn>
+ <apn mcc="432" mnc="93" carrier="MCCI MMS" apn="MCI-GPRS" mmsc="http://192.168.193.134" mmsport="38090" type="mms"></apn>
+ <apn mcc="434" mnc="04" carrier="Beeline-UZB MMS" apn="mms.beeline.uz" mmsc="http://mms" mmsproxy="172.30.30.166" mmsport="8080" type="mms" user="beeline" password="beeline" authtype="1"></apn>
+ <apn mcc="434" mnc="4" carrier="Beeline-UZB MMS" apn="mms.beeline.uz" mmsc="http://mms" mmsproxy="172.30.30.166" mmsport="8080" type="mms" user="beeline" password="beeline" authtype="1"></apn>
+ <apn mcc="434" mnc="05" carrier="UCELL MMS" apn="mms" mmsc="http://mmsc:8002/" mmsproxy="10.64.164.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="434" mnc="5" carrier="UCELL MMS" apn="mms" mmsc="http://mmsc:8002/" mmsproxy="10.64.164.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="434" mnc="07" carrier="MTS-UZB MMS" apn="mms.mts.uz" mmsc="http://mmsc/was" mmsproxy="10.10.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="440" mnc="20" carrier="Application" apn="andoworld.softbank.ne.jp" mmsc="http://mms/" mmsproxy="andmms.softbank.ne.jp" mmsport="8080" type="default,mms,supl,hpri" authtype="0"></apn>
+ <apn mcc="440" mnc="20" carrier="Application" apn="plus.acs.jp" mmsc="http://mms-s" mmsproxy="andmms.plus.acs.ne.jp" mmsport="8080" type="default,mms,supl,hipri" user="ym" password="ym" authtype="2"></apn>
+ <apn mcc="440" mnc="20" carrier="Application" apn="plus.acs.jp" mmsc="http://mms-s" mmsproxy="andmms.plusacs.ne.jp" mmsport="8080" type="default,mms,supl" user="plusw6q9tattkmpk" password="msfbbam83bsdetxb" authtype="2"></apn>
+ <apn mcc="450" mnc="05" carrier="SKT 3G INTERNET" apn="web.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" server="*"></apn>
+ <apn mcc="450" mnc="05" carrier="SKT 3G Roaming" apn="roaming.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" server="*"></apn>
+ <apn mcc="450" mnc="05" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" server="*"></apn>
+ <apn mcc="450" mnc="05" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="ia,default,mms,supl,fota,cbs" server="*"></apn>
+ <apn mcc="450" mnc="05" carrier="SKT LTE Roaming" apn="lte-roaming.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" server="*"></apn>
+ <apn mcc="450" mnc="5" carrier="SKT 3G INTERNET" apn="web.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" authtype="0" server="*"></apn>
+ <apn mcc="450" mnc="5" carrier="SKT LTE INTERNET" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" authtype="0" server="*"></apn>
+ <apn mcc="450" mnc="5" carrier="SKT-Home" apn="lte.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms" authtype="0" server="*"></apn>
+ <apn mcc="450" mnc="5" carrier="SKT-Roaming" apn="roaming.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms" authtype="0" server="*"></apn>
+ <apn mcc="450" mnc="06" carrier="LG uplus" apn="internet.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" type="default,mms,supl,fota,cbs" protocol="IPV4V6"></apn>
+ <apn mcc="450" mnc="06" carrier="LG uplus IMS" apn="ims.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" type="ia,ims" protocol="IPV4V6"></apn>
+ <apn mcc="450" mnc="06" carrier="LG uplus LTE Roaming" apn="lte-roaming.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" authtype="0"></apn>
+ <apn mcc="450" mnc="06" carrier="LG uplus Roaming" apn="wroaming.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" authtype="0"></apn>
+ <apn mcc="450" mnc="6" carrier="LG uplus" apn="internet.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" type="default,mms,supl,fota,cbs" protocol="IPV4V6"></apn>
+ <apn mcc="450" mnc="6" carrier="LG uplus IMS" apn="ims.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" type="ia,ims" protocol="IPV4V6"></apn>
+ <apn mcc="450" mnc="6" carrier="LG uplus LTE Roaming" apn="lte-roaming.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" authtype="0"></apn>
+ <apn mcc="450" mnc="6" carrier="LG uplus Roaming" apn="wroaming.lguplus.co.kr" mmsc="http://omammsc.uplus.co.kr:9084" authtype="0"></apn>
+ <apn mcc="450" mnc="08" carrier="KT" apn="lte.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" server="*"></apn>
+ <apn mcc="450" mnc="08" carrier="KT 3G INTERNET" apn="alwayson-r6.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" type="default,mms,supl,fota,cbs" server="*" port="80"></apn>
+ <apn mcc="450" mnc="08" carrier="KT LTE INTERNET" apn="lte150.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" type="default,mms,supl,fota,cbs" server="*" port="80"></apn>
+ <apn mcc="450" mnc="08" carrier="KT LTE INTERNET" apn="lte150.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" type="ia,default,mms,supl,fota,cbs" server="*" port="80"></apn>
+ <apn mcc="450" mnc="8" carrier="KT" apn="lte.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" type="default,mms" authtype="0" server="*"></apn>
+ <apn mcc="450" mnc="8" carrier="KT 3G INTERNET" apn="alwayson-r6.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" type="default,mms,supl,fota,cbs" authtype="0" server="*" port="80"></apn>
+ <apn mcc="450" mnc="8" carrier="KT LTE INTERNET" apn="lte150.ktfwing.com" mmsc="http://mmsc.ktfwing.com:9082" type="default,mms,supl,fota,cbs" authtype="0" server="*" port="80"></apn>
+ <apn mcc="450" mnc="11" carrier="SKT 3G INTERNET" apn="web.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" type="default,mms,supl,fota,cbs" server="*"></apn>
+ <apn mcc="450" mnc="11" carrier="SKT 3G Roaming" apn="roaming.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" server="*"></apn>
+ <apn mcc="450" mnc="11" carrier="SKT LTE Roaming" apn="lte-roaming.sktelecom.com" mmsc="http://omms.nate.com:9082/oma_mms" mmsproxy="smart.nate.com" mmsport="9093" server="*"></apn>
+ <apn mcc="452" mnc="01" carrier="Mobi-gprs-mms" apn="m-i090" mmsc="http://203.162.21.114/mmsc" mmsproxy="203.162.21.114" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="452" mnc="1" carrier="Mobi MMS" apn="m-i090" mmsc="http://203.162.21.114/mmsc" mmsproxy="203.162.21.114" mmsport="9201" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="452" mnc="1" carrier="Mobi-gprs-mms" apn="m-i090" mmsc="http://203.162.21.114/mmsc" mmsproxy="203.162.21.114" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="452" mnc="02" carrier="Vina-gprs-mms" apn="m3-mms" mmsc="http://mms.vinaphone.com.vn" mmsproxy="10.1.10.46" mmsport="8000" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="452" mnc="2" carrier="Vina MMS" apn="m3-mms" mmsc="http://mms.vinaphone.com.vn" mmsproxy="10.1.10.46" mmsport="9201" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="452" mnc="2" carrier="Vina-gprs-mms" apn="m3-mms" mmsc="http://mms.vinaphone.com.vn" mmsproxy="10.1.10.46" mmsport="8000" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="452" mnc="04" carrier="Viettel-gprs-mms" apn="v-mms" mmsc="http://mms.viettelmobile.com.vn/mms/wapenc" mmsproxy="192.168.233.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="452" mnc="4" carrier="Viettel MMS" apn="v-mms" mmsc="http://mms.viettelmobile.com.vn/mms/wapenc" mmsproxy="192.168.233.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="452" mnc="4" carrier="Viettel-gprs-mms" apn="v-mms" mmsc="http://mms.viettelmobile.com.vn/mms/wapenc" mmsproxy="192.168.233.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="452" mnc="05" carrier="Vietnamobile_MMS" apn="mms" mmsc="http://10.10.128.58/servlets/mms" mmsproxy="10.10.128.44" mmsport="8080" type="mms"></apn>
+ <apn mcc="452" mnc="5" carrier="EVN MMS" apn="mms" mmsc="http://10.10.128.58/servlets/mms" mmsproxy="10.10.128.44" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="452" mnc="5" carrier="Vietnamobile_MMS" apn="mms" mmsc="http://10.10.128.58/servlets/mms" mmsproxy="10.10.128.44" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="452" mnc="07" carrier="Gmobile MMS" apn="mms" mmsc="http://mms" mmsproxy="10.16.70.199" mmsport="8080" type="mms" user="mms" password="mms" authtype="2"></apn>
+ <apn mcc="452" mnc="07" carrier="Gmobile-gprs-mms" apn="mms" mmsc="http://mms" mmsproxy="10.16.70.199" mmsport="8080" type="mms"></apn>
+ <apn mcc="452" mnc="7" carrier="Beeline MMS VN" apn="mms" mmsc="http://mms" mmsproxy="10.16.70.199" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="452" mnc="7" carrier="Gmobile-gprs-mms" apn="mms" mmsc="http://mms" mmsproxy="10.16.70.199" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="452" mnc="08" carrier="e-mms" apn="e-mms" mmsc="http://10.18.2.172:38090" mmsproxy="10.18.2.183" mmsport="8080" type="mms"></apn>
+ <apn mcc="452" mnc="8" carrier="e-mms" apn="e-mms" mmsc="http://10.18.2.172:38090" mmsproxy="10.18.2.183" mmsport="8080" type="mms"></apn>
+ <apn mcc="454" mnc="00" carrier="1O1O" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="00" carrier="NWMOBILE" apn="NWMOBILE" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.61" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="00" carrier="one2free" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="000" carrier="one2free" apn="hkcsl" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="002" carrier="one2free" apn="hkcsl" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="02" carrier="1O1O" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="02" carrier="NWMOBILE" apn="NWMOBILE" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.61" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="02" carrier="one2free" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="2" carrier="1O1O" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="2" carrier="NWMOBILE" apn="NWMOBILE" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.61" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="03" carrier="3" apn="mobile.three.com.hk" mmsc="http://mms.um.three.com.hk:10021/mmsc" mmsproxy="mms.three.com.hk" mmsport="8799" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="03" carrier="3 LTE" apn="mobile.lte.three.com.hk" mmsc="http://mms.um.three.com.hk:10021/mmsc" mmsproxy="mms.three.com.hk" mmsport="8799" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="004" carrier="3" apn="mobile.three.com.hk" mmsc="http://mms.um.three.com.hk:10021/mmsc" mmsproxy="mms.three.com.hk" mmsport="8799"></apn>
+ <apn mcc="454" mnc="04" carrier="3(2G) MMS" apn="mms-g.three.com.hk" mmsc="http://10.30.15.51:10021/mmsc" mmsproxy="10.30.15.53" mmsport="8080" type="mms"></apn>
+ <apn mcc="454" mnc="4" carrier="3(2G) MMS" apn="mms-g.three.com.hk" mmsc="http://10.30.15.51:10021/mmsc" mmsproxy="10.30.15.53" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="454" mnc="4" carrier="3-DB MMS" apn="mms-g.three.com.hk" mmsc="http://10.30.15.51:10021/mmsc" mmsproxy="10.30.15.53" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="454" mnc="06" carrier="SmarTone" apn="SmarTone" mmsc="http://mms.smartone.com/server" mmsproxy="10.9.9.9" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="10" carrier="one2free" apn="hkcsl" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="12" carrier="CMHK MMS" apn="cmhk" mmsc="http://mms.hk.chinamobile.com/mms" type="mms"></apn>
+ <apn mcc="454" mnc="12" carrier="CMHK MMS" apn="cmhk" mmsc="http://mms.hk.chinamobile.com/mms" type="mms" authtype="0"></apn>
+ <apn mcc="454" mnc="12" carrier="CMHK MMS" apn="peoples.mms" mmsc="http://mms.hk.chinamobile.com/mms" mmsproxy="172.31.31.36" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="454" mnc="13" carrier="CMHK MMS" apn="cmhk" mmsc="http://mms.hk.chinamobile.com/mms" type="mms"></apn>
+ <apn mcc="454" mnc="15" carrier="SmarTone" apn="SmarTone" mmsc="http://mms.smartone.com/server" mmsproxy="10.9.9.9" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="16" carrier="PCCW MMS" apn="pccwmms" mmsc="http://mmsc.mms.pccwmobile.com:8002/" mmsproxy="10.131.2.8" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="454" mnc="16" carrier="PCCW-HKT" apn="pccw" mmsc="http://3gmms.pccwmobile.com:8080/was" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="16" carrier="PCCW-HKT" apn="pccw" mmsc="http://mms.pccw-hkt.com:8080" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="17" carrier="SmarTone" apn="SmarTone" mmsc="http://mms.smartone.com/server" mmsproxy="10.9.9.9" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="18" carrier="1O1O" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="18" carrier="NWMOBILE" apn="NWMOBILE" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.61" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="18" carrier="one2free" apn="hkcsl" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="18" carrier="one2free" apn="mobile" mmsc="http://192.168.58.171:8002" mmsproxy="192.168.59.51" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="454" mnc="019" carrier="PCCW-HKT" apn="pccw" mmsc="http://mms.pccw-hkt.com:8080" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="19" carrier="PCCW-HKT" apn="pccw" mmsc="http://3gmms.pccwmobile.com:8080/was" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="020" carrier="PCCW-HKT" apn="pccw" mmsc="http://3gmms.pccwmobile.com:8080/was" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="20" carrier="PCCW-HKT" apn="pccw" mmsc="http://mms.pccw-hkt.com:8080" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="029" carrier="PCCW-HKT" apn="pccw" mmsc="http://3gmms.pccwmobile.com:8080/was" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="454" mnc="029" carrier="PCCW-HKT" apn="pccw" mmsc="http://mms.pccw-hkt.com:8080" mmsproxy="10.140.14.10" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="455" mnc="00" carrier="SmarTone Macau" apn="smartgprs" mmsc="http://momms.smartone.com/dmog/mo" mmsproxy="10.9.9.29" mmsport="8080" type="default,supl,mms" authtype="3"></apn>
+ <apn mcc="455" mnc="01" carrier="CTM (Prepaid)" apn="ctmprepaid" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="455" mnc="01" carrier="CTM MMS" apn="ctmmms" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="mms"></apn>
+ <apn mcc="455" mnc="1" carrier="CTM (Prepaid)" apn="ctmprepaid" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="455" mnc="1" carrier="CTM MMS" apn="ctmmms" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="455" mnc="03" carrier="3 Macau" apn="mobile.three.com.mo" mmsc="http://mms.three.com.mo:10021/mmsc" mmsproxy="mms.three.com.mo" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="455" mnc="3" carrier="HutchisonMMS" apn="mms.hutchisonmacau.com" mmsc="http://10.30.15.51:10021/mmsc" mmsproxy="10.30.15.53" mmsport="9201" type="mms" user="hutchison" password="1234" authtype="3" proxy="10.30.3.151" port="9201"></apn>
+ <apn mcc="455" mnc="04" carrier="CTM (Prepaid)" apn="ctmprepaid" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="455" mnc="04" carrier="CTM MMS" apn="ctmmms" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="mms"></apn>
+ <apn mcc="455" mnc="4" carrier="CTM (Prepaid)" apn="ctmprepaid" mmsc="http://mms.wap.ctm.net:8002" mmsproxy="192.168.99.3" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="455" mnc="05" carrier="3 Macau" apn="mobile.three.com.mo" mmsc="http://mms.three.com.mo:10021/mmsc" mmsproxy="mms.three.com.mo" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="460" mnc="0" carrier="中国移动彩信 (China Mobile)" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0" proxy="10.0.0.172" port="80"></apn>
+ <apn mcc="460" mnc="0" carrier="移动彩信" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="00" carrier="中国移动彩信 (China Mobile)" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" proxy="10.0.0.172" port="80"></apn>
+ <apn mcc="460" mnc="01" carrier="中国联通 3g 彩信 (China Unicom)" apn="3gwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms"></apn>
+ <apn mcc="460" mnc="01" carrier="中国联通彩信 (China Unicom)" apn="uniwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms"></apn>
+ <apn mcc="460" mnc="01" carrier="联通2g彩信 (China Unicom)" apn="uniwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms"></apn>
+ <apn mcc="460" mnc="01" carrier="联通彩信 (China Unicom)" apn="3gwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms"></apn>
+ <apn mcc="460" mnc="1" carrier="2G联通彩信" apn="uniwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="1" carrier="3G联通彩信" apn="3gwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="1" carrier="联通2g彩信 (China Unicom)" apn="uniwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="1" carrier="联通彩信 (China Unicom)" apn="3gwap" mmsc="http://mmsc.myuni.com.cn" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="02" carrier="中国移动彩信 (China Mobile)" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" proxy="10.0.0.172" port="80"></apn>
+ <apn mcc="460" mnc="2" carrier="中国移动彩信 (China Mobile)" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0" proxy="10.0.0.172" port="80"></apn>
+ <apn mcc="460" mnc="2" carrier="移动彩信" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="03" carrier="CTWAP" apn="ctwap" mmsc="http://mmsc.vnet.mobi" mmsproxy="10.0.0.200" mmsport="80" type="default,mms" proxy="10.0.0.200" port="80"></apn>
+ <apn mcc="460" mnc="07" carrier="中国移动彩信 (China Mobile)" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" proxy="10.0.0.172" port="80"></apn>
+ <apn mcc="460" mnc="7" carrier="中国移动彩信 (China Mobile)" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0" proxy="10.0.0.172" port="80"></apn>
+ <apn mcc="460" mnc="7" carrier="移动彩信" apn="cmwap" mmsc="http://mmsc.monternet.com" mmsproxy="10.0.0.172" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="460" mnc="12" carrier="CTWAP" apn="ctwap" mmsc="http://mmsc.vnet.mobi" mmsproxy="10.0.0.200" mmsport="80" type="default,mms" proxy="10.0.0.200" port="80"></apn>
+ <apn mcc="460" mnc="13" carrier="CTWAP" apn="ctwap" mmsc="http://mmsc.vnet.mobi" mmsproxy="10.0.0.200" mmsport="80" type="default,mms" proxy="10.0.0.200" port="80"></apn>
+ <apn mcc="466" mnc="01" carrier="遠傳電信(Far EasTone) (MMS)" apn="fetnet01" mmsc="http://mms" mmsproxy="210.241.199.199" mmsport="9201" type="mms"></apn>
+ <apn mcc="466" mnc="1" carrier="遠傳電信 MMS" apn="fetnet01" mmsc="http://mms/" mmsproxy="210.241.199.199" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="1" carrier="遠傳電信(Far EasTone) (MMS)" apn="fetnet01" mmsc="http://mms" mmsproxy="210.241.199.199" mmsport="9201" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="88" carrier="和信電訊(KGT-Online) (MMS)" apn="kgtmms" mmsc="http://mms.kgtmms.net.tw/mms/wapenc" mmsproxy="172.28.33.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="466" mnc="89" carrier="T Star-MMS" apn="internet" mmsc="http://mms.vibo.net.tw" mmsproxy="172.24.128.36" mmsport="8080" type="mms"></apn>
+ <apn mcc="466" mnc="89" carrier="威寶電信 MMS" apn="vibo" mmsc="http://mms/" mmsproxy="172.24.128.36" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="89" carrier="威寶電信(VIBO) (MMS)" apn="vibo" mmsc="http://mms" mmsproxy="172.24.128.36" mmsport="8080" type="mms"></apn>
+ <apn mcc="466" mnc="89" carrier="威寶電信(VIBO) (MMS)" apn="vibo" mmsc="http://mms" mmsproxy="172.24.128.36" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="92" carrier="中華電信 MMS" apn="emome" mmsc="http://mms.emome.net:8002" mmsproxy="10.1.1.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="92" carrier="中華電信(Chunghwa) (MMS)" apn="emome" mmsc="http://mms.emome.net:8002" mmsproxy="10.1.1.1" mmsport="8080" type="mms"></apn>
+ <apn mcc="466" mnc="92" carrier="中華電信(Chunghwa) (MMS)" apn="emome" mmsc="http://mms.emome.net:8002" mmsproxy="10.1.1.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="93" carrier="台湾大哥大 MMS" apn="mms" mmsc="http://mms.catch.net.tw/" mmsproxy="10.1.1.2" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="93" carrier="台灣大哥大(TW Mobile) (MMS)" apn="MMS" mmsc="http://mms.catch.net.tw" mmsproxy="10.1.1.2" mmsport="80" type="mms"></apn>
+ <apn mcc="466" mnc="93" carrier="台灣大哥大(TW Mobile) (MMS)" apn="MMS" mmsc="http://mms.catch.net.tw" mmsproxy="10.1.1.2" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="97" carrier="台湾大哥大 MMS" apn="mms" mmsc="http://mms.catch.net.tw/" mmsproxy="10.1.1.2" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="97" carrier="台灣大哥大(TW Mobile) (MMS)" apn="MMS" mmsc="http://mms.catch.net.tw" mmsproxy="10.1.1.2" mmsport="80" type="mms"></apn>
+ <apn mcc="466" mnc="97" carrier="台灣大哥大(TW Mobile) (MMS)" apn="MMS" mmsc="http://mms.catch.net.tw" mmsproxy="10.1.1.2" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="99" carrier="台湾大哥大 MMS" apn="mms" mmsc="http://mms.catch.net.tw/" mmsproxy="10.1.1.2" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="466" mnc="99" carrier="台灣大哥大(TW Mobile) (MMS)" apn="mms" mmsc="http://mms.catch.net.tw" mmsproxy="10.1.1.2" mmsport="80" type="mms"></apn>
+ <apn mcc="466" mnc="99" carrier="台灣大哥大(TW Mobile) (MMS)" apn="mms" mmsc="http://mms.catch.net.tw" mmsproxy="10.1.1.2" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="470" mnc="01" carrier="GP-MMS" apn="gpmms" mmsc="http://mms.gpsurf.net/servlets/mms" mmsproxy="10.128.1.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="470" mnc="1" carrier="GP-MMS" apn="gpmms" mmsc="http://mms.gpsurf.net/servlets/mms" mmsproxy="10.128.1.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="502" mnc="10" carrier="DiGi MMS" apn="digimms" mmsc="http://mms.digi.com.my/servlets/mms" mmsproxy="203.92.128.160" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="502" mnc="12" carrier="Maxis" apn="unet" mmsc="http://172.16.74.100:10021/mmsc" mmsproxy="202.75.133.49" mmsport="80" type="default,mms" user="maxis" password="wap" authtype="0" proxy="202.75.133.49" port="80"></apn>
+ <apn mcc="502" mnc="12" carrier="Maxis Internet" apn="max4g" mmsc="http://172.16.74.100:10021/mmsc" mmsproxy="202.75.133.49" mmsport="80" type="default,supl,mms" user="maxis" password="wap" authtype="1"></apn>
+ <apn mcc="502" mnc="13" carrier="Celcom Internet" apn="celcom4g" mmsc="http://mms.celcom.net.my" mmsproxy="10.128.1.242" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="502" mnc="16" carrier="DiGi MMS" apn="digimms" mmsc="http://mms.digi.com.my/servlets/mms" mmsproxy="203.92.128.160" mmsport="80" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="502" mnc="16" carrier="DiGi MMS" apn="digimms" mmsc="http://mms.digi.com.my/servlets/mms" mmsproxy="203.92.128.160" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="502" mnc="17" carrier="Maxis Internet" apn="max4g" mmsc="http://172.16.74.100:10021/mmsc" mmsproxy="202.75.133.49" mmsport="80" type="default,supl,mms" user="maxis" password="wap" authtype="1"></apn>
+ <apn mcc="502" mnc="18" carrier="U Mobile Internet" apn="my3g" mmsc="http://10.30.3.11/servlets/mms" mmsproxy="10.30.5.11" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="502" mnc="18" carrier="U Mobile Internet" apn="my3g" mmsc="http://10.30.3.11/servlets/mms" mmsproxy="10.30.5.11" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="502" mnc="18" carrier="umobile" apn="my3g" mmsc="http://10.30.3.11/servlets/mms" mmsproxy="10.30.5.11" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="502" mnc="19" carrier="Celcom" apn="celcom3G" mmsc="http://mms.celcom.net.my" mmsproxy="10.128.1.242" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="502" mnc="19" carrier="Celcom Internet" apn="celcom4g" mmsc="http://mms.celcom.net.my" mmsproxy="10.128.1.242" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="502" mnc="19" carrier="Celcom Internet" apn="celcom4g" mmsc="http://mms.celcom.net.my" mmsproxy="10.128.1.242" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="502" mnc="142" carrier="Maxis Internet" apn="max4g" mmsc="http://172.16.74.100:10021/mmsc" mmsproxy="202.75.133.49" mmsport="80" type="default,supl,mms" user="maxis" password="wap" authtype="1"></apn>
+ <apn mcc="502" mnc="143" carrier="DiGi MMS" apn="digimms" mmsc="http://mms.digi.com.my/servlets/mms" mmsproxy="203.92.128.160" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="502" mnc="145" carrier="Celcom Internet" apn="celcom4g" mmsc="http://mms.celcom.net.my" mmsproxy="10.128.1.242" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="502" mnc="146" carrier="DiGi MMS" apn="digimms" mmsc="http://mms.digi.com.my/servlets/mms" mmsproxy="203.92.128.160" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="502" mnc="147" carrier="Maxis Internet" apn="max4g" mmsc="http://172.16.74.100:10021/mmsc" mmsproxy="202.75.133.49" mmsport="80" type="default,supl,mms" user="maxis" password="wap" authtype="1"></apn>
+ <apn mcc="502" mnc="148" carrier="Celcom Internet" apn="celcom4g" mmsc="http://mms.celcom.net.my" mmsproxy="10.128.1.242" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="505" mnc="01" carrier="Telstra MMS" apn="telstra.mms" mmsc="http://mmsc.telstra.com:8002/" mmsproxy="10.1.1.180" mmsport="80" type="mms"></apn>
+ <apn mcc="505" mnc="1" carrier="Telstra MMS" apn="telstra.mms" mmsc="http://mmsc.telstra.com:8002" mmsproxy="10.1.1.180" mmsport="80" type="mms" authtype="0" protocol="IP"></apn>
+ <apn mcc="505" mnc="1" carrier="Telstra MMS" apn="telstra.mms" mmsc="http://mmsc.telstra.com:8002/" mmsproxy="10.1.1.180" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="505" mnc="02" carrier="Optus MMS" apn="mms" mmsc="http://mmsc.optus.com.au:8002/" mmsproxy="61.88.190.10" mmsport="8070" type="mms"></apn>
+ <apn mcc="505" mnc="02" carrier="Virgin MMS" apn="virginmms" mmsc="http://mms.virginvibe.com.au:8002" mmsproxy="202.139.83.152" mmsport="8070" type="mms" mvno_match_data="505029" mvno_type="imsi"></apn>
+ <apn mcc="505" mnc="2" carrier="Optus MMS" apn="mms" mmsc="http://mmsc.optus.com.au:8002/" mmsproxy="61.88.190.10" mmsport="8070" type="mms" authtype="0"></apn>
+ <apn mcc="505" mnc="2" carrier="Virgin MMS" apn="virginmms" mmsc="http://mms.virginvibe.com.au:8002" mmsproxy="202.139.83.152" mmsport="8070" type="mms" authtype="0" mvno_match_data="505029" mvno_type="imsi"></apn>
+ <apn mcc="505" mnc="2" carrier="Virgin MMS" apn="virginmms" mmsc="http://mms.virginvibe.com.au:8002/" mmsproxy="202.139.83.152" mmsport="8070" type="mms" authtype="0" mvno_match_data="virgin mobile" mvno_type="spn"></apn>
+ <apn mcc="505" mnc="03" carrier="Vodafone live!" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.au/pxtsend" mmsproxy="10.202.2.60" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="505" mnc="3" carrier="Vodafone AU" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.au/pxtsend" mmsproxy="10.202.2.60" mmsport="8080" type="default,mms" authtype="0" protocol="IP"></apn>
+ <apn mcc="505" mnc="3" carrier="Vodafone live!" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.au/pxtsend" mmsproxy="10.202.2.60" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="505" mnc="06" carrier="Planet 3" apn="3services" mmsc="http://mmsc.three.net.au:10021/mmsc" mmsproxy="10.176.57.25" mmsport="8799" authtype="0" protocol="IP"></apn>
+ <apn mcc="505" mnc="6" carrier="Planet 3" apn="3services" mmsc="http://mmsc.three.net.au:10021/mmsc" mmsproxy="10.176.57.25" mmsport="8799" authtype="0" protocol="IP"></apn>
+ <apn mcc="505" mnc="07" carrier="VF AU PXT" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.au/pxtsend" mmsproxy="10.202.2.60" mmsport="8080" type="mms"></apn>
+ <apn mcc="505" mnc="11" carrier="Telstra MMS" apn="Telstra.mms" mmsc="http://mmsc.telstra.com:8002" mmsproxy="10.1.1.180" mmsport="80" type="mms"></apn>
+ <apn mcc="505" mnc="12" carrier="3" apn="3services" mmsc="http://mmsc.three.net.au:10021/mmsc" mmsproxy="10.176.57.25" mmsport="8799" type="default,supl,mms"></apn>
+ <apn mcc="505" mnc="71" carrier="Telstra MMS" apn="Telstra.mms" mmsc="http://mmsc.telstra.com:8002" mmsproxy="10.1.1.180" mmsport="80" type="mms"></apn>
+ <apn mcc="505" mnc="72" carrier="Telstra MMS" apn="Telstra.mms" mmsc="http://mmsc.telstra.com:8002" mmsproxy="10.1.1.180" mmsport="80" type="mms"></apn>
+ <apn mcc="505" mnc="88" carrier="VF AU PXT" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.au/pxtsend" mmsproxy="10.202.2.60" mmsport="8080" type="mms"></apn>
+ <apn mcc="505" mnc="90" carrier="Optus MMS" apn="mms" mmsc="http://mmsc.optus.com.au:8002/" mmsproxy="61.88.190.10" mmsport="8070" type="mms"></apn>
+ <apn mcc="505" mnc="99" carrier="Vodafone Live!" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.au/pxtsend" mmsproxy="10.202.2.60" mmsport="8080"></apn>
+ <apn mcc="510" mnc="01" carrier="INDOSAT-GPRS" apn="indosatgprs" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="*" proxy="10.19.19.19" port="8080"></apn>
+ <apn mcc="510" mnc="01" carrier="Indosat MMS" apn="indosatmms" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="mms" user="indosat" password="indosat" authtype="1"></apn>
+ <apn mcc="510" mnc="1" carrier="Indosat MMS" apn="indosatmms" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="mms" user="indosat" password="indosat" authtype="1"></apn>
+ <apn mcc="510" mnc="1" carrier="Indosat MMS (01)" apn="indosatmms" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="mms" user="indosat" password="indosat" authtype="0"></apn>
+ <apn mcc="510" mnc="08" carrier="AXISmms" apn="AXISmms" mmsc="http://mmsc.axis" mmsproxy="10.8.3.8" mmsport="8080" type="mms" user="axis" password="123456" authtype="1"></apn>
+ <apn mcc="510" mnc="08" carrier="AXISwap" apn="AXIS" mmsc="http://mmsc.axis" mmsproxy="10.8.3.8" mmsport="8080" type="*" proxy="10.8.3.8" port="8080"></apn>
+ <apn mcc="510" mnc="8" carrier="AXIS MMS" apn="AXISmms" mmsc="http://mmsc.AXIS" mmsproxy="10.8.3.8" mmsport="8080" type="mms" user="AXIS" password="123456" authtype="0"></apn>
+ <apn mcc="510" mnc="10" carrier="TSEL-GPRS" apn="telkomsel" mmsc="http://mms.telkomsel.com" mmsproxy="10.1.89.150" mmsport="8000" type="*" proxy="10.1.89.130" port="8000"></apn>
+ <apn mcc="510" mnc="10" carrier="TSEL-MMS" apn="mms" mmsc="http://mms.telkomsel.com" mmsproxy="10.1.89.150" mmsport="8000" type="mms" user="wap" password="wap123" authtype="1"></apn>
+ <apn mcc="510" mnc="10" carrier="Telkomsel MMS" apn="mms" mmsc="http://mms.telkomsel.com" mmsproxy="10.1.89.150" mmsport="8000" type="mms" user="wap" password="wap123" authtype="0"></apn>
+ <apn mcc="510" mnc="11" carrier="XL MMS" apn="www.xlmms.net" mmsc="http://mmc.xl.net.id/servlets/mms" mmsproxy="202.152.240.50" mmsport="8080" type="mms" user="xlgprs" password="proxl" authtype="0" server="http://mmc.xl.net.id/servlets/mms"></apn>
+ <apn mcc="510" mnc="11" carrier="XL-GPRS" apn="www.xlgprs.net" mmsc="http://mmc.xl.net.id/servlets/mms" mmsproxy="202.152.240.050" mmsport="8080" type="*" proxy="202.152.240.050" port="8080"></apn>
+ <apn mcc="510" mnc="11" carrier="XL-MMS" apn="www.xlmms.net" mmsc="http://mmc.xl.net.id/servlets/mms" mmsproxy="202.152.240.50" mmsport="8080" type="mms" user="xlgprs" password="proxl" authtype="1"></apn>
+ <apn mcc="510" mnc="21" carrier="INDOSAT-GPRS" apn="indosatgprs" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="*" proxy="10.19.19.19" port="8080"></apn>
+ <apn mcc="510" mnc="21" carrier="Indosat MMS" apn="indosatmms" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="mms" user="indosat" password="indosat" authtype="1"></apn>
+ <apn mcc="510" mnc="21" carrier="Indosat MMS (21)" apn="indosatmms" mmsc="http://mmsc.indosat.com" mmsproxy="10.19.19.19" mmsport="8080" type="mms" user="indosat" password="indosat" authtype="0"></apn>
+ <apn mcc="510" mnc="89" carrier="3 MMS (510.89)" apn="3mms" mmsc="http://mms.three.co.id" mmsproxy="10.4.0.10" mmsport="8080" type="mms" user="3mms" password="3mms" authtype="0"></apn>
+ <apn mcc="510" mnc="89" carrier="3-GPRS" apn="3gprs" mmsc="http://mms.three.co.id" mmsproxy="10.4.0.10" mmsport="3128" type="*" proxy="10.4.0.10" port="3128"></apn>
+ <apn mcc="510" mnc="89" carrier="3MMS" apn="3mms" mmsc="http://mms.three.co.id" mmsproxy="10.4.0.10" mmsport="3128" type="mms" user="3mms" password="3mms" authtype="1"></apn>
+ <apn mcc="515" mnc="02" carrier="myGlobe MMS" apn="mms.globe.com.ph" mmsc="http://192.40.100.22:10021/mmsc" mmsproxy="203.177.42.214" mmsport="8080" type="mms"></apn>
+ <apn mcc="515" mnc="2" carrier="Globe MMS" apn="mms.globe.com.ph" mmsc="http://192.40.100.22:10021/mmsc" mmsproxy="203.177.42.214" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="515" mnc="2" carrier="myGlobe MMS" apn="mms.globe.com.ph" mmsc="http://192.40.100.22:10021/mmsc" mmsproxy="203.177.42.214" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="515" mnc="03" carrier="Smart MMS" apn="mms" mmsc="http://10.102.61.238:8002" mmsproxy="10.102.61.46" mmsport="8080" type="mms"></apn>
+ <apn mcc="515" mnc="3" carrier="SMART MMS" apn="mms" mmsc="http://10.102.61.238:8002" mmsproxy="10.102.61.46" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="515" mnc="05" carrier="SUN MMS" apn="mms" mmsc="http://mmscenter.suncellular.com.ph" mmsproxy="202.138.159.78" mmsport="8080" type="mms"></apn>
+ <apn mcc="515" mnc="5" carrier="SUN MMS" apn="mms" mmsc="http://mmscenter.suncellular.com.ph" mmsproxy="202.138.159.78" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="515" mnc="18" carrier="CURE MMS" apn="redmms" mmsc="http://10.102.61.193:8002/mmsc" mmsproxy="10.138.3.35" mmsport="8080" type="mms"></apn>
+ <apn mcc="515" mnc="18" carrier="Redmms" apn="real.globe.com.ph" mmsc="http://10.102.61.193:8002/mmsc" mmsproxy="10.138.3.35" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="0" carrier="CAT3G MMS" apn="catmms" mmsc="http://mms.cat3g.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="0" carrier="TRUE-H MMS" apn="hmms" mmsc="http://mms.trueh.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" user="true" password="true" authtype="3"></apn>
+ <apn mcc="520" mnc="00" carrier="CAT3G MMS" apn="catmms" mmsc="http://mms.cat3g.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="00" carrier="TRUE-H MMS" apn="hmms" mmsc="http://mms.trueh.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" user="true" password="true" authtype="1" mvno_match_data="01" mvno_type="gid"></apn>
+ <apn mcc="520" mnc="01" carrier="AIS MMS" apn="multimedia" mmsc="http://mms.mobilelife.co.th" mmsproxy="203.170.229.34" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="1" carrier="AIS MMS" apn="multimedia" mmsc="http://mms.mobilelife.co.th" mmsproxy="203.170.229.34" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="03" carrier="AIS MMS" apn="multimedia" mmsc="http://mms.mobilelife.co.th" mmsproxy="203.170.229.34" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="3" carrier="AIS 3G MMS" apn="mms" mmsc="http://mms.ais.co.th" mmsproxy="203.170.229.34" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="3" carrier="AIS MMS" apn="multimedia" mmsc="http://mms.mobilelife.co.th" mmsproxy="203.170.229.34" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="04" carrier="TRUE-H MMS" apn="hmms" mmsc="http://mms.trueh.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" user="true" password="true" authtype="1"></apn>
+ <apn mcc="520" mnc="4" carrier="TRUE-H MMS" apn="hmms" mmsc="http://mms.trueh.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" user="true" password="true" authtype="3"></apn>
+ <apn mcc="520" mnc="05" carrier="dtac MMS" apn="mms" mmsc="http://mms2.dtac.co.th:8002/" mmsproxy="10.10.10.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="5" carrier="dtac mms" apn="mms" mmsc="http://mms2.dtac.co.th:8002/" mmsproxy="10.10.10.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="15" carrier="TOT 3G MMS" apn="mms" mmsc="http://mms.tot3g.net:8002" mmsproxy="192.168.0.72" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="15" carrier="TOT 3G MMS" apn="mms" mmsc="http://mms.tot3g.net:8002" mmsproxy="192.168.0.72" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="18" carrier="dtac MMS" apn="mms" mmsc="http://mms.dtac.co.th:8002/" mmsproxy="10.10.10.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="18" carrier="dtac MMS" apn="mms" mmsc="http://mms.dtac.co.th:8002/" mmsproxy="10.10.10.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="18" carrier="dtac MMS" apn="mms" mmsc="http://mms.dtac.co.th:8002/" mmsproxy="203.155.200.133" mmsport="8080" type="mms"></apn>
+ <apn mcc="520" mnc="18" carrier="dtac MMS" apn="mms" mmsc="http://mms.dtac.co.th:8002/" mmsproxy="203.155.200.133" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="520" mnc="99" carrier="TRUE MMS" apn="hmms" mmsc="http://mms.truelife.com:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" user="true" password="true" authtype="1"></apn>
+ <apn mcc="520" mnc="99" carrier="True MMS" apn="mms" mmsc="http://mms.trueworld.net:8002/" mmsproxy="10.4.7.39" mmsport="8080" type="mms" user="true" password="true" authtype="3"></apn>
+ <apn mcc="525" mnc="01" carrier="SingTel (PostPaid)" apn="e-ideas" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="525" mnc="01" carrier="SingTel (PrePaid)" apn="hicard" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="525" mnc="1" carrier="SingTel (PostPaid)" apn="e-ideas" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="525" mnc="1" carrier="SingTel (PrePaid)" apn="hicard" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms" authtype="0"></apn>
+ <apn mcc="525" mnc="1" carrier="SingTel MMS" apn="hicard" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="525" mnc="02" carrier="SingTel (PostPaid)" apn="e-ideas" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="525" mnc="02" carrier="SingTel (PrePaid)" apn="hicard" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="525" mnc="2" carrier="SingTel (PostPaid)" apn="e-ideas" mmsc="http://mms.singtel.com:10021/mmsc" mmsproxy="165.21.42.84" mmsport="8080" type="default,supl,mms"></apn>
+ <apn mcc="525" mnc="03" carrier="M1 MMS(3G)" apn="miworld" mmsc="http://mmsgw:8002" mmsproxy="172.16.14.10" mmsport="8080" type="mms" user="65" password="user123" authtype="1"></apn>
+ <apn mcc="525" mnc="3" carrier="M1 MMS" apn="miworld" mmsc="http://mmsgw:8002/" mmsproxy="172.16.14.10" mmsport="8080" type="mms" user="65" password="user123" authtype="3"></apn>
+ <apn mcc="525" mnc="3" carrier="M1 MMS(3G)" apn="miworld" mmsc="http://mmsgw:8002" mmsproxy="172.16.14.10" mmsport="8080" type="mms" user="65" password="user123" authtype="1"></apn>
+ <apn mcc="525" mnc="04" carrier="M1 MMS(3G)" apn="miworld" mmsc="http://mmsgw:8002" mmsproxy="172.16.14.10" mmsport="8080" type="mms" user="65" password="user123" authtype="1"></apn>
+ <apn mcc="525" mnc="05" carrier="SH MMS Postpaid" apn="shmms" mmsc="http://mms.starhubgee.com.sg:8002/" mmsproxy="10.12.1.80" mmsport="80" type="mms"></apn>
+ <apn mcc="525" mnc="5" carrier="SH MMS Postpaid" apn="shmms" mmsc="http://mms.starhubgee.com.sg:8002/" mmsproxy="10.12.1.80" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="525" mnc="5" carrier="StarHub MMS" apn="shmms" mmsc="http://mms.starhubgee.com.sg:8002/" mmsproxy="10.12.1.80" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="530" mnc="01" carrier="VFNZ Gateway" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.nz/pxtsend" mmsproxy="172.30.38.3" mmsport="8080" type="mms"></apn>
+ <apn mcc="530" mnc="1" carrier="VFNZ Gateway" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.nz/pxtsend" mmsproxy="172.30.38.3" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="530" mnc="1" carrier="VFNZ PXT" apn="live.vodafone.com" mmsc="http://pxt.vodafone.net.nz/pxtsend" mmsproxy="172.30.38.3" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="530" mnc="05" carrier="TelecomMMS" apn="wap.telecom.co.nz" mmsc="http://lsmmsc.xtra.co.nz" mmsproxy="210.55.11.73" mmsport="80" type="mms"></apn>
+ <apn mcc="530" mnc="5" carrier="TelecomMMS" apn="wap.telecom.co.nz" mmsc="http://lsmmsc.xtra.co.nz" mmsproxy="210.55.11.73" mmsport="80" type="mms" authtype="0"></apn>
+ <apn mcc="530" mnc="24" carrier="2Degrees MMS" apn="mms" mmsc="http://mms.2degreesmobile.net.nz:48090" mmsproxy="118.148.1.118" mmsport="8080" type="mms"></apn>
+ <apn mcc="530" mnc="24" carrier="2degrees MMS" apn="mms" mmsc="http://mms.2degreesmobile.net.nz:48090" mmsproxy="118.148.1.118" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="537" mnc="03" carrier="PNG MMS" apn="wap.digicelpng.com" mmsc="http://mms.digicelpng.com:8990" mmsproxy="10.149.83.116" mmsport="8080" type="mms"></apn>
+ <apn mcc="537" mnc="03" carrier="Papua New Guinea:Digicel:Modem" apn="wap.digicel.com.pg" mmsc="http://wapdigicel.com" type="dun" authtype="1" proxy="10.149.122.12" port="8080"></apn>
+ <apn mcc="537" mnc="3" carrier="PNG MMS" apn="wap.digicelpng.com" mmsc="http://mms.digicelpng.com:8990" mmsproxy="10.149.83.116" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="537" mnc="3" carrier="Papua New Guinea:Digicel:Mms" apn="wap.digicel.com.pg" mmsc="http://mmc.digicel.com.pg/servlets/mms" mmsproxy="10.149.122.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="539" mnc="88" carrier="Tonga:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="539" mnc="88" carrier="Tonga:Digicel:Modem" apn="wap" mmsc="http://wapdigicel.com" type="dun" authtype="1" proxy="172.16.7.12" port="8080"></apn>
+ <apn mcc="541" mnc="05" carrier="Vanatu:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="541" mnc="05" carrier="Vanatu:Digicel:Modem" apn="wap" mmsc="http://wapdigicel.com" type="dun" authtype="1" proxy="172.16.7.12" port="8080"></apn>
+ <apn mcc="541" mnc="5" carrier="Vanatu:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="542" mnc="02" carrier="Fiji:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="542" mnc="02" carrier="Fiji:Digicel:Modem" apn="wap" mmsc="http://wapdigicel.com" type="dun" authtype="1" proxy="172.16.7.12" port="8080"></apn>
+ <apn mcc="542" mnc="2" carrier="Fiji:Digicel:Mms" apn="wap" mmsc="http://mms.digicelgroup.com" mmsproxy="172.16.7.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="602" mnc="01" carrier="Mobinil MMS" apn="mobinilmms" mmsc="http://10.7.13.24:8002" mmsproxy="62.241.155.45" mmsport="8080" type="mms"></apn>
+ <apn mcc="602" mnc="1" carrier="Mobinil MMS" apn="mobinilmms" mmsc="http://10.7.13.24:8002" mmsproxy="62.241.155.45" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="602" mnc="02" carrier="Vodafone MMS" apn="mms.vodafone.com.eg" mmsc="http://mms.vodafone.com.eg/servlets/mms" mmsproxy="163.121.178.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="602" mnc="2" carrier="vodafone EG mms" apn="mms.Vodafone.com.eg" mmsc="http://mms.vodafone.com.eg/servlets/mms" mmsproxy="163.121.178.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="602" mnc="03" carrier="Etisalat MMS" apn="etisalat" mmsc="http://10.71.131.7:38090" mmsproxy="10.71.130.29" mmsport="8080" type="mms"></apn>
+ <apn mcc="602" mnc="3" carrier="Etisalat MMS" apn="etisalat" mmsc="http://10.71.131.7:38090" mmsproxy="10.71.130.29" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="602" mnc="3" carrier="etisalat EG" apn="etisalat" mmsc="http://10.71.131.7:38090" mmsproxy="10.71.130.29" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="603" mnc="01" carrier="Mobilis mms" apn="mms" mmsc="http://172.25.49.9/servlets/mms" mmsproxy="172.25.49.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="603" mnc="1" carrier="Mobilis mms" apn="mms" mmsc="http://172.25.49.9/servlets/mms" mmsproxy="172.25.49.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="603" mnc="02" carrier="djezzy.mms" apn="djezzy.mms" mmsc="http://172.24.97.152:6021/mmsc" mmsproxy="172.24.97.158" mmsport="8799" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="603" mnc="2" carrier="Djezzy MMS" apn="djezzy.mms" mmsc="http://172.24.97.152.10021/mmsc" mmsproxy="172.24.97.158" mmsport="9201" type="mms" authtype="0"></apn>
+ <apn mcc="603" mnc="2" carrier="djezzy.mms" apn="djezzy.mms" mmsc="http://172.24.97.152:6021/mmsc" mmsproxy="172.24.97.158" mmsport="8799" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="603" mnc="03" carrier="nedjmamms" apn="nedjmamms" mmsc="http://10.10.111.1" mmsproxy="192.168.52.3" mmsport="3128" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="603" mnc="3" carrier="Nedjma MMS" apn="nedjmamms" mmsc="http://10.10.111.1" mmsproxy="192.168.52.3" mmsport="9201" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="603" mnc="3" carrier="nedjmamms" apn="nedjmamms" mmsc="http://10.10.111.1" mmsproxy="192.168.52.3" mmsport="3128" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="604" mnc="0" carrier="GPRS MMS" apn="mms.meditel.ma" mmsc="http://mms.meditel.ma:8088/mms" mmsproxy="10.8.8.9" mmsport="8080" type="mms" user="MEDIMMS" password="MEDIMMS" authtype="1"></apn>
+ <apn mcc="604" mnc="0" carrier="Meditel MMS" apn="mms.meditel.ma" mmsc="http://mms.meditel.ma:8088/mms" mmsproxy="10.8.8.9" mmsport="8080" type="mms" user="MEDIMMS" password="MEDIMMS" authtype="0"></apn>
+ <apn mcc="604" mnc="00" carrier="GPRS MMS" apn="mms.meditel.ma" mmsc="http://mms.meditel.ma:8088/mms" mmsproxy="10.8.8.9" mmsport="8080" type="mms" user="MEDIMMS" password="MEDIMMS" authtype="1"></apn>
+ <apn mcc="604" mnc="01" carrier="MMS IAM" apn="mmsiam" mmsc="http://mms:8002/" mmsproxy="10.16.35.50" mmsport="8080" type="mms"></apn>
+ <apn mcc="604" mnc="1" carrier="MMS IAM" apn="mmsiam" mmsc="http://mms:8002/" mmsproxy="10.16.35.50" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="604" mnc="02" carrier="MMS" apn="mms.wana.ma" mmsc="http://mms.wana.ma:38090" mmsproxy="10.86.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="604" mnc="2" carrier="MMS" apn="mms.wana.ma" mmsc="http://mms.wana.ma:38090" mmsproxy="10.86.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="605" mnc="01" carrier="MMS Orange" apn="mms.otun" mmsc="http://mms.orange.tn" mmsproxy="10.12.1.52" mmsport="8080" type="mms"></apn>
+ <apn mcc="605" mnc="1" carrier="MMS Orange" apn="mms.otun" mmsc="http://mms.orange.tn" mmsproxy="10.12.1.52" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="605" mnc="1" carrier="Orange TUN MMS" apn="mms.otun" mmsc="https://mms.orange.tn" mmsproxy="10.12.1.52" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="605" mnc="02" carrier="Tunisie Telecom MMS" apn="mms.tn" mmsc="http://192.168.0.3:19090/was" mmsproxy="192.168.0.2" mmsport="8080" type="mms" user="mms@tt1" password="mms" authtype="1"></apn>
+ <apn mcc="605" mnc="2" carrier="Tunisie Telecom MMS" apn="mms.tn" mmsc="http://192.168.0.3:19090/was" mmsproxy="192.168.0.2" mmsport="8080" type="mms" user="mms@tt1" password="mms" authtype="1"></apn>
+ <apn mcc="605" mnc="2" carrier="Tuntel MMS" apn="mms.tn" mmsc="http://mms/" mmsproxy="213.150.186.106" mmsport="9201" type="mms" user="mms@tt1" password="mms" authtype="0"></apn>
+ <apn mcc="605" mnc="03" carrier="MMS Tunisiana" apn="mms.tunisiana.com" mmsc="http://mmsc.tunisiana.com" mmsproxy="10.3.2.100" mmsport="80" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="605" mnc="3" carrier="Tunisiana MMS" apn="mms.tunisiana.com" mmsc="http://mmsc.tunisiana.com" mmsproxy="10.3.2.100" mmsport="9201" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="606" mnc="0" carrier="Libyana MMS" apn="mms" mmsc="http://62.240.62.180:80" mmsproxy="192.168.8.148" mmsport="8000" type="mms" authtype="0"></apn>
+ <apn mcc="606" mnc="00" carrier="Libyana MMS" apn="mms" mmsc="http://62.240.62.180:80" mmsproxy="192.168.8.148" mmsport="8000" type="mms" authtype="0"></apn>
+ <apn mcc="608" mnc="01" carrier="Orange MMS SN" apn="mms" mmsc="http://mmsalize/servlets/mms" mmsproxy="172.16.30.9" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="608" mnc="1" carrier="Orange MMS SN" apn="mms" mmsc="http://mmsalize/servlets/mms" mmsproxy="172.16.30.9" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="608" mnc="1" carrier="Orange SN MMS" apn="mms" mmsc="http://mmsalize/servlets/mms" mmsproxy="172.16.30.9" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="610" mnc="02" carrier="Orange ML MMS" apn="mms" mmsc="http://10.109.6.2/servlets/mms" mmsproxy="10.109.4.35" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="610" mnc="2" carrier="Orange ML MMS" apn="mms" mmsc="http://10.109.6.2/servlets/mms" mmsproxy="10.109.4.35" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="610" mnc="2" carrier="Orange ML MMS" apn="mms" mmsc="http://10.109.6.2/servlets/mms" mmsproxy="10.109.4.35" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="612" mnc="03" carrier="Omms CI" apn="orangecimms" mmsc="http://172.20.6.1/servlets/mms" mmsproxy="172.20.4.33" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="612" mnc="3" carrier="Omms CI" apn="orangecimms" mmsc="http://172.20.6.1/servlets/mms" mmsproxy="172.20.4.33" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="612" mnc="3" carrier="Orange CI MMS" apn="orangecimms" mmsc="http://172.20.6.1/servlets/mms" mmsproxy="172.20.4.33" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="614" mnc="04" carrier="Orange MMS" apn="orange.mms" mmsc="http://10.10.10.35:38090/was" mmsproxy="10.10.10.36" mmsport="8080" type="mms" user="orange" password="orange"></apn>
+ <apn mcc="614" mnc="4" carrier="Orange MMS" apn="orange.mms" mmsc="http://10.10.10.35:38090/was" mmsproxy="10.10.10.36" mmsport="8080" type="mms" user="orange" password="orange" authtype="3"></apn>
+ <apn mcc="614" mnc="4" carrier="Orange NE MMS" apn="orange.mms" mmsc="http://10.10.35:38090/was" mmsproxy="10.10.10.36" mmsport="8080" type="mms" user="orange" password="orange" authtype="0"></apn>
+ <apn mcc="617" mnc="01" carrier="Orange MMS" apn="orangemms" mmsc="http://10.2.1.20:8514" mmsproxy="10.2.1.20" mmsport="8080" type="mms" user="mmsc" password="mmsc"></apn>
+ <apn mcc="617" mnc="1" carrier="Orange MMS" apn="orangemms" mmsc="http://10.2.1.20:8514" mmsproxy="10.2.1.20" mmsport="8080" type="mms" user="mmsc" password="mmsc" authtype="3"></apn>
+ <apn mcc="617" mnc="1" carrier="Orange MU MMS" apn="orangemms" mmsc="http://1.2.1.20:8514" mmsproxy="10.2.1.20:8080" type="mms" user="mmsc" password="mmsc" authtype="0"></apn>
+ <apn mcc="620" mnc="0" carrier="Glo mms" apn="glo mms" mmsc="http://mms.gloworld.com/mms" mmsproxy="10.161.85.4" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="01" carrier="MTN MMS" apn="mtn mms" mmsc="http://172.17.3.7" mmsproxy="172.17.3.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="1" carrier="MTN MMS" apn="mtn mms" mmsc="http://172.17.3.7" mmsproxy="172.17.3.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="02" carrier="Vodafone_mms" apn="mms" mmsc="http://mms.vodaphone.com.gh/mms" mmsproxy="172.24.97.1" mmsport="9201" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="2" carrier="Vodafone_mms" apn="mms" mmsc="http://mms.vodaphone.com.gh/mms" mmsproxy="172.24.97.1" mmsport="9201" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="03" carrier="Tigo mms" apn="mms.tigo.com.gh" mmsc="http://mms/" mmsproxy="10.4.1.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="3" carrier="Tigo mms" apn="mms.tigo.com.gh" mmsc="http://mms/" mmsproxy="10.4.1.7" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="06" carrier="Airtel mms" apn="mms/airtel mms" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="620" mnc="6" carrier="Airtel mms" apn="mms/airtel mms" mmsc="http://100.1.201.171:10021/mmsc" mmsproxy="100.1.201.172" mmsport="8799" type="mms" authtype="0"></apn>
+ <apn mcc="621" mnc="20" carrier="Airtel MMS" apn="mms.ng.zain.com" mmsc="http://10.210.3.239:9800/mm1" mmsproxy="172.18.254.5" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="621" mnc="20" carrier="Zain NG MMS" apn="mms.ng.zain.com" mmsc="http://172.23.1.12/wapenc" mmsproxy="172.18.254.5" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="621" mnc="30" carrier="MTN MMS" apn="web.gprs.mtnnigeria.net" mmsc="http://10.199.212.8/servlets/mms" mmsproxy="10.199.212.2" type="mms"></apn>
+ <apn mcc="621" mnc="30" carrier="MTN MMS" apn="web.gprs.mtnnigeria.net" mmsc="http://10.199.212.8/servlets/mms" mmsproxy="10.199.212.2" type="mms" authtype="0"></apn>
+ <apn mcc="621" mnc="30" carrier="MTN NG Internet" apn="web.gprs.mtnnigeria.net" mmsc="http://10.199.212.8/servlets/mms" mmsproxy="10.199.212.2" mmsport="8080" type="default,mms" user="web" password="web" authtype="0"></apn>
+ <apn mcc="621" mnc="50" carrier="Glo MMS" apn="glomms" mmsc="http://mms.gloworld.com/mmsc" mmsproxy="10.100.114.144" mmsport="3130" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="621" mnc="50" carrier="Glo MMS" apn="glomms" mmsc="http://mms.gloworld.com/mmsc" mmsproxy="10.100.82.4" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="621" mnc="60" carrier="Etisalat Internet" apn="etisalat" mmsc="http://10.71.170.30:38090/was" mmsproxy="10.71.170.5" mmsport="8080" type="default,mms" authtype="0"></apn>
+ <apn mcc="621" mnc="60" carrier="etisalat MMS" apn="etisalat" mmsc="http://10.71.170.30:38090/was" type="mms"></apn>
+ <apn mcc="621" mnc="60" carrier="etisalat MMS" apn="etisalat" mmsc="http://10.71.170.30:38090/was" type="mms" authtype="0"></apn>
+ <apn mcc="624" mnc="02" carrier="Orange CM" apn="orangecmgprs" mmsc="http://mms.orange.cm" mmsproxy="192.168.122.101" mmsport="8080" type="default,mms" user="orange" password="orange" proxy="192.168.122.101" port="8080"></apn>
+ <apn mcc="624" mnc="2" carrier="Orange CM" apn="orangecmgprs" mmsc="http://mms.orange.cm" mmsproxy="192.168.122.101" mmsport="8080" type="default,mms" user="orange" password="orange" authtype="0"></apn>
+ <apn mcc="624" mnc="2" carrier="Orange CM" apn="orangecmgprs" mmsc="http://mms.orange.cm" mmsproxy="192.168.122.101" mmsport="8080" type="default,mms" user="orange" password="orange" authtype="3" proxy="192.168.122.101" port="8080"></apn>
+ <apn mcc="627" mnc="01" carrier="Orange GQ MMS" apn="orangemms" mmsc="http://192.168.17.34/servlets/mms" mmsproxy="192.168.17.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="627" mnc="1" carrier="Orange GQ MMS" apn="orangemms" mmsc="http://192.168.17.34/servlets/mms" mmsproxy="192.168.17.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="630" mnc="01" carrier="Vodacom MMS" apn="vodalive" mmsc="http://172.24.97.1/mmsc" mmsproxy="172.24.97.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="630" mnc="1" carrier="Vodacom MMS" apn="vodalive" mmsc="http://172.24.97.1/mmsc" mmsproxy="172.24.97.1" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="639" mnc="02" carrier="safaricom mms" apn="safaricom" mmsc="http://mms.gprs.safaricom.com" mmsproxy="172.22.2.38" mmsport="8080" type="mms" user="saf" password="data" authtype="1"></apn>
+ <apn mcc="639" mnc="2" carrier="Safaricom Internet" apn="safaricom" mmsc="http://mms.gprs.safaricom.com" mmsproxy="172.22.2.38" mmsport="8080" type="default,mms" user="saf" password="data" authtype="0" proxy="172.22.2.38" port="8080"></apn>
+ <apn mcc="639" mnc="2" carrier="safaricom mms" apn="safaricom" mmsc="http://mms.gprs.safaricom.com" mmsproxy="172.22.2.38" mmsport="8080" type="mms" user="saf" password="data" authtype="1"></apn>
+ <apn mcc="639" mnc="03" carrier="Airtel mms" apn="mms" mmsc="http://mms.ke.airtel.com:8002" mmsproxy="172.30.9.8" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="639" mnc="03" carrier="mms" apn="ke.celtel.com" mmsc="http://mms.ke.celtel.com/servlets/mms" mmsproxy="172.30.8.50" mmsport="8080" type="mms"></apn>
+ <apn mcc="639" mnc="3" carrier="Airtel mms" apn="mms" mmsc="http://mms.ke.airtel.com:8002" mmsproxy="172.30.9.8" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="639" mnc="05" carrier="Yu Internet" apn="Internet" mmsc="http://10.4.16.22/servlets/mms" mmsproxy="10.4.16.6" mmsport="8080" type="default,supl,mms" proxy="10.4.16.6" port="8080"></apn>
+ <apn mcc="639" mnc="05" carrier="Yu mms" apn="Yu" mmsc="http://10.4.16.22/servlets/mms" mmsproxy="10.4.16.6" mmsport="8080" type="mms"></apn>
+ <apn mcc="639" mnc="5" carrier="Yu Internet" apn="Internet" mmsc="http://10.4.16.22/servlets/mms" mmsproxy="10.4.16.6" mmsport="8080" type="default,supl,mms" proxy="10.4.16.6" port="8080"></apn>
+ <apn mcc="639" mnc="5" carrier="Yu mms" apn="Yu" mmsc="http://10.4.16.22/servlets/mms" mmsproxy="10.4.16.6" mmsport="8080" type="mms"></apn>
+ <apn mcc="639" mnc="07" carrier="Orange MMS" apn="mms.orange.co.ke" mmsc="http://10.36.16.5/servlets/mms" mmsproxy="10.36.17.130" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="639" mnc="7" carrier="Orange KE MMS" apn="mms.orange.co.ke" mmsc="http://10.36.16.5/servlets/mms" mmsproxy="10.36.16.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="640" mnc="04" carrier="Vodacom MMS" apn="mms" mmsc="http://10.154.0.12/mms/" type="mms"></apn>
+ <apn mcc="640" mnc="4" carrier="Vodacom MMS" apn="mms" mmsc="http://10.154.0.12/mms/" type="mms"></apn>
+ <apn mcc="641" mnc="14" carrier="Orange MMS" apn="orangemms" mmsc="http://mms/" type="mms"></apn>
+ <apn mcc="641" mnc="14" carrier="Orange MMS" apn="orangemms" mmsc="http://mms/" type="mms" authtype="0"></apn>
+ <apn mcc="646" mnc="02" carrier="Orange MG MMS" apn="orangemms" mmsc="http://10.152.10.70.38090" mmsproxy="10.150.0.115" mmsport="8080" type="mms" user="mms" password="orange"></apn>
+ <apn mcc="646" mnc="2" carrier="Orange MG MMS" apn="orangemms" mmsc="http://10.152.10.70.38090" mmsproxy="10.150.0.115" mmsport="8080" type="mms" user="mms" password="orange" authtype="3"></apn>
+ <apn mcc="646" mnc="2" carrier="Orange MG MMS" apn="orangemms" mmsc="http://10.152.10.70:38090" mmsproxy="10.150.0.115" mmsport="8080" type="mms" user="mms" password="orange" authtype="0"></apn>
+ <apn mcc="647" mnc="00" carrier="Orange MMS Réunion" apn="orangerun.acte" mmsc="http://mms.orange.re" mmsproxy="192.168.10.200" mmsport="8080" type="mms" user="orange" password="orange"></apn>
+ <apn mcc="647" mnc="02" carrier="OnlyMMS" apn="onlymms" mmsc="http://10.4.85.50:8514" mmsproxy="10.4.85.50" mmsport="8080" type="mms" user="only" password="only" authtype="1"></apn>
+ <apn mcc="647" mnc="10" carrier="Coriolis MMS" apn="mmscoriolis" mmsc="http://mmscoriolis" mmsproxy="10.143.156.6" mmsport="8080" type="mms" mvno_match_data="12" mvno_type="gid"></apn>
+ <apn mcc="647" mnc="10" carrier="MMS" apn="mmssfr" mmsc="http://mms" mmsproxy="10.0.224.145" mmsport="8080" type="mms" user="mms" password="mms"></apn>
+ <apn mcc="647" mnc="10" carrier="MMS" apn="mmssfr" mmsc="http://mms" mmsproxy="10.0.224.145" mmsport="8080" type="mms" user="mms" password="mms" authtype="3"></apn>
+ <apn mcc="647" mnc="10" carrier="SRR MMS" apn="mmssfr" mmsc="http://mms" mmsproxy="10.0.224.145" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="651" mnc="01" carrier="VCL MMS GPRS" apn="mms" mmsc="http://mmsc.vodacom4me.co.ls" mmsproxy="10.113.63.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="651" mnc="1" carrier="VCL MMS GPRS" apn="mms" mmsc="http://mmsc.vodacom4me.co.ls" mmsproxy="10.113.63.11" mmsport="8080" type="mms"></apn>
+ <apn mcc="652" mnc="02" carrier="Orange BW MMS" apn="mms.orange.co.bw" mmsc="http://10.0.0.242/servlets/mms" mmsproxy="10.0.0.226" mmsport="8080" type="mms"></apn>
+ <apn mcc="652" mnc="2" carrier="Orange BW MMS" apn="mms.orange.co.bw" mmsc="http://10.0.0.242/servlets/mms" mmsproxy="10.0.0.226" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="01" carrier="MMS.Vodacom" apn="lte.vodacom.za" mmsc="http://mmsc.vodacom4me.co.za" mmsproxy="196.6.128.13" mmsport="8080" type="mms"></apn>
+ <apn mcc="655" mnc="01" carrier="MMS.Vodacom" apn="mms.vodacom.net" mmsc="http://mmsc.vodacom4me.co.za" mmsproxy="196.6.128.13" mmsport="8080" type="mms"></apn>
+ <apn mcc="655" mnc="1" carrier="MMS.Vodacom" apn="lte.vodacom.za" mmsc="http://mmsc.vodacom4me.co.za" mmsproxy="196.6.128.13" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="1" carrier="MMS.Vodacom" apn="mms.vodacom.net" mmsc="http://mmsc.vodacom4me.co.za" mmsproxy="196.6.128.13" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="1" carrier="Vlive! MMS" apn="mms.vodacom.net" mmsc="http://mmsc.vodacom4me.co.za" mmsproxy="196.6.128.13" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="02" carrier="Telkom Mobile MMS" apn="mms" mmsc="http://mms.8ta.com:38090/was" mmsproxy="41.151.254.162" mmsport="8080" type="mms"></apn>
+ <apn mcc="655" mnc="2" carrier="8.ta MMS" apn="mms" mmsc="http://mms.8ta.com:38090/was" mmsproxy="41.151.254.162" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="2" carrier="Telkom Mobile MMS" apn="mms" mmsc="http://mms.8ta.com:38090/was" mmsproxy="41.151.254.162" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="07" carrier="Cell C MMS" apn="mms" mmsc="http://mms.cmobile.co.za/" mmsproxy="196.31.116.250" mmsport="8080" type="mms"></apn>
+ <apn mcc="655" mnc="07" carrier="Redbull MMS" apn="mms" mmsc="http://mms.cmobile.co.za/" mmsproxy="196.31.116.250" mmsport="8080" type="mms" mvno_match_data="6550713" mvno_type="imsi"></apn>
+ <apn mcc="655" mnc="07" carrier="Virgin_MMS_1" apn="vmms" mmsc="http://mms.virginmobile.co.za" mmsproxy="196.31.116.242" mmsport="8080" type="mms" mvno_match_data="6550710" mvno_type="imsi"></apn>
+ <apn mcc="655" mnc="07" carrier="Virgin_MMS_2" apn="vmms" mmsc="http://mms.virginmobile.co.za" mmsproxy="196.31.116.242" mmsport="9201" type="mms" mvno_match_data="6550710" mvno_type="imsi"></apn>
+ <apn mcc="655" mnc="7" carrier="Cell C MMS" apn="mms" mmsc="http://mms.cmobile.co.za/" mmsproxy="196.31.116.250" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="7" carrier="Cell C mms" apn="mms" mmsc="http://mms.cmobile.co.za" mmsproxy="196.31.116.250" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="655" mnc="7" carrier="Virgin MMS" apn="vmms" mmsc="http://mms.virginmobile.co.za" mmsproxy="196.31.116.242" mmsport="8080" type="mms" authtype="1" mvno_match_data="Virgin" mvno_type="spn"></apn>
+ <apn mcc="655" mnc="7" carrier="Virgin_MMS_1" apn="vmms" mmsc="http://mms.virginmobile.co.za" mmsproxy="196.31.116.242" mmsport="8080" type="mms" authtype="0" mvno_match_data="6550710" mvno_type="imsi"></apn>
+ <apn mcc="655" mnc="7" carrier="Virgin_MMS_2" apn="vmms" mmsc="http://mms.virginmobile.co.za" mmsproxy="196.31.116.242" mmsport="9201" type="mms" authtype="0" mvno_match_data="6550710" mvno_type="imsi"></apn>
+ <apn mcc="655" mnc="10" carrier="MTN MMS" apn="myMTN" mmsc="http://mms.mtn.co.za/mms/wapenc" mmsproxy="196.11.240.241" mmsport="8080" type="mms" user="mtnmms" password="mtnmms" authtype="0"></apn>
+ <apn mcc="655" mnc="10" carrier="MTN MMS" apn="myMTN" mmsc="http://mms.mtn.co.za/mms/wapenc" mmsproxy="196.11.240.241" mmsport="8080" type="mms" user="mtnmms" password="mtnmms" authtype="1"></apn>
+ <apn mcc="704" mnc="01" carrier="MMS CLARO" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms"></apn>
+ <apn mcc="704" mnc="1" carrier="Guatemala:Claro:MMS" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="704" mnc="1" carrier="MMS CLARO" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="704" mnc="02" carrier="MMS TIGO" apn="mms.tigo.gt" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms"></apn>
+ <apn mcc="704" mnc="2" carrier="Guatemala:Tigo:Mms" apn="mms.tigo.gt" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="1"></apn>
+ <apn mcc="704" mnc="2" carrier="MMS TIGO" apn="mms.tigo.gt" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="0"></apn>
+ <apn mcc="704" mnc="03" carrier="Movistar MMS" apn="mms.movistar.gt" mmsc="http://mms.movistar.gt" mmsproxy="10.12.22.1" mmsport="80" type="mms" user="movistargt" password="movistargt" authtype="1"></apn>
+ <apn mcc="704" mnc="3" carrier="Guatemala:Movistar:MMS" apn="mms.movistar.gt" mmsc="http://mms.movistar.gt" mmsproxy="10.12.22.1" mmsport="80" type="mms" user="movistargt" password="movistargt" authtype="1"></apn>
+ <apn mcc="704" mnc="3" carrier="Movistar MMS" apn="mms.movistar.gt" mmsc="http://mms.movistar.gt" mmsproxy="10.12.22.1" mmsport="80" type="mms" user="movistargt" password="movistargt" authtype="1"></apn>
+ <apn mcc="704" mnc="030" carrier="Movistar MMS" apn="mms.movistar.gt" mmsc="http://mms.movistar.gt" mmsproxy="10.12.22.1" mmsport="80" type="mms" user="movistargt" password="movistargt" authtype="1"></apn>
+ <apn mcc="706" mnc="01" carrier="MMS CLARO" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms"></apn>
+ <apn mcc="706" mnc="1" carrier="El Salvador:Claro:MMS" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="706" mnc="1" carrier="MMS CLARO" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="706" mnc="02" carrier="MMS" apn="wap.digicelsv.com" mmsc="http://mmc.digiceljamaica.com/servlets/mms" mmsproxy="172.26.5.12" mmsport="8080" type="mms"></apn>
+ <apn mcc="706" mnc="2" carrier="El Salvador:Digicel:Mms" apn="wap.digicelsv.com" mmsc="http://172.26.5.132/servlets/mms" mmsproxy="172.26.5.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="706" mnc="2" carrier="MMS" apn="wap.digicelsv.com" mmsc="http://mmc.digiceljamaica.com/servlets/mms" mmsproxy="172.26.5.12" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="706" mnc="03" carrier="MMS Tigo" apn="mms.tigo.sv" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms"></apn>
+ <apn mcc="706" mnc="3" carrier="El Salvador:Tigo:Mms" apn="mms.tigo.sv" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="1"></apn>
+ <apn mcc="706" mnc="3" carrier="MMS Tigo" apn="mms.tigo.sv" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="0"></apn>
+ <apn mcc="706" mnc="04" carrier="Movistar MMS" apn="mms.movistar.sv" mmsc="http://mms.movistar.sv" mmsproxy="10.12.20.1" mmsport="80" type="mms" user="movistarsv" password="movistarsv" authtype="1"></apn>
+ <apn mcc="706" mnc="4" carrier="El Salvador:Movistar:MMS" apn="mms.movistar.sv" mmsc="http://mms.movistar.sv" mmsproxy="10.12.20.1" mmsport="80" type="mms" user="movistarsv" password="movistarsv" authtype="1"></apn>
+ <apn mcc="706" mnc="4" carrier="Movistar MMS" apn="mms.movistar.sv" mmsc="http://mms.movistar.sv" mmsproxy="10.12.20.1" mmsport="80" type="mms" user="movistarsv" password="movistarsv" authtype="1"></apn>
+ <apn mcc="706" mnc="040" carrier="Movistar MMS" apn="mms.movistar.sv" mmsc="http://mms.movistar.sv" mmsproxy="10.12.20.1" mmsport="80" type="mms" user="movistarsv" password="movistarsv" authtype="1"></apn>
+ <apn mcc="708" mnc="00" carrier="MMS Claro" apn="mms.ideasclaro" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="708" mnc="001" carrier="MMS Claro" apn="mms.ideasclaro" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms"></apn>
+ <apn mcc="708" mnc="1" carrier="Honduras:Claro:MMS" apn="mms.megatel.hn" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" user="mmsmegatel" password="mmsmegatel" authtype="1"></apn>
+ <apn mcc="708" mnc="1" carrier="MMS Claro" apn="mms.ideasclaro" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="708" mnc="02" carrier="MMS TIGO" apn="mms.tigo.hn" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms"></apn>
+ <apn mcc="708" mnc="2" carrier="Honduras:Tigo:Mms" apn="mms.tigo.hn" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="1"></apn>
+ <apn mcc="708" mnc="2" carrier="MMS TIGO" apn="mms.tigo.hn" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="0"></apn>
+ <apn mcc="708" mnc="04" carrier="Honduras:Digicel:Mms" apn="wap.digicelhn.com" mmsc="http://mms.digicelsv.com/servlets/mms" mmsproxy="172.26.5.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="708" mnc="04" carrier="Honduras:Digicel:Modem" apn="wap.digicelhn.com" mmsc="http://www.digicelive.com" type="dun" authtype="1" proxy="172.26.5.12" port="8080"></apn>
+ <apn mcc="708" mnc="4" carrier="Honduras:Digicel:Mms" apn="wap.digicelhn.com" mmsc="http://mms.digicelsv.com/servlets/mms" mmsproxy="172.26.5.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="708" mnc="4" carrier="Honduras:Digicel:Modem" apn="wap.digicelhn.com" mmsc="http://www.digicelive.com" type="dun" authtype="1" proxy="172.26.5.12" port="8080"></apn>
+ <apn mcc="708" mnc="020" carrier="MMS TIGO" apn="mms.tigo.hn" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms"></apn>
+ <apn mcc="708" mnc="20" carrier="Honduras:Tigo: Mms" apn="mms.tigo.hn" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="1"></apn>
+ <apn mcc="708" mnc="20" carrier="MMS TIGO" apn="mms.tigo.hn" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" authtype="0"></apn>
+ <apn mcc="708" mnc="040" carrier="Honduras:Digicel:Mms:2" apn="wap.digicelhn.com" mmsc="http://mms.digicelsv.com/servlets/mms" mmsproxy="172.26.5.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="708" mnc="40" carrier="Honduras:Digicel:Mms:2" apn="wap.digicelhn.com" mmsc="http://mms.digicelsv.com/servlets/mms" mmsproxy="172.26.5.12" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="710" mnc="21" carrier="Enitel MMS" apn="mms.indeasalo.ni" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="710" mnc="21" carrier="Nicaragua:Enitel:MMS" apn="mms.emovil" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" user="mmsemovil" password="mmsemovil" authtype="1"></apn>
+ <apn mcc="710" mnc="30" carrier="Movistar MMS" apn="mms.movistar.ni" mmsc="http://mms.movistar.ni" mmsproxy="10.12.23.1" mmsport="80" type="mms" user="movistarni" password="movistarni" authtype="1"></apn>
+ <apn mcc="710" mnc="73" carrier="Enitel MMS" apn="mms.indeasalo.ni" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="710" mnc="73" carrier="Nicaragua:Alo:MMS" apn="mms.ideasalo.ni" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="710" mnc="300" carrier="Movistar MMS" apn="mms.movistar.ni" mmsc="http://mms.movistar.ni" mmsproxy="10.12.23.1" mmsport="80" type="mms" user="movistarni" password="movistarni" authtype="1"></apn>
+ <apn mcc="710" mnc="300" carrier="Nicaragua:Movistar:MMS" apn="mms.movistar.ni" mmsc="http://mms.movistar.ni" mmsproxy="10.12.23.1" mmsport="80" type="mms" user="movistarni" password="movistarni" authtype="1"></apn>
+ <apn mcc="710" mnc="730" carrier="Enitel MMS" apn="mms.indeasalo.ni" mmsc="http://10.6.32.27/servlets/mms" mmsproxy="10.6.32.2" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="712" mnc="01" carrier="Costar Rica:Kolbi:Modem" apn="kolbi" mmsc="http://mimundokolbi.ice.cr" mmsport="8080" type="dun" authtype="1"></apn>
+ <apn mcc="712" mnc="01" carrier="Kolbi Multimedia" apn="kolbimundo" mmsc="http://mmsice" mmsproxy="10.184.202.24" mmsport="8080" type="mms"></apn>
+ <apn mcc="712" mnc="01" carrier="Kolbi_Multimedia" apn="kolbimundo" mmsc="http://mmsice" mmsproxy="10.184.202.24" mmsport="8080" type="mms"></apn>
+ <apn mcc="712" mnc="1" carrier="Costar Rica:Kolbi:Internet" apn="kolbi" mmsc="http://mimundokolbi.ice.cr" mmsport="8080" type="default" authtype="1"></apn>
+ <apn mcc="712" mnc="1" carrier="Costar Rica:Kolbi:MMS" apn="mmscelular" mmsc="http://mmsice" mmsproxy="172.27.10.4" mmsport="8080" type="mms" authtype="1" port="8080"></apn>
+ <apn mcc="712" mnc="1" carrier="Kolbi_Multimedia" apn="kolbimundo" mmsc="http://mmsice" mmsproxy="10.184.202.24" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="712" mnc="02" carrier="Kolbi Multimedia" apn="kolbimundo" mmsc="http://mmsice" mmsproxy="10.184.202.24" mmsport="8080" type="mms"></apn>
+ <apn mcc="712" mnc="02" carrier="Kolbi_Multimedia" apn="kolbimundo" mmsc="http://mmsice" mmsproxy="10.184.202.24" mmsport="8080" type="mms"></apn>
+ <apn mcc="712" mnc="2" carrier="Kolbi_Multimedia" apn="kolbimundo" mmsc="http://mmsice" mmsproxy="10.184.202.24" mmsport="8080" type="mms"></apn>
+ <apn mcc="712" mnc="03" carrier="MMS CLARO CR" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms"></apn>
+ <apn mcc="712" mnc="3" carrier="Costa Rica:Claro:Internet" apn="internet.ideasclaro" mmsc="http://wap.ideasclaro.com" type="default" authtype="1" port="8080"></apn>
+ <apn mcc="712" mnc="3" carrier="Costa Rica:Claro:MMS" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="712" mnc="3" carrier="MMS CLARO CR" apn="mms.ideasclaro" mmsc="http://mms.ideasclaro.com:8002" mmsproxy="216.230.133.66" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="712" mnc="04" carrier="Movistar MMS" apn="mms.movistar.cr" mmsc="http://mms.movistar.cr" mmsproxy="10.221.79.83" mmsport="80" type="mms" user="movistarcr" password="movistarcr" authtype="1"></apn>
+ <apn mcc="712" mnc="4" carrier="Costa Rica:Movistar:Internet" apn="internet.movistar.cr" mmsc="http://www.movistar.co.cr" type="default" user="movistarcr" password="movistarcr" authtype="1"></apn>
+ <apn mcc="712" mnc="4" carrier="Costa Rica:Movistar:MMS" apn="mms.movistar.cr" mmsc="http://mms.movistar.cr" mmsproxy="10.221.79.83" mmsport="80" type="mms" user="movistarcr" password="movistarcr" authtype="1"></apn>
+ <apn mcc="712" mnc="4" carrier="Movistar MMS" apn="mms.movistar.cr" mmsc="http://mms.movistar.cr" mmsproxy="10.221.79.83" mmsport="80" type="mms" user="movistarcr" password="movistarcr" authtype="1"></apn>
+ <apn mcc="712" mnc="019" carrier="MMS Tuyo" apn="tm7mms" mmsc="http://mmsc.tuyomovil.com:1981" mmsproxy="10.186.181.5" mmsport="3128" type="mms"></apn>
+ <apn mcc="712" mnc="19" carrier="MMS Tuyo" apn="tm7mms" mmsc="http://mmsc.tuyomovil.com:1981" mmsproxy="10.186.181.5" mmsport="3128" type="mms"></apn>
+ <apn mcc="712" mnc="190" carrier="MMS Tuyo" apn="tm7mms" mmsc="http://mmsc.tuyomovil.com:1981" mmsproxy="10.186.181.5" mmsport="3128" type="mms"></apn>
+ <apn mcc="714" mnc="01" carrier="MMS" apn="apn02.cwpanama.com.pa" mmsc="http://mms.zonamovil.com.pa" mmsproxy="172.25.3.5" mmsport="8080" type="mms"></apn>
+ <apn mcc="714" mnc="1" carrier="MMS" apn="apn02.cwpanama.com.pa" mmsc="http://mms.zonamovil.com.pa" mmsproxy="172.25.3.5" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="714" mnc="1" carrier="Panama:CW:Internet" apn="apn01.cwpanama.com.pa" mmsc="http://wap.masmovil.com.pa" type="default" authtype="1"></apn>
+ <apn mcc="714" mnc="1" carrier="Panama:CW:MMS" apn="apn02.cwpanama.com.pa" mmsc="http://mms.zonamovil.com.pa" mmsproxy="172.25.3.5" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="714" mnc="02" carrier="Movistar MMS" apn="mms.movistar.pa" mmsc="http://mms.movistar.pa" mmsproxy="10.12.21.1" mmsport="80" type="mms" user="movistarpamms" password="movistarpa" authtype="1"></apn>
+ <apn mcc="714" mnc="2" carrier="Movistar MMS" apn="mms.movistar.pa" mmsc="http://mms.movistar.pa" mmsproxy="10.12.21.1" mmsport="80" type="mms" user="movistarpamms" password="movistarpa" authtype="1"></apn>
+ <apn mcc="714" mnc="03" carrier="MMS Claro" apn="mms.claro.com.pa" mmsc="http://www.claro.com.pa/mms/" mmsproxy="10.240.3.129" mmsport="8799" type="mms" user="CLAROMMS" password="CLAROMMS" authtype="1"></apn>
+ <apn mcc="714" mnc="3" carrier="MMS Claro" apn="mms.claro.com.pa" mmsc="http://www.claro.com.pa/mms/" mmsproxy="10.240.3.129" mmsport="8799" type="mms" user="CLAROMMS" password="CLAROMMS" authtype="1"></apn>
+ <apn mcc="714" mnc="3" carrier="Panama:Claro:MMS" apn="mms.claro.com.pa" mmsc="http://www.claro.com.pa/mms/" mmsproxy="10.240.3.129" mmsport="8799" type="mms" user="CLAROMMS" password="CLAROMMS" authtype="1"></apn>
+ <apn mcc="714" mnc="04" carrier="MMS" apn="wap.digicelpanama.com" mmsc="http://mmc.digicelpanama.com/servlets/mms" mmsproxy="172.27.99.99" mmsport="8080" type="mms"></apn>
+ <apn mcc="714" mnc="4" carrier="MMS" apn="wap.digicelpanama.com" mmsc="http://mmc.digicelpanama.com/servlets/mms" mmsproxy="172.27.99.99" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="714" mnc="4" carrier="Panama:Digicel:Mms" apn="wap.digicelpanama.com" mmsc="http://mmc.digicelpanama.com/servlets/mms" mmsproxy="172.27.99.99" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="714" mnc="020" carrier="Movistar MMS" apn="mms.movistar.pa" mmsc="http://mms.movistar.pa" mmsproxy="10.12.21.1" mmsport="80" type="mms" user="movistarpamms" password="movistarpa" authtype="1"></apn>
+ <apn mcc="714" mnc="20" carrier="Movistar MMS" apn="mms.movistar.pa" mmsc="http://mms.movistar.pa" mmsproxy="10.12.21.1" mmsport="80" type="mms" user="movistarpamms" password="movistarpa" authtype="1"></apn>
+ <apn mcc="714" mnc="20" carrier="Panama:Movistar:MMS" apn="mms.movistar.pa" mmsc="http://mms.movistar.pa" mmsproxy="10.12.21.1" mmsport="80" type="mms" user="movistarpamms" password="movistarpa" authtype="1"></apn>
+ <apn mcc="714" mnc="040" carrier="Panama:Digicel:Mms:2" apn="wap.digicelpanama.com" mmsc="http://mmc.digicelpanama.com/servlets/mms" mmsproxy="172.27.99.99" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="714" mnc="40" carrier="Panama:Digicel:Mms:2" apn="wap.digicelpanama.com" mmsc="http://mmc.digicelpanama.com/servlets/mms" mmsproxy="172.27.99.99" mmsport="9201" type="mms" authtype="1"></apn>
+ <apn mcc="716" mnc="06" carrier="Movistar MMS" apn="mms.movistar.pe" mmsc="http://mmsc.telefonicamovistar.com.pe:8088/mms/" mmsproxy="200.4.196.118" mmsport="8080" type="mms" user="movistar@mms" password="movistar" authtype="1"></apn>
+ <apn mcc="716" mnc="6" carrier="Movistar MMS" apn="mms.movistar.pe" mmsc="http://mmsc.telefonicamovistar.com.pe:8088/mms/" mmsproxy="200.4.196.118" mmsport="8080" type="mms" user="movistar@mms" password="movistar" authtype="1"></apn>
+ <apn mcc="716" mnc="10" carrier="CLARO MMS" apn="mms.claro.pe" mmsc="http://claro/servlets/mms" mmsproxy="192.168.231.30" mmsport="80" type="mms" user="claro" password="claro" authtype="1"></apn>
+ <apn mcc="716" mnc="15" carrier="Bitel - MMS" apn="bitel-mms" mmsc="http://181.176.241.99:8080" mmsproxy="10.121.144.3" mmsport="8000" type="mms" authtype="1" protocol="IP" roaming_protocol="IP"></apn>
+ <apn mcc="716" mnc="17" carrier="Entel MMS" apn="mms.entel.pe" mmsc="http://mms.entel.pe" mmsproxy="10.0.215.74" mmsport="8080" type="mms" authtype="0" protocol="IP"></apn>
+ <apn mcc="716" mnc="17" carrier="Nextel MMS" apn="mms.nextel.com.pe" mmsc="http://mms.nextel.pe" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="722" mnc="01" carrier="Quam_MMS" apn="mms.movil" mmsc="http://mms.quam.com.ar" mmsproxy="200.68.32.239" mmsport="9090" type="mms" user="mms" password="mms" authtype="1" mvno_match_data="CELULAR" mvno_type="spn"></apn>
+ <apn mcc="722" mnc="01" carrier="Quam_MMS" apn="mms.movil" mmsc="http://mms.quam.com.ar" mmsproxy="200.68.32.239" mmsport="9090" type="mms" user="mms" password="mms" authtype="1" mvno_match_data="QUAM" mvno_type="spn"></apn>
+ <apn mcc="722" mnc="07" carrier="Movistar MMS" apn="mms.gprs.unifon.com.ar" mmsc="http://mms.movistar.com.ar" mmsproxy="200.68.32.239" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="722" mnc="7" carrier="Argentina:Movistar:MMS" apn="mms.gprs.unifon.com.ar" mmsc="http://mms.movistar.com.ar" mmsproxy="200.68.32.239" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="722" mnc="31" carrier="Claro AR" apn="igprs.claro.com.ar" mmsc="http://mms.claro.com.ar" type="default,supl,dun,mms"></apn>
+ <apn mcc="722" mnc="31" carrier="Claro AR" apn="igprs.claro.com.ar" mmsc="http://mms.claro.com.ar" type="default,supl,mms"></apn>
+ <apn mcc="722" mnc="34" carrier="Argentina:Personal:MMS" apn="mms" mmsc="http://mms.personal.com" mmsproxy="172.25.7.31" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="722" mnc="34" carrier="Personal MMS" apn="mms" mmsc="http://mms.personal.com" mmsproxy="172.25.7.31" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="722" mnc="36" carrier="Argentina:Nuestro:MMS" apn="mms.nuestro.com.ar" mmsc="http://mms.nuestro.com.ar" mmsproxy="172.16.0.20" mmsport="8080" type="mms" user="mms" authtype="0"></apn>
+ <apn mcc="722" mnc="310" carrier="Argentina:Claro:Internet Claro AR" apn="igprs.claro.com.ar" mmsc="http://mms.claro.com.ar" type="default,mms" authtype="0"></apn>
+ <apn mcc="722" mnc="310" carrier="Claro AR" apn="igprs.claro.com.ar" mmsc="http://mms.claro.com.ar" type="default,supl,dun,mms"></apn>
+ <apn mcc="722" mnc="310" carrier="Claro AR" apn="igprs.claro.com.ar" mmsc="http://mms.claro.com.ar" type="default,supl,dun,mms" authtype="0"></apn>
+ <apn mcc="722" mnc="310" carrier="Claro AR" apn="igprs.claro.com.ar" mmsc="http://mms.claro.com.ar" type="default,supl,mms"></apn>
+ <apn mcc="722" mnc="340" carrier="Argentina:Personal :MMS" apn="mms" mmsc="http://mms.personal.com" mmsproxy="172.25.7.31" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="722" mnc="341" carrier="Argentina:Personal: MMS" apn="mms" mmsc="http://mms.personal.com" mmsproxy="172.25.7.31" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="722" mnc="341" carrier="Personal MMS" apn="mms" mmsc="http://mms.personal.com" mmsproxy="172.25.7.31" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="724" mnc="01" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="02" carrier="TIM Connect" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,supl,mms" user="tim" password="tim" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="2" carrier="TIM CONNECT" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,dun,mms" user="tim" password="tim" authtype="1"></apn>
+ <apn mcc="724" mnc="2" carrier="TIM Connect" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,supl,mms" user="tim" password="tim" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="03" carrier="TIM Connect" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,supl,mms" user="tim" password="tim" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="3" carrier="TIM CONNECT" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,dun,mms" user="tim" password="tim" authtype="1"></apn>
+ <apn mcc="724" mnc="3" carrier="TIM Connect" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,supl,mms" user="tim" password="tim" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="04" carrier="TIM Connect" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,supl,mms" user="tim" password="tim" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="4" carrier="TIM CONNECT" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,dun,mms" user="tim" password="tim" authtype="1"></apn>
+ <apn mcc="724" mnc="4" carrier="TIM Connect" apn="timbrasil.br" mmsc="http://mms.tim.br" mmsproxy="200.179.66.242" mmsport="8080" type="default,supl,mms" user="tim" password="tim" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="05" carrier="Claro Foto" apn="mms.claro.com.br" mmsc="http://mms.claro.com.br" mmsproxy="200.169.126.10" mmsport="8799" type="mms" user="claro" password="claro" authtype="1"></apn>
+ <apn mcc="724" mnc="5" carrier="Claro Foto" apn="mms.claro.com.br" mmsc="http://mms.claro.com.br" mmsproxy="200.169.126.10" mmsport="8799" type="mms" user="claro" password="claro" authtype="1"></apn>
+ <apn mcc="724" mnc="06" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="6" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="6" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="07" carrier="SCTL MMS" apn="mms.sercomtel.com.br" mmsc="http://mms.claro.com.br" mmsproxy="200.169.126.10" mmsport="8799" type="mms" user="sercomtel" password="sercomtel"></apn>
+ <apn mcc="724" mnc="10" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="10" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="11" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="11" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="015" carrier="Sercomtel" apn="mms.sercomtel.com.br" mmsc="http://mms.claro.com.br" mmsproxy="200.169.126.10" mmsport="8799" type="mms" authtype="1"></apn>
+ <apn mcc="724" mnc="15" carrier="Sercomtel:MMS" apn="mms.sercomtel.com.br" mmsc="http://mms.claro.com.br" mmsproxy="200.169.126.10" mmsport="8799" type="mms" user="sercomtel" password="sercomtel" authtype="1"></apn>
+ <apn mcc="724" mnc="16" carrier="MMS GPRS" apn="mmsgprs.oi.com.br" mmsc="http://200.222.42.204:8002" mmsproxy="192.168.10.50" mmsport="3128" type="mms" user="oimms" password="oioioi" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="16" carrier="Oi:MMS:3" apn="mmsgprs.oi.com.br" mmsc="http://200.222.42.204:8002" mmsproxy="192.168.10.50" mmsport="3128" type="mms" user="oimms" password="oioioi" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="19" carrier="MMS Telemig" apn="mmsgprs.telemigcelular.com.br" mmsc="http://mms.telemigcelular.com.br" mmsproxy="200.192.230.142" mmsport="8080" type="mms" user="celular" password="celular"></apn>
+ <apn mcc="724" mnc="19" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="23" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="23" carrier="Vivo MMS" apn="mms.vivo.com.br" mmsc="http://termnat.vivomms.com.br:8088/mms" mmsproxy="200.142.130.104" mmsport="80" type="mms" user="vivo" password="vivo" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="24" carrier="Oi:MMS:2" apn="mmsgprs.oi.com.br" mmsc="http://200.222.42.204:8002" mmsproxy="192.168.10.50" mmsport="3128" type="mms" user="oimms" password="oioioi" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="31" carrier="MMS GPRS" apn="mmsgprs.oi.com.br" mmsc="http://200.222.42.204:8002" mmsproxy="192.168.10.50" mmsport="3128" type="mms" user="oimms" password="oioioi" authtype="1" protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="31" carrier="Oi:MMS:1" apn="mmsgprs.oi.com.br" mmsc="http://200.222.42.204:8002" mmsproxy="192.168.10.50" mmsport="3128" type="mms" user="oimms" password="oioioi" authtype="1" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="724" mnc="032" carrier="CTBC" apn="mms.ctbc.br" mmsc="http://mms.ctbccelular.com.br/was" mmsproxy="172.29.7.70" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="724" mnc="32" carrier="CTBC:MMS:1" apn="mms.ctbc.br" mmsc="http://mms.ctbccelular.com.br/was" mmsproxy="172.29.7.70" mmsport="8080" type="mms" user="CTBC" password="1212" authtype="1"></apn>
+ <apn mcc="724" mnc="033" carrier="CTBC" apn="mms.ctbc.br" mmsc="http://mms.ctbccelular.com.br/was" mmsproxy="172.29.7.70" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="724" mnc="33" carrier="CTBC:MMS:2" apn="mms.ctbc.br" mmsc="http://mms.ctbccelular.com.br/was" mmsproxy="172.29.7.70" mmsport="8080" type="mms" user="CTBC" password="1212" authtype="1"></apn>
+ <apn mcc="724" mnc="034" carrier="CTBC" apn="mms.ctbc.br" mmsc="http://mms.ctbccelular.com.br/was" mmsproxy="172.29.7.70" mmsport="8080" type="default,supl,mms" authtype="1"></apn>
+ <apn mcc="724" mnc="34" carrier="CTBC:MMS:3" apn="mms.ctbc.br" mmsc="http://mms.ctbccelular.com.br/was" mmsproxy="172.29.7.70" mmsport="8080" type="mms" user="CTBC" password="1212" authtype="1"></apn>
+ <apn mcc="724" mnc="039" carrier="Nextel" apn="mms.nextel3g.net.br" mmsc="http://3gmms.nextel3g.net.br" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="724" mnc="39" carrier="Nextel MMS" apn="mms.nextel3g.net.br" mmsc="http://3gmms.nextel3g.net.br" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="724" mnc="39" carrier="Nextel MMS" apn="mms.nextel3g.net.br" mmsc="http://3gmms.nextel3g.net.br" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0" protocol="IPV4V6" roaming_protocol="IPV4V6"></apn>
+ <apn mcc="730" mnc="01" carrier="MMS Entel" apn="mms.entelpcs.cl" mmsc="http://mmsc.entelpcs.cl" mmsproxy="10.99.0.10" mmsport="8080" type="mms" user="entelpcs" password="entelpcs" authtype="1"></apn>
+ <apn mcc="730" mnc="1" carrier="Chile:Entel:MMS Entel PCS:1" apn="mms.entelpcs.cl" mmsc="http://mmsc.entelpcs.cl" mmsproxy="10.99.0.10" mmsport="8080" type="mms" user="entelpcs" password="entelpcs" authtype="1"></apn>
+ <apn mcc="730" mnc="1" carrier="MMS Entel" apn="mms.entelpcs.cl" mmsc="http://mmsc.entelpcs.cl" mmsproxy="10.99.0.10" mmsport="8080" type="mms" user="entelpcs" password="entelpcs" authtype="1"></apn>
+ <apn mcc="730" mnc="02" carrier="Movistar MMS" apn="mms.tmovil.cl" mmsc="http://mms.movistar.cl" mmsproxy="172.17.8.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="730" mnc="2" carrier="Movistar MMS" apn="mms.tmovil.cl" mmsc="http://mms.movistar.cl" mmsproxy="172.17.8.10" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="730" mnc="03" carrier="MMS Claro" apn="mms.clarochile.cl" mmsc="http://mms.clarochile.cl" mmsproxy="172.23.200.200" mmsport="8080" type="mms" user="clarochile" password="clarochile" authtype="1"></apn>
+ <apn mcc="730" mnc="3" carrier="Chile:Claro:MMS" apn="mms.clarochile.cl" mmsc="http://mms.clarochile.cl" mmsproxy="172.23.200.200" mmsport="8080" type="mms" user="clarochile" password="clarochile" authtype="1"></apn>
+ <apn mcc="730" mnc="3" carrier="MMS Claro" apn="mms.clarochile.cl" mmsc="http://mms.clarochile.cl" mmsproxy="172.23.200.200" mmsport="8080" type="mms" user="clarochile" password="clarochile" authtype="1"></apn>
+ <apn mcc="730" mnc="08" carrier="MMS" apn="mms.vtr.com" mmsc="http://192.168.94.162:19090/was" mmsproxy="192.168.94.210" mmsport="9028" type="mms" user="mms" authtype="0"></apn>
+ <apn mcc="730" mnc="8" carrier="MMS" apn="mms.vtr.com" mmsc="http://192.168.94.162:19090/was" mmsproxy="192.168.94.210" mmsport="9028" type="mms" user="mms" authtype="0"></apn>
+ <apn mcc="730" mnc="09" carrier="MMS Nextel" apn="mms.nextelmovil.cl" mmsc="http://3gmms.nextelmovil.cl" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="730" mnc="9" carrier="Nextel MMS" apn="mms.nextelmovil.cl" mmsc="http://3gmms.nextelmovil.cl" mmsproxy="129.192.129.104" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="730" mnc="10" carrier="Chile:Entel:MMS Entel PCS:2" apn="mms.entelpcs.cl" mmsc="http://mmsc.entelpcs.cl" mmsproxy="10.99.0.10" mmsport="8080" type="mms" user="entelpcs" password="entelpcs" authtype="1"></apn>
+ <apn mcc="730" mnc="10" carrier="MMS Entel" apn="mms.entelpcs.cl" mmsc="http://mmsc.entelpcs.cl" mmsproxy="10.99.0.10" mmsport="8080" type="mms" user="entelpcs" password="entelpcs" authtype="1"></apn>
+ <apn mcc="730" mnc="10" carrier="MMS Entel PCS" apn="mms.entelpcs.cl" mmsc="http://mmsc.entelpcs.cl" mmsproxy="10.99.0.10" mmsport="8080" type="mms" user="entelpcs" password="entelpcs" authtype="1"></apn>
+ <apn mcc="732" mnc="12" carrier="Movistar MMS" apn="mms.movistar.com.co" mmsc="http://mms.movistar.com.co" mmsproxy="192.168.222.7" mmsport="9001" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="732" mnc="101" carrier="Colombia:Comcel:MMS" apn="mms.comcel.com.co" mmsc="http://www.comcel.com.co/mms/" mmsproxy="198.228.90.225" mmsport="8799" type="mms" user="COMCELMMS" password="COMCELMMS" authtype="1"></apn>
+ <apn mcc="732" mnc="101" carrier="MMS Comcel 3GSM" apn="mms.comcel.com.co" mmsc="http://www.comcel.com.co/mms/" mmsproxy="198.228.90.225" mmsport="8799" type="mms" user="COMCELMMS" password="COMCELMMS" authtype="1"></apn>
+ <apn mcc="732" mnc="103" carrier="TIGO Multimedia" apn="mms.colombiamovil.com.co" mmsc="http://mms.ola.com.co" mmsproxy="190.102.206.48" mmsport="8080" type="mms" user="mms-cm1900" password="mms-cm1900" authtype="1"></apn>
+ <apn mcc="732" mnc="111" carrier="TIGO Multimedia" apn="mms.colombiamovil.com.co" mmsc="http://mms.ola.com.co" mmsproxy="190.102.206.48" mmsport="8080" type="mms" user="mms-cm1900" password="mms-cm1900" authtype="1"></apn>
+ <apn mcc="732" mnc="123" carrier="Movistar MMS" apn="mms.movistar.com.co" mmsc="http://mms.movistar.com.co" mmsproxy="192.168.222.7" mmsport="9001" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="734" mnc="01" carrier="MMS" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="734" mnc="1" carrier="MMS" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="734" mnc="1" carrier="Venezuela:Digitel:MMS:1" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="734" mnc="02" carrier="MMS" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="734" mnc="2" carrier="MMS" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="734" mnc="2" carrier="Venezuela:Digitel:MMS:2" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="734" mnc="03" carrier="MMS" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms"></apn>
+ <apn mcc="734" mnc="3" carrier="MMS" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="734" mnc="3" carrier="Venezuela:Digitel:MMS:3" apn="expresate.digitel.ve" mmsc="http://mms.412.com.ve/servlets/mms" mmsproxy="10.99.0.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="734" mnc="04" carrier="Movistar MMS" apn="mms.movistar.ve" mmsc="http://mms.movistar.com.ve:8088/mms" mmsproxy="200.35.64.73" mmsport="9001" type="mms"></apn>
+ <apn mcc="734" mnc="4" carrier="Movistar MMS" apn="mms.movistar.ve" mmsc="http://mms.movistar.com.ve:8088/mms" mmsproxy="200.35.64.73" mmsport="9001" type="mms" authtype="0"></apn>
+ <apn mcc="734" mnc="4" carrier="Venezuela:Movistar:MMS" apn="mms.movistar.ve" mmsc="http://mms.movistar.com.ve:8088/mms" mmsproxy="200.35.64.73" mmsport="9001" type="mms" authtype="1"></apn>
+ <apn mcc="734" mnc="06" carrier="MMS" apn="mm.movilnet.com.ve" mmsc="http://mms2.movilnet.com.ve/servlets/mms" mmsproxy="192.168.16.12" mmsport="8080" type="mms"></apn>
+ <apn mcc="734" mnc="6" carrier="MMS" apn="mm.movilnet.com.ve" mmsc="http://mms2.movilnet.com.ve/servlets/mms" mmsproxy="192.168.16.12" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="734" mnc="6" carrier="Venezuela:Movilnet:MMS" apn="mm.movilnet.com.ve" mmsc="http://mms2.movilnet.com.ve/servlets/mms" mmsproxy="192.168.16.12" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="736" mnc="01" carrier="VIVAMMS" apn="mms.nuevatel.com" mmsc="http://mmsgw.nuevatel.com:1981" mmsproxy="192.168.101.4" mmsport="3128" type="mms" authtype="1"></apn>
+ <apn mcc="736" mnc="1" carrier="VIVAMMS" apn="mms.nuevatel.com" mmsc="http://mmsgw.nuevatel.com:1981" mmsproxy="192.168.101.4" mmsport="3128" type="mms" authtype="1"></apn>
+ <apn mcc="736" mnc="02" carrier="ENTEL MMS GPRS" apn="mms.movil.com.bo" mmsc="http://mms.movil.com.bo/servlets/mms" mmsproxy="172.27.7.10" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="736" mnc="03" carrier="MMSTIGO" apn="mms.tigo.bo" mmsc="http://mms" mmsproxy="172.25.100.8" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="738" mnc="01" carrier="Guyana:Digicel:Mms" apn="wap.digicelgy.com" mmsc="http://mmc.digicelgy.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="9201" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="738" mnc="01" carrier="Guyana:Digicel:Modem" apn="wap.digicelgy.com" mmsc="http://www.digicelive.com" type="dun" user="wap" password="wap" authtype="1" proxy="172.20.6.12" port="8080"></apn>
+ <apn mcc="738" mnc="1" carrier="Guyana:Digicel:Mms" apn="wap.digicelgy.com" mmsc="http://mmc.digicelgy.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="9201" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="740" mnc="0" carrier="Ecuador:Movistar:MMS" apn="mms.movistar.com.ec" mmsc="http://mms.movistar.com.ec:8088/mms/" mmsproxy="10.3.5.50" mmsport="9001" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="740" mnc="00" carrier="Movistar MMS" apn="mms.movistar.com.ec" mmsc="http://mms.movistar.com.ec:8088/mms/" mmsproxy="10.3.5.50" mmsport="9001" type="mms" user="movistar" password="movistar" authtype="1"></apn>
+ <apn mcc="740" mnc="01" carrier="MMS Claro" apn="mms.claro.com.ec" mmsc="http://iesmms.porta.com.ec/" mmsproxy="216.250.208.94" mmsport="8799" type="mms" user="portamms" password="portamms2003" authtype="1"></apn>
+ <apn mcc="740" mnc="1" carrier="Ecuador:Claro:MMS" apn="mms.claro.com.ec" mmsc="http://iesmms.porta.com.ec" mmsproxy="216.250.208.94" mmsport="8799" type="mms" user="portamms" password="portamms2003" authtype="1"></apn>
+ <apn mcc="740" mnc="1" carrier="MMS Claro" apn="mms.claro.com.ec" mmsc="http://iesmms.porta.com.ec/" mmsproxy="216.250.208.94" mmsport="8799" type="mms" user="portamms" password="portamms2003" authtype="1"></apn>
+ <apn mcc="740" mnc="02" carrier="CNT MMS" apn="mms.alegro.net.ec" mmsc="http://mms.alegro.net.ec/mms/" mmsproxy="10.4.85.3" mmsport="8080" type="mms"></apn>
+ <apn mcc="740" mnc="2" carrier="CNT MMS" apn="mms.alegro.net.ec" mmsc="http://mms.alegro.net.ec/mms/" mmsproxy="10.4.85.3" mmsport="8080" type="mms" authtype="0"></apn>
+ <apn mcc="740" mnc="2" carrier="Ecuador:Alegro:Internet" apn="wap.alegro.net.ec" mmsc="www.alegro.com.ec/alegrowap/default.aspx" mmsproxy="10.4.85.3" type="default" authtype="1" port="8080"></apn>
+ <apn mcc="740" mnc="2" carrier="Ecuador:Alegro:Mms" apn="mms.alegro.net.ec" mmsc="http://mms.alegro.com.ec" mmsproxy="10.4.85.3" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="740" mnc="010" carrier="MMS Claro" apn="mms.claro.com.ec" mmsc="http://iesmms.porta.com.ec/" mmsproxy="216.250.208.94" mmsport="8799" type="mms" user="portamms" password="portamms2003" authtype="1"></apn>
+ <apn mcc="744" mnc="01" carrier="Paraguay:Voxx:Modem" apn="vox.wap" mmsc="http://www.vox.com.py/" mmsproxy="172.24.97.29" type="dun" authtype="1" port="8080"></apn>
+ <apn mcc="744" mnc="01" carrier="VOX MMS" apn="vox.mms" mmsc="http://mms.vox.com.py/mmsc" mmsproxy="172.24.97.29" mmsport="8080" type="mms" user="vox" password="vox" authtype="1"></apn>
+ <apn mcc="744" mnc="1" carrier="Paraguay:Voxx:Mms" apn="vox.mms" mmsc="http://srvvirtual.vox.com.py/mmsc" mmsproxy="172.24.97.29" mmsport="8080" type="mms" authtype="1"></apn>
+ <apn mcc="744" mnc="1" carrier="VOX MMS" apn="vox.mms" mmsc="http://mms.vox.com.py/mmsc" mmsproxy="172.24.97.29" mmsport="8080" type="mms" user="vox" password="vox" authtype="1"></apn>
+ <apn mcc="744" mnc="02" carrier="MMS GPRS PY" apn="mms.ctimovil.com.py" mmsc="http://mms.ctimovil.com.py" mmsproxy="170.51.255.240" mmsport="8080" type="mms" user="ctimms" password="ctimms999" authtype="1"></apn>
+ <apn mcc="744" mnc="2" carrier="Paraguay:Claro:Internet Claro PY" apn="igprs.claro.com.py" mmsc="http://mms.claro.com.py" type="default,mms" authtype="0"></apn>
+ <apn mcc="744" mnc="04" carrier="MMS Tigo" apn="mms.tigo.py" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" user="tigo" password="tigo" authtype="1"></apn>
+ <apn mcc="744" mnc="4" carrier="Paraguay:Tigo:MMS TIGO" apn="mms.tigo.py" mmsc="http://mms" mmsproxy="10.16.17.12" mmsport="8888" type="mms" user="tigo" password="tigo" authtype="1"></apn>
+ <apn mcc="744" mnc="05" carrier="Personal MMS Py" apn="mms" mmsc="http://mms" mmsproxy="172.16.192.7" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="744" mnc="5" carrier="Paraguay:Personal:MMS Personal" apn="mms" mmsc="http://mms" mmsproxy="172.16.192.7" mmsport="8080" type="mms" user="mms" password="mms" authtype="0"></apn>
+ <apn mcc="744" mnc="5" carrier="Personal MMS Py" apn="mms" mmsc="http://mms" mmsproxy="172.16.192.7" mmsport="8080" type="mms" user="mms" password="mms" authtype="1"></apn>
+ <apn mcc="746" mnc="03" carrier="Suriname:Digicel:Mms" apn="wap.digicelsr.com" mmsc="http://mmc.digicelsr.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="9201" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="746" mnc="03" carrier="Suriname:Digicel:Modem" apn="wap.digicelsr.com" mmsc="http://wapdigicel.com" type="dun" user="wap" password="wap" authtype="1" proxy="172.20.6.12" port="8080"></apn>
+ <apn mcc="746" mnc="3" carrier="Suriname:Digicel:Mms" apn="wap.digicelsr.com" mmsc="http://mmc.digicelsr.com/servlets/mms" mmsproxy="172.20.6.12" mmsport="9201" type="mms" user="wap" password="wap" authtype="1"></apn>
+ <apn mcc="746" mnc="3" carrier="Suriname:Digicel:Modem" apn="wap.digicelsr.com" mmsc="http://wapdigicel.com" type="dun" user="wap" password="wap" authtype="1" proxy="172.20.6.12" port="8080"></apn>
+ <apn mcc="748" mnc="01" carrier="mmsANCEL" apn="mms" mmsc="http://mmsc.mms.ancelutil.com.uy" mmsproxy="200.40.246.2" mmsport="3128" type="mms" authtype="1"></apn>
+ <apn mcc="748" mnc="1" carrier="Uruguay:Ancel:MMS" apn="mms" mmsc="http://mmsc.mms.ancelutil.com.uy" mmsproxy="200.40.246.2" mmsport="3128" type="mms" authtype="0"></apn>
+ <apn mcc="748" mnc="1" carrier="mmsANCEL" apn="mms" mmsc="http://mmsc.mms.ancelutil.com.uy" mmsproxy="200.40.246.2" mmsport="3128" type="mms" authtype="1"></apn>
+ <apn mcc="748" mnc="07" carrier="Movistar MMS" apn="apnmms.movistar.com.uy" mmsc="http://mmsc.movistar.com.uy" mmsproxy="10.0.2.29" mmsport="8080" type="mms" user="mmsuy" password="mmsuy" authtype="1"></apn>
+ <apn mcc="748" mnc="7" carrier="Uruguay:Movistar:MMS" apn="apnmms.movistar.com.uy" mmsc="http://mmsc.movistar.com.uy" mmsproxy="10.0.2.29" mmsport="8080" type="mms" user="mmsuy" password="mmsuy" authtype="0"></apn>
+ <apn mcc="748" mnc="10" carrier="MMS GPRS UY" apn="mms.ctimovil.com.uy" mmsc="http://mms.ctimovil.com.uy" mmsproxy="170.51.255.240" mmsport="8080" type="mms" user="ctimms" password="ctimms999" authtype="1"></apn>
+ <apn mcc="748" mnc="10" carrier="Uruguay:Claro:Internet Claro UY" apn="igprs.claro.com.uy" mmsc="http://mms.claro.com.uy" type="default,mms" authtype="0"></apn>
+ </apns>
diff --git a/res/xml/mms_config.xml b/res/xml/mms_config.xml
new file mode 100644
index 0000000..e5a9338
--- /dev/null
+++ b/res/xml/mms_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<!-- Version History
+ version 1 - initial version.
+ version 2 - added recipientLimit.
+ version 3 - added min/max recycler values.
+ version 4 - added sms to mms text threshold.
+-->
+
+<mms_config version="4">
+ <!-- Flag indicating whether MMS should be enabled -->
+ <bool name="enabledMMS">true</bool>
+
+ <!-- Maximum message size in bytes for a MMS message -->
+ <int name="maxMessageSize">307200</int>
+
+ <!-- Maximum height for an attached image -->
+ <int name="maxImageHeight">480</int>
+
+ <!-- Maximum width for an attached image -->
+ <int name="maxImageWidth">640</int>
+
+ <!-- Maximum number of recipients allowed per message. Use a value of -1
+ to indicate no limit. -->
+ <int name="recipientLimit">-1</int>
+
+ <!-- If true, The text message over 160 characters will be sent in multi part.
+ If false, The text message over 160 characters will be sent
+ via multi media message. -->
+ <bool name="enableMultipartSMS">true</bool>
+
+ <!-- If enableMultipartSMS is true and smsToMmsTextThreshold > 1, then multi-part SMS messages
+ will be converted into a single mms message. For example, if the mms_config.xml file
+ specifies <int name="smsToMmsTextThreshold">7</int>, then on the 8th sms segment, the
+ message will be converted to an mms. -->
+ <int name="smsToMmsTextThreshold">-1</int>
+
+ <!-- Maximum length for message text. Use a value of -1
+ to indicate default value -->
+ <int name="maxMessageTextSize">-1</int>
+
+ <!-- UAProf parameter tag name -->
+ <!-- this is default to "x-wap-profile". Override if necessary. Optional -->
+ <!--
+ <string name="uaProfTagName">x-wap-profile</string>
+ -->
+
+ <!-- Reference for additional http parameters used in MMS http request.
+ Parameters are seperated by '|'. Optional.
+ -->
+ <!--
+ <string name="httpParams">
+ x-up-calling-line-id: ##LINE1##|x-carrier-magic: http://magic.google.com
+ </string>
+ -->
+
+ <!-- Suffix to the NAI header (encoded together with base64) -->
+ <!--
+ <string name="naiSuffix"></string>
+ -->
+</mms_config>
diff --git a/res/xml/preferences_application.xml b/res/xml/preferences_application.xml
new file mode 100644
index 0000000..7a18d09
--- /dev/null
+++ b/res/xml/preferences_application.xml
@@ -0,0 +1,91 @@
+<?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.
+-->
+<!-- Preference screen definition for Bugle's application-wide settings -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- KLP+ only -->
+ <PreferenceScreen
+ android:key="@string/sms_disabled_pref_key"
+ android:title="@string/sms_disabled_pref_title"
+ android:persistent="false">
+ <intent
+ android:action="android.provider.Telephony.ACTION_CHANGE_DEFAULT">
+ <extra android:name="package" android:value="com.android.messaging" />
+ </intent>
+ </PreferenceScreen>
+ <PreferenceScreen
+ android:key="@string/sms_enabled_pref_key"
+ android:title="@string/sms_enabled_pref_title"
+ android:persistent="false">
+ <intent
+ android:action="android.settings.WIRELESS_SETTINGS">
+ </intent>
+ </PreferenceScreen>
+
+ <CheckBoxPreference
+ android:key="@string/send_sound_pref_key"
+ android:title="@string/send_sound_pref_title"
+ android:defaultValue="@bool/send_sound_pref_default"
+ android:persistent="true" />
+
+ <CheckBoxPreference
+ android:key="@string/notifications_enabled_pref_key"
+ android:title="@string/notifications_enabled_pref_title"
+ android:defaultValue="@bool/notifications_enabled_pref_default"
+ android:persistent="true"
+ android:disableDependentsState="false" />
+
+ <RingtonePreference
+ android:key="@string/notification_sound_pref_key"
+ android:title="@string/notification_sound_pref_title"
+ android:ringtoneType="notification"
+ android:showSilent="true"
+ android:showDefault="true"
+ android:persistent="true"
+ android:defaultValue=""
+ android:dependency="@string/notifications_enabled_pref_key" />
+
+ <CheckBoxPreference
+ android:key="@string/notification_vibration_pref_key"
+ android:title="@string/notification_vibrate_pref_title"
+ android:defaultValue="@bool/notification_vibration_pref_default"
+ android:persistent="true"
+ android:dependency="@string/notifications_enabled_pref_key" />
+
+ <PreferenceScreen
+ android:key="@string/advanced_pref_key"
+ android:title="@string/advanced_settings" />
+
+ <PreferenceCategory
+ android:key="@string/debug_pref_key"
+ android:title="@string/debug_category_pref_title">
+
+ <CheckBoxPreference
+ android:key="@string/dump_sms_pref_key"
+ android:title="@string/dump_sms_pref_title"
+ android:summary="@string/dump_sms_pref_summary"
+ android:defaultValue="@bool/dump_sms_pref_default" />
+
+ <CheckBoxPreference
+ android:key="@string/dump_mms_pref_key"
+ android:title="@string/dump_mms_pref_title"
+ android:summary="@string/dump_mms_pref_summary"
+ android:defaultValue="@bool/dump_mms_pref_default" />
+
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/res/xml/preferences_per_subscription.xml b/res/xml/preferences_per_subscription.xml
new file mode 100644
index 0000000..5e00086
--- /dev/null
+++ b/res/xml/preferences_per_subscription.xml
@@ -0,0 +1,66 @@
+<?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.
+-->
+<!-- Preference screen definition for Bugle's subscription-specific settings -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <PreferenceCategory
+ android:key="@string/mms_messaging_category_pref_key"
+ android:title="@string/mms_messaging_category_pref_title">
+
+ <Preference
+ android:title="@string/group_mms_pref_title"
+ android:key="@string/group_mms_pref_key"/>
+
+ <com.android.messaging.ui.appsettings.PhoneNumberPreference
+ android:key="@string/mms_phone_number_pref_key"
+ android:title="@string/mms_phone_number_pref_title" />
+
+ <CheckBoxPreference
+ android:key="@string/auto_retrieve_mms_pref_key"
+ android:title="@string/auto_retrieve_mms_pref_title"
+ android:summary="@string/auto_retrieve_mms_pref_summary"
+ android:defaultValue="@bool/auto_retrieve_mms_pref_default" />
+
+ <CheckBoxPreference
+ android:key="@string/auto_retrieve_mms_when_roaming_pref_key"
+ android:dependency="@string/auto_retrieve_mms_pref_key"
+ android:title="@string/auto_retrieve_mms_when_roaming_pref_title"
+ android:summary="@string/auto_retrieve_mms_when_roaming_pref_summary"
+ android:defaultValue="@bool/auto_retrieve_mms_when_roaming_pref_default" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="@string/advanced_category_pref_key"
+ android:title="@string/advanced_category_pref_title">
+
+ <CheckBoxPreference
+ android:key="@string/delivery_reports_pref_key"
+ android:title="@string/delivery_reports_pref_title"
+ android:summary="@string/delivery_reports_pref_summary"
+ android:defaultValue="@bool/delivery_reports_pref_default" />
+
+ <Preference
+ android:key="@string/wireless_alerts_key"
+ android:title="@string/wireless_alerts_title"/>
+
+ <PreferenceScreen
+ android:key="@string/sms_apns_key"
+ android:title="@string/sms_apns_title" />
+
+ </PreferenceCategory>
+</PreferenceScreen>
diff --git a/res/xml/widget_conversation.xml b/res/xml/widget_conversation.xml
new file mode 100644
index 0000000..d1a573b
--- /dev/null
+++ b/res/xml/widget_conversation.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minResizeWidth="110dp"
+ android:minResizeHeight="110dp"
+ android:minWidth="180dp"
+ android:minHeight="180dp"
+ android:updatePeriodMillis="0"
+ android:initialLayout="@layout/widget_conversation"
+ android:resizeMode="horizontal|vertical"
+ android:widgetCategory="home_screen|keyguard"
+ android:previewImage="@drawable/messenger_widget_conversation_preview"
+ android:configure="com.android.messaging.ui.WidgetPickConversationActivity">
+</appwidget-provider>
diff --git a/res/xml/widget_conversation_list.xml b/res/xml/widget_conversation_list.xml
new file mode 100644
index 0000000..b294acb
--- /dev/null
+++ b/res/xml/widget_conversation_list.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minResizeWidth="110dp"
+ android:minResizeHeight="110dp"
+ android:minWidth="250dp"
+ android:minHeight="180dp"
+ android:updatePeriodMillis="0"
+ android:initialLayout="@layout/widget_conversation_list"
+ android:resizeMode="horizontal|vertical"
+ android:previewImage="@drawable/messenger_widget_preview"
+ android:widgetCategory="home_screen|keyguard">
+</appwidget-provider>
diff --git a/src/android/support/v7/mms/ApnException.java b/src/android/support/v7/mms/ApnException.java
new file mode 100644
index 0000000..075d92d
--- /dev/null
+++ b/src/android/support/v7/mms/ApnException.java
@@ -0,0 +1,39 @@
+/*
+ * 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 android.support.v7.mms;
+
+/**
+ * APN exception
+ */
+public class ApnException extends Exception {
+
+ public ApnException() {
+ super();
+ }
+
+ public ApnException(String message) {
+ super(message);
+ }
+
+ public ApnException(Throwable cause) {
+ super(cause);
+ }
+
+ public ApnException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/android/support/v7/mms/ApnSettingsLoader.java b/src/android/support/v7/mms/ApnSettingsLoader.java
new file mode 100644
index 0000000..ff8ed12
--- /dev/null
+++ b/src/android/support/v7/mms/ApnSettingsLoader.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.support.v7.mms;
+
+import java.util.List;
+
+/**
+ * Interface for loading APNs for default SMS SIM
+ */
+public interface ApnSettingsLoader {
+ /**
+ * Interface to represent the minimal information MMS lib needs from an APN
+ */
+ interface Apn {
+ /**
+ * Get the MMSC URL string
+ *
+ * @return MMSC URL
+ */
+ String getMmsc();
+
+ /**
+ * Get the MMS proxy host address
+ *
+ * @return MMS proxy
+ */
+ String getMmsProxy();
+
+ /**
+ * Get the MMS proxy host port
+ *
+ * @return the port of MMS proxy
+ */
+ int getMmsProxyPort();
+
+ /**
+ * Flag the APN as a successful APN to use
+ */
+ void setSuccess();
+ }
+
+ /**
+ * Get a list possible APN matching the subId and APN name
+ *
+ * @param apnName the APN name
+ * @return a list of possible APNs
+ */
+ List<Apn> get(String apnName);
+}
diff --git a/src/android/support/v7/mms/ApnsXmlParser.java b/src/android/support/v7/mms/ApnsXmlParser.java
new file mode 100644
index 0000000..eeafcf6
--- /dev/null
+++ b/src/android/support/v7/mms/ApnsXmlParser.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.ContentValues;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Parser for built-in XML resource file for APN list
+ */
+class ApnsXmlParser extends MmsXmlResourceParser {
+ interface ApnProcessor {
+ void process(ContentValues apnValues);
+ }
+
+ private static final String TAG_APNS = "apns";
+ private static final String TAG_APN = "apn";
+
+ private final ApnProcessor mApnProcessor;
+
+ private final ContentValues mValues = new ContentValues();
+
+ ApnsXmlParser(final XmlPullParser parser, final ApnProcessor apnProcessor) {
+ super(parser);
+ mApnProcessor = apnProcessor;
+ }
+
+ // Parse one APN
+ @Override
+ protected void parseRecord() throws IOException, XmlPullParserException {
+ if (TAG_APN.equals(mInputParser.getName())) {
+ mValues.clear();
+ // Collect all the attributes
+ for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
+ final String key = mInputParser.getAttributeName(i);
+ if (key != null) {
+ mValues.put(key, mInputParser.getAttributeValue(i));
+ }
+ }
+ // We are done parsing one APN, call the handler
+ if (mApnProcessor != null) {
+ mApnProcessor.process(mValues);
+ }
+ }
+ // We are at the end tag
+ if (mInputParser.next() != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException("Expecting end tag @" + xmlParserDebugContext());
+ }
+ }
+
+ @Override
+ protected String getRootTag() {
+ return TAG_APNS;
+ }
+}
diff --git a/src/android/support/v7/mms/CarrierConfigValuesLoader.java b/src/android/support/v7/mms/CarrierConfigValuesLoader.java
new file mode 100644
index 0000000..ad94fb2
--- /dev/null
+++ b/src/android/support/v7/mms/CarrierConfigValuesLoader.java
@@ -0,0 +1,198 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.os.Bundle;
+
+/**
+ * Loader for carrier dependent configuration values
+ */
+public interface CarrierConfigValuesLoader {
+ /**
+ * Get the carrier config values in a bundle
+ *
+ * @param subId the associated subscription ID for the carrier configuration
+ * @return a bundle of all the values
+ */
+ Bundle get(int subId);
+
+ // Configuration keys and default values
+
+ /** Boolean value: if MMS is enabled */
+ public static final String CONFIG_ENABLED_MMS = "enabledMMS";
+ public static final boolean CONFIG_ENABLED_MMS_DEFAULT = true;
+ /**
+ * Boolean value: if transaction ID should be appended to
+ * the download URL of a single segment WAP push message
+ */
+ public static final String CONFIG_ENABLED_TRANS_ID = "enabledTransID";
+ public static final boolean CONFIG_ENABLED_TRANS_ID_DEFAULT = false;
+ /**
+ * Boolean value: if acknowledge or notify response to a download
+ * should be sent to the WAP push message's download URL
+ */
+ public static final String CONFIG_ENABLED_NOTIFY_WAP_MMSC = "enabledNotifyWapMMSC";
+ public static final boolean CONFIG_ENABLED_NOTIFY_WAP_MMSC_DEFAULT = false;
+ /**
+ * Boolean value: if phone number alias can be used
+ */
+ public static final String CONFIG_ALIAS_ENABLED = "aliasEnabled";
+ public static final boolean CONFIG_ALIAS_ENABLED_DEFAULT = false;
+ /**
+ * Boolean value: if audio is allowed in attachment
+ */
+ public static final String CONFIG_ALLOW_ATTACH_AUDIO = "allowAttachAudio";
+ public static final boolean CONFIG_ALLOW_ATTACH_AUDIO_DEFAULT = true;
+ /**
+ * Boolean value: if true, long sms messages are always sent as multi-part sms
+ * messages, with no checked limit on the number of segments. If false, then
+ * as soon as the user types a message longer than a single segment (i.e. 140 chars),
+ * the message will turn into and be sent as an mms message or separate,
+ * independent SMS messages (dependent on CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES flag).
+ * This feature exists for carriers that don't support multi-part sms.
+ */
+ public static final String CONFIG_ENABLE_MULTIPART_SMS = "enableMultipartSMS";
+ public static final boolean CONFIG_ENABLE_MULTIPART_SMS_DEFAULT = true;
+ /**
+ * Boolean value: if SMS delivery report is supported
+ */
+ public static final String CONFIG_ENABLE_SMS_DELIVERY_REPORTS = "enableSMSDeliveryReports";
+ public static final boolean CONFIG_ENABLE_SMS_DELIVERY_REPORTS_DEFAULT = true;
+ /**
+ * Boolean value: if group MMS is supported
+ */
+ public static final String CONFIG_ENABLE_GROUP_MMS = "enableGroupMms";
+ public static final boolean CONFIG_ENABLE_GROUP_MMS_DEFAULT = true;
+ /**
+ * Boolean value: if the content_disposition field of an MMS part should be parsed
+ * Check wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21. Most carriers support it except some.
+ */
+ public static final String CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION =
+ "supportMmsContentDisposition";
+ public static final boolean CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT = true;
+ /**
+ * Boolean value: if the sms app should support a link to the system settings
+ * where amber alerts are configured.
+ */
+ public static final String CONFIG_CELL_BROADCAST_APP_LINKS = "config_cellBroadcastAppLinks";
+ public static final boolean CONFIG_CELL_BROADCAST_APP_LINKS_DEFAULT = true;
+ /**
+ * Boolean value: if multipart SMS should be sent as separate SMS messages
+ */
+ public static final String CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES =
+ "sendMultipartSmsAsSeparateMessages";
+ public static final boolean CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_DEFAULT = false;
+ /**
+ * Boolean value: if MMS read report is supported
+ */
+ public static final String CONFIG_ENABLE_MMS_READ_REPORTS = "enableMMSReadReports";
+ public static final boolean CONFIG_ENABLE_MMS_READ_REPORTS_DEFAULT = false;
+ /**
+ * Boolean value: if MMS delivery report is supported
+ */
+ public static final String CONFIG_ENABLE_MMS_DELIVERY_REPORTS = "enableMMSDeliveryReports";
+ public static final boolean CONFIG_ENABLE_MMS_DELIVERY_REPORTS_DEFAULT = false;
+ /**
+ * Boolean value: if "charset" value is supported in the "Content-Type" HTTP header
+ */
+ public static final String CONFIG_SUPPORT_HTTP_CHARSET_HEADER = "supportHttpCharsetHeader";
+ public static final boolean CONFIG_SUPPORT_HTTP_CHARSET_HEADER_DEFAULT = false;
+ /**
+ * Integer value: maximal MMS message size in bytes
+ */
+ public static final String CONFIG_MAX_MESSAGE_SIZE = "maxMessageSize";
+ public static final int CONFIG_MAX_MESSAGE_SIZE_DEFAULT = 300 * 1024;
+ /**
+ * Integer value: maximal MMS image height in pixels
+ */
+ public static final String CONFIG_MAX_IMAGE_HEIGHT = "maxImageHeight";
+ public static final int CONFIG_MAX_IMAGE_HEIGHT_DEFAULT = 480;
+ /**
+ * Integer value: maximal MMS image width in pixels
+ */
+ public static final String CONFIG_MAX_IMAGE_WIDTH = "maxImageWidth";
+ public static final int CONFIG_MAX_IMAGE_WIDTH_DEFAULT = 640;
+ /**
+ * Integer value: limit on recipient list of an MMS message
+ */
+ public static final String CONFIG_RECIPIENT_LIMIT = "recipientLimit";
+ public static final int CONFIG_RECIPIENT_LIMIT_DEFAULT = Integer.MAX_VALUE;
+ /**
+ * Integer value: HTTP socket timeout in milliseconds for MMS
+ */
+ public static final String CONFIG_HTTP_SOCKET_TIMEOUT = "httpSocketTimeout";
+ public static final int CONFIG_HTTP_SOCKET_TIMEOUT_DEFAULT = 60 * 1000;
+ /**
+ * Integer value: minimal number of characters of an alias
+ */
+ public static final String CONFIG_ALIAS_MIN_CHARS = "aliasMinChars";
+ public static final int CONFIG_ALIAS_MIN_CHARS_DEFAULT = 2;
+ /**
+ * Integer value: maximal number of characters of an alias
+ */
+ public static final String CONFIG_ALIAS_MAX_CHARS = "aliasMaxChars";
+ public static final int CONFIG_ALIAS_MAX_CHARS_DEFAULT = 48;
+ /**
+ * Integer value: the threshold of number of SMS parts when an multipart SMS will be
+ * converted into an MMS, e.g. if this is "4", when an multipart SMS message has 5
+ * parts, then it will be sent as MMS message instead. "-1" indicates no such conversion
+ * can happen.
+ */
+ public static final String CONFIG_SMS_TO_MMS_TEXT_THRESHOLD = "smsToMmsTextThreshold";
+ public static final int CONFIG_SMS_TO_MMS_TEXT_THRESHOLD_DEFAULT = -1;
+ /**
+ * Integer value: the threshold of SMS length when it will be converted into an MMS.
+ * "-1" indicates no such conversion can happen.
+ */
+ public static final String CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD =
+ "smsToMmsTextLengthThreshold";
+ public static final int CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_DEFAULT = -1;
+ /**
+ * Integer value: maximal length in bytes of SMS message
+ */
+ public static final String CONFIG_MAX_MESSAGE_TEXT_SIZE = "maxMessageTextSize";
+ public static final int CONFIG_MAX_MESSAGE_TEXT_SIZE_DEFAULT = -1;
+ /**
+ * Integer value: maximum number of characters allowed for mms subject
+ */
+ public static final String CONFIG_MAX_SUBJECT_LENGTH = "maxSubjectLength";
+ public static final int CONFIG_MAX_SUBJECT_LENGTH_DEFAULT = 40;
+ /**
+ * String value: name for the user agent profile HTTP header
+ */
+ public static final String CONFIG_UA_PROF_TAG_NAME = "mUaProfTagName";
+ public static final String CONFIG_UA_PROF_TAG_NAME_DEFAULT = "x-wap-profile";
+ /**
+ * String value: additional HTTP headers for MMS HTTP requests.
+ * The format is
+ * header_1:header_value_1|header_2:header_value_2|...
+ * Each value can contain macros.
+ */
+ public static final String CONFIG_HTTP_PARAMS = "httpParams";
+ public static final String CONFIG_HTTP_PARAMS_DEFAULT = null;
+ /**
+ * String value: number of email gateway
+ */
+ public static final String CONFIG_EMAIL_GATEWAY_NUMBER = "emailGatewayNumber";
+ public static final String CONFIG_EMAIL_GATEWAY_NUMBER_DEFAULT = null;
+ /**
+ * String value: suffix for the NAI HTTP header value, e.g. ":pcs"
+ * (NAI is used as authentication in HTTP headers for some carriers)
+ */
+ public static final String CONFIG_NAI_SUFFIX = "naiSuffix";
+ public static final String CONFIG_NAI_SUFFIX_DEFAULT = null;
+}
diff --git a/src/android/support/v7/mms/CarrierConfigXmlParser.java b/src/android/support/v7/mms/CarrierConfigXmlParser.java
new file mode 100644
index 0000000..42e7c21
--- /dev/null
+++ b/src/android/support/v7/mms/CarrierConfigXmlParser.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.support.v7.mms;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * XML parser for carrier config (i.e. mms_config)
+ */
+class CarrierConfigXmlParser extends MmsXmlResourceParser {
+ interface KeyValueProcessor {
+ void process(String type, String key, String value);
+ }
+
+ private static final String TAG_MMS_CONFIG = "mms_config";
+
+ private final KeyValueProcessor mKeyValueProcessor;
+
+ CarrierConfigXmlParser(final XmlPullParser parser, final KeyValueProcessor keyValueProcessor) {
+ super(parser);
+ mKeyValueProcessor = keyValueProcessor;
+ }
+
+ // Parse one key/value
+ @Override
+ protected void parseRecord() throws IOException, XmlPullParserException {
+ final String key = mInputParser.getAttributeValue(null, "name");
+ // We are at the start tag, the name of the tag is the type
+ // e.g. <int name="key">value</int>
+ final String type = mInputParser.getName();
+ int nextEvent = mInputParser.next();
+ String value = null;
+ if (nextEvent == XmlPullParser.TEXT) {
+ value = mInputParser.getText();
+ nextEvent = mInputParser.next();
+ }
+ if (nextEvent != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException("Expecting end tag @" + xmlParserDebugContext());
+ }
+ // We are done parsing one mms_config key/value, call the handler
+ if (mKeyValueProcessor != null) {
+ mKeyValueProcessor.process(type, key, value);
+ }
+ }
+
+ @Override
+ protected String getRootTag() {
+ return TAG_MMS_CONFIG;
+ }
+
+}
diff --git a/src/android/support/v7/mms/DefaultApnSettingsLoader.java b/src/android/support/v7/mms/DefaultApnSettingsLoader.java
new file mode 100644
index 0000000..83f2fd3
--- /dev/null
+++ b/src/android/support/v7/mms/DefaultApnSettingsLoader.java
@@ -0,0 +1,497 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.messaging.R;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Default implementation of APN settings loader
+ */
+class DefaultApnSettingsLoader implements ApnSettingsLoader {
+ /**
+ * The base implementation of an APN
+ */
+ private static class BaseApn implements Apn {
+ /**
+ * Create a base APN from parameters
+ *
+ * @param typesIn the APN type field
+ * @param mmscIn the APN mmsc field
+ * @param proxyIn the APN mmsproxy field
+ * @param portIn the APN mmsport field
+ * @return an instance of base APN, or null if any of the parameter is invalid
+ */
+ public static BaseApn from(final String typesIn, final String mmscIn, final String proxyIn,
+ final String portIn) {
+ if (!isValidApnType(trimWithNullCheck(typesIn), APN_TYPE_MMS)) {
+ return null;
+ }
+ String mmsc = trimWithNullCheck(mmscIn);
+ if (TextUtils.isEmpty(mmsc)) {
+ return null;
+ }
+ mmsc = trimV4AddrZeros(mmsc);
+ try {
+ new URI(mmsc);
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+ String mmsProxy = trimWithNullCheck(proxyIn);
+ int mmsProxyPort = 80;
+ if (!TextUtils.isEmpty(mmsProxy)) {
+ mmsProxy = trimV4AddrZeros(mmsProxy);
+ final String portString = trimWithNullCheck(portIn);
+ if (portString != null) {
+ try {
+ mmsProxyPort = Integer.parseInt(portString);
+ } catch (final NumberFormatException e) {
+ // Ignore, just use 80 to try
+ }
+ }
+ }
+ return new BaseApn(mmsc, mmsProxy, mmsProxyPort);
+ }
+
+ private final String mMmsc;
+ private final String mMmsProxy;
+ private final int mMmsProxyPort;
+
+ public BaseApn(final String mmsc, final String proxy, final int port) {
+ mMmsc = mmsc;
+ mMmsProxy = proxy;
+ mMmsProxyPort = port;
+ }
+
+ @Override
+ public String getMmsc() {
+ return mMmsc;
+ }
+
+ @Override
+ public String getMmsProxy() {
+ return mMmsProxy;
+ }
+
+ @Override
+ public int getMmsProxyPort() {
+ return mMmsProxyPort;
+ }
+
+ @Override
+ public void setSuccess() {
+ // Do nothing
+ }
+
+ public boolean equals(final BaseApn other) {
+ return TextUtils.equals(mMmsc, other.getMmsc()) &&
+ TextUtils.equals(mMmsProxy, other.getMmsProxy()) &&
+ mMmsProxyPort == other.getMmsProxyPort();
+ }
+ }
+
+ /**
+ * An in-memory implementation of an APN. These APNs are organized into an in-memory list.
+ * The order of the list can be changed by the setSuccess method.
+ */
+ private static class MemoryApn implements Apn {
+ /**
+ * Create an in-memory APN loaded from resources
+ *
+ * @param apns the in-memory APN list
+ * @param typesIn the APN type field
+ * @param mmscIn the APN mmsc field
+ * @param proxyIn the APN mmsproxy field
+ * @param portIn the APN mmsport field
+ * @return an in-memory APN instance, null if there is invalid parameter
+ */
+ public static MemoryApn from(final List<Apn> apns, final String typesIn,
+ final String mmscIn, final String proxyIn, final String portIn) {
+ if (apns == null) {
+ return null;
+ }
+ final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn);
+ if (base == null) {
+ return null;
+ }
+ for (final Apn apn : apns) {
+ if (apn instanceof MemoryApn && ((MemoryApn) apn).equals(base)) {
+ return null;
+ }
+ }
+ return new MemoryApn(apns, base);
+ }
+
+ private final List<Apn> mApns;
+ private final BaseApn mBase;
+
+ public MemoryApn(final List<Apn> apns, final BaseApn base) {
+ mApns = apns;
+ mBase = base;
+ }
+
+ @Override
+ public String getMmsc() {
+ return mBase.getMmsc();
+ }
+
+ @Override
+ public String getMmsProxy() {
+ return mBase.getMmsProxy();
+ }
+
+ @Override
+ public int getMmsProxyPort() {
+ return mBase.getMmsProxyPort();
+ }
+
+ @Override
+ public void setSuccess() {
+ // If this is being marked as a successful APN, move it to the top of the list so
+ // next time it will be tried first
+ boolean moved = false;
+ synchronized (mApns) {
+ if (mApns.get(0) != this) {
+ mApns.remove(this);
+ mApns.add(0, this);
+ moved = true;
+ }
+ }
+ if (moved) {
+ Log.d(MmsService.TAG, "Set APN ["
+ + "MMSC=" + getMmsc() + ", "
+ + "PROXY=" + getMmsProxy() + ", "
+ + "PORT=" + getMmsProxyPort() + "] to be first");
+ }
+ }
+
+ public boolean equals(final BaseApn other) {
+ if (other == null) {
+ return false;
+ }
+ return mBase.equals(other);
+ }
+ }
+
+ /**
+ * APN_TYPE_ALL is a special type to indicate that this APN entry can
+ * service all data connections.
+ */
+ public static final String APN_TYPE_ALL = "*";
+ /** APN type for MMS traffic */
+ public static final String APN_TYPE_MMS = "mms";
+
+ private static final String[] APN_PROJECTION = {
+ Telephony.Carriers.TYPE,
+ Telephony.Carriers.MMSC,
+ Telephony.Carriers.MMSPROXY,
+ Telephony.Carriers.MMSPORT,
+ };
+ private static final int COLUMN_TYPE = 0;
+ private static final int COLUMN_MMSC = 1;
+ private static final int COLUMN_MMSPROXY = 2;
+ private static final int COLUMN_MMSPORT = 3;
+
+ private static final String APN_MCC = "mcc";
+ private static final String APN_MNC = "mnc";
+ private static final String APN_APN = "apn";
+ private static final String APN_TYPE = "type";
+ private static final String APN_MMSC = "mmsc";
+ private static final String APN_MMSPROXY = "mmsproxy";
+ private static final String APN_MMSPORT = "mmsport";
+
+ private final Context mContext;
+
+ // Cached APNs for subIds
+ private final SparseArray<List<Apn>> mApnsCache;
+
+ DefaultApnSettingsLoader(final Context context) {
+ mContext = context;
+ mApnsCache = new SparseArray<>();
+ }
+
+ @Override
+ public List<Apn> get(final String apnName) {
+ final int subId = Utils.getEffectiveSubscriptionId(MmsManager.DEFAULT_SUB_ID);
+ List<Apn> apns;
+ boolean didLoad = false;
+ synchronized (this) {
+ apns = mApnsCache.get(subId);
+ if (apns == null) {
+ apns = new ArrayList<>();
+ mApnsCache.put(subId, apns);
+ loadLocked(subId, apnName, apns);
+ didLoad = true;
+ }
+ }
+ if (didLoad) {
+ Log.i(MmsService.TAG, "Loaded " + apns.size() + " APNs");
+ }
+ return apns;
+ }
+
+ private void loadLocked(final int subId, final String apnName, final List<Apn> apns) {
+ // Try system APN table first
+ loadFromSystem(subId, apnName, apns);
+ if (apns.size() > 0) {
+ return;
+ }
+ // Try loading from apns.xml in resources
+ loadFromResources(subId, apnName, apns);
+ if (apns.size() > 0) {
+ return;
+ }
+ // Try resources but without APN name
+ loadFromResources(subId, null/*apnName*/, apns);
+ }
+
+ /**
+ * Load matching APNs from telephony provider.
+ * We try different combinations of the query to work around some platform quirks.
+ *
+ * @param subId the SIM subId
+ * @param apnName the APN name to match
+ * @param apns the list used to return results
+ */
+ private void loadFromSystem(final int subId, final String apnName, final List<Apn> apns) {
+ Uri uri;
+ if (Utils.supportMSim() && subId != MmsManager.DEFAULT_SUB_ID) {
+ uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId);
+ } else {
+ uri = Telephony.Carriers.CONTENT_URI;
+ }
+ Cursor cursor = null;
+ try {
+ for (; ; ) {
+ // Try different combinations of queries. Some would work on some platforms.
+ // So we query each combination until we find one returns non-empty result.
+ cursor = querySystem(uri, true/*checkCurrent*/, apnName);
+ if (cursor != null) {
+ break;
+ }
+ cursor = querySystem(uri, false/*checkCurrent*/, apnName);
+ if (cursor != null) {
+ break;
+ }
+ cursor = querySystem(uri, true/*checkCurrent*/, null/*apnName*/);
+ if (cursor != null) {
+ break;
+ }
+ cursor = querySystem(uri, false/*checkCurrent*/, null/*apnName*/);
+ break;
+ }
+ } catch (final SecurityException e) {
+ // Can't access platform APN table, return directly
+ return;
+ }
+ if (cursor == null) {
+ return;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ final Apn apn = BaseApn.from(
+ cursor.getString(COLUMN_TYPE),
+ cursor.getString(COLUMN_MMSC),
+ cursor.getString(COLUMN_MMSPROXY),
+ cursor.getString(COLUMN_MMSPORT));
+ if (apn != null) {
+ apns.add(apn);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Query system APN table
+ *
+ * @param uri The APN query URL to use
+ * @param checkCurrent If add "CURRENT IS NOT NULL" condition
+ * @param apnName The optional APN name for query condition
+ * @return A cursor of the query result. If a cursor is returned as not null, it is
+ * guaranteed to contain at least one row.
+ */
+ private Cursor querySystem(final Uri uri, final boolean checkCurrent, String apnName) {
+ Log.i(MmsService.TAG, "Loading APNs from system, "
+ + "checkCurrent=" + checkCurrent + " apnName=" + apnName);
+ final StringBuilder selectionBuilder = new StringBuilder();
+ String[] selectionArgs = null;
+ if (checkCurrent) {
+ selectionBuilder.append(Telephony.Carriers.CURRENT).append(" IS NOT NULL");
+ }
+ apnName = trimWithNullCheck(apnName);
+ if (!TextUtils.isEmpty(apnName)) {
+ if (selectionBuilder.length() > 0) {
+ selectionBuilder.append(" AND ");
+ }
+ selectionBuilder.append(Telephony.Carriers.APN).append("=?");
+ selectionArgs = new String[] { apnName };
+ }
+ try {
+ final Cursor cursor = mContext.getContentResolver().query(
+ uri,
+ APN_PROJECTION,
+ selectionBuilder.toString(),
+ selectionArgs,
+ null/*sortOrder*/);
+ if (cursor == null || cursor.getCount() < 1) {
+ if (cursor != null) {
+ cursor.close();
+ }
+ Log.w(MmsService.TAG, "Query " + uri + " with apn " + apnName + " and "
+ + (checkCurrent ? "checking CURRENT" : "not checking CURRENT")
+ + " returned empty");
+ return null;
+ }
+ return cursor;
+ } catch (final SQLiteException e) {
+ Log.w(MmsService.TAG, "APN table query exception: " + e);
+ } catch (final SecurityException e) {
+ Log.w(MmsService.TAG, "Platform restricts APN table access: " + e);
+ throw e;
+ }
+ return null;
+ }
+
+ /**
+ * Find matching APNs using builtin APN list resource
+ *
+ * @param subId the SIM subId
+ * @param apnName the APN name to match
+ * @param apns the list for returning results
+ */
+ private void loadFromResources(final int subId, final String apnName, final List<Apn> apns) {
+ Log.i(MmsService.TAG, "Loading APNs from resources, apnName=" + apnName);
+ final int[] mccMnc = Utils.getMccMnc(mContext, subId);
+ if (mccMnc[0] == 0 && mccMnc[0] == 0) {
+ Log.w(MmsService.TAG, "Can not get valid mcc/mnc from system");
+ return;
+ }
+ // MCC/MNC is good, loading/querying APNs from XML
+ XmlResourceParser xml = null;
+ try {
+ xml = mContext.getResources().getXml(R.xml.apns);
+ new ApnsXmlParser(xml, new ApnsXmlParser.ApnProcessor() {
+ @Override
+ public void process(ContentValues apnValues) {
+ final String mcc = trimWithNullCheck(apnValues.getAsString(APN_MCC));
+ final String mnc = trimWithNullCheck(apnValues.getAsString(APN_MNC));
+ final String apn = trimWithNullCheck(apnValues.getAsString(APN_APN));
+ try {
+ if (mccMnc[0] == Integer.parseInt(mcc) &&
+ mccMnc[1] == Integer.parseInt(mnc) &&
+ (TextUtils.isEmpty(apnName) || apnName.equalsIgnoreCase(apn))) {
+ final String type = apnValues.getAsString(APN_TYPE);
+ final String mmsc = apnValues.getAsString(APN_MMSC);
+ final String mmsproxy = apnValues.getAsString(APN_MMSPROXY);
+ final String mmsport = apnValues.getAsString(APN_MMSPORT);
+ final Apn newApn = MemoryApn.from(apns, type, mmsc, mmsproxy, mmsport);
+ if (newApn != null) {
+ apns.add(newApn);
+ }
+ }
+ } catch (final NumberFormatException e) {
+ // Ignore
+ }
+ }
+ }).parse();
+ } catch (final Resources.NotFoundException e) {
+ Log.w(MmsService.TAG, "Can not get apns.xml " + e);
+ } finally {
+ if (xml != null) {
+ xml.close();
+ }
+ }
+ }
+
+ private static String trimWithNullCheck(final String value) {
+ return value != null ? value.trim() : null;
+ }
+
+ /**
+ * Trim leading zeros from IPv4 address strings
+ * Our base libraries will interpret that as octel..
+ * Must leave non v4 addresses and host names alone.
+ * For example, 192.168.000.010 -> 192.168.0.10
+ *
+ * @param addr a string representing an ip addr
+ * @return a string propertly trimmed
+ */
+ private static String trimV4AddrZeros(final String addr) {
+ if (addr == null) {
+ return null;
+ }
+ final String[] octets = addr.split("\\.");
+ if (octets.length != 4) {
+ return addr;
+ }
+ final StringBuilder builder = new StringBuilder(16);
+ String result = null;
+ for (int i = 0; i < 4; i++) {
+ try {
+ if (octets[i].length() > 3) {
+ return addr;
+ }
+ builder.append(Integer.parseInt(octets[i]));
+ } catch (final NumberFormatException e) {
+ return addr;
+ }
+ if (i < 3) {
+ builder.append('.');
+ }
+ }
+ result = builder.toString();
+ return result;
+ }
+
+ /**
+ * Check if the APN contains the APN type we want
+ *
+ * @param types The string encodes a list of supported types
+ * @param requestType The type we want
+ * @return true if the input types string contains the requestType
+ */
+ public static boolean isValidApnType(final String types, final String requestType) {
+ // If APN type is unspecified, assume APN_TYPE_ALL.
+ if (TextUtils.isEmpty(types)) {
+ return true;
+ }
+ for (final String t : types.split(",")) {
+ if (t.equals(requestType) || t.equals(APN_TYPE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java b/src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java
new file mode 100644
index 0000000..1d45f8f
--- /dev/null
+++ b/src/android/support/v7/mms/DefaultCarrierConfigValuesLoader.java
@@ -0,0 +1,126 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.messaging.R;
+
+/**
+ * The default implementation of loader for carrier config values
+ */
+class DefaultCarrierConfigValuesLoader implements CarrierConfigValuesLoader {
+ /*
+ * Key types
+ */
+ public static final String KEY_TYPE_INT = "int";
+ public static final String KEY_TYPE_BOOL = "bool";
+ public static final String KEY_TYPE_STRING = "string";
+
+ private final Context mContext;
+
+ // Cached values for subIds
+ private final SparseArray<Bundle> mValuesCache;
+
+ DefaultCarrierConfigValuesLoader(final Context context) {
+ mContext = context;
+ mValuesCache = new SparseArray<>();
+ }
+
+ @Override
+ public Bundle get(int subId) {
+ subId = Utils.getEffectiveSubscriptionId(subId);
+ Bundle values;
+ boolean didLoad = false;
+ synchronized (this) {
+ values = mValuesCache.get(subId);
+ if (values == null) {
+ values = new Bundle();
+ mValuesCache.put(subId, values);
+ loadLocked(subId, values);
+ didLoad = true;
+ }
+ }
+ if (didLoad) {
+ Log.i(MmsService.TAG, "Carrier configs loaded: " + values);
+ }
+ return values;
+ }
+
+ private void loadLocked(final int subId, final Bundle values) {
+ // For K and earlier, load from resources
+ loadFromResources(subId, values);
+ if (Utils.hasMmsApi()) {
+ // For L and later, also load from system MMS service
+ loadFromSystem(subId, values);
+ }
+ }
+
+ /**
+ * Load from system, using MMS API
+ *
+ * @param subId which SIM to load for
+ * @param values the result to add to
+ */
+ private static void loadFromSystem(final int subId, final Bundle values) {
+ try {
+ final Bundle systemValues = Utils.getSmsManager(subId).getCarrierConfigValues();
+ if (systemValues != null) {
+ values.putAll(systemValues);
+ }
+ } catch (final Exception e) {
+ Log.w(MmsService.TAG, "Calling system getCarrierConfigValues exception", e);
+ }
+ }
+
+ private void loadFromResources(final int subId, final Bundle values) {
+ // Get a subscription-dependent context for loading the mms_config.xml
+ final Context subContext = Utils.getSubDepContext(mContext, subId);
+ XmlResourceParser xml = null;
+ try {
+ xml = subContext.getResources().getXml(R.xml.mms_config);
+ new CarrierConfigXmlParser(xml, new CarrierConfigXmlParser.KeyValueProcessor() {
+ @Override
+ public void process(String type, String key, String value) {
+ try {
+ if (KEY_TYPE_INT.equals(type)) {
+ values.putInt(key, Integer.parseInt(value));
+ } else if (KEY_TYPE_BOOL.equals(type)) {
+ values.putBoolean(key, Boolean.parseBoolean(value));
+ } else if (KEY_TYPE_STRING.equals(type)) {
+ values.putString(key, value);
+ }
+ } catch (final NumberFormatException e) {
+ Log.w(MmsService.TAG, "Load carrier value from resources: "
+ + "invalid " + key + "," + value + "," + type);
+ }
+ }
+ }).parse();
+ } catch (final Resources.NotFoundException e) {
+ Log.w(MmsService.TAG, "Can not get mms_config.xml");
+ } finally {
+ if (xml != null) {
+ xml.close();
+ }
+ }
+ }
+}
diff --git a/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java b/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java
new file mode 100644
index 0000000..e59a234
--- /dev/null
+++ b/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java
@@ -0,0 +1,88 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * The default implementation of loader of UA and UAProfUrl
+ */
+class DefaultUserAgentInfoLoader implements UserAgentInfoLoader {
+ // Default values to be used as user agent info
+ private static final String DEFAULT_USER_AGENT = "Android MmsLib/1.0";
+ private static final String DEFAULT_UA_PROF_URL =
+ "http://www.gstatic.com/android/sms/mms_ua_profile.xml";
+
+ private Context mContext;
+ private boolean mLoaded;
+
+ private String mUserAgent;
+ private String mUAProfUrl;
+
+ DefaultUserAgentInfoLoader(final Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public String getUserAgent() {
+ load();
+ return mUserAgent;
+ }
+
+ @Override
+ public String getUAProfUrl() {
+ load();
+ return mUAProfUrl;
+ }
+
+ private void load() {
+ if (mLoaded) {
+ return;
+ }
+ boolean didLoad = false;
+ synchronized (this) {
+ if (!mLoaded) {
+ loadLocked();
+ mLoaded = true;
+ didLoad = true;
+ }
+ }
+ if (didLoad) {
+ Log.i(MmsService.TAG, "Loaded user agent info: "
+ + "UA=" + mUserAgent + ", UAProfUrl=" + mUAProfUrl);
+ }
+ }
+
+ private void loadLocked() {
+ if (Utils.hasUserAgentApi()) {
+ // load the MMS User agent and UaProfUrl from TelephonyManager APIs
+ final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ mUserAgent = telephonyManager.getMmsUserAgent();
+ mUAProfUrl = telephonyManager.getMmsUAProfUrl();
+ }
+ if (TextUtils.isEmpty(mUserAgent)) {
+ mUserAgent = DEFAULT_USER_AGENT;
+ }
+ if (TextUtils.isEmpty(mUAProfUrl)) {
+ mUAProfUrl = DEFAULT_UA_PROF_URL;
+ }
+ }
+}
diff --git a/src/android/support/v7/mms/DownloadRequest.java b/src/android/support/v7/mms/DownloadRequest.java
new file mode 100644
index 0000000..254c8ca
--- /dev/null
+++ b/src/android/support/v7/mms/DownloadRequest.java
@@ -0,0 +1,132 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Request to download an MMS
+ */
+class DownloadRequest extends MmsRequest {
+
+ DownloadRequest(final String locationUrl, final Uri pduUri,
+ final PendingIntent sentIntent) {
+ super(locationUrl, pduUri, sentIntent);
+ }
+
+ @Override
+ protected boolean loadRequest(final Context context, final Bundle mmsConfig) {
+ // No need to load PDU from app. Always true.
+ return true;
+ }
+
+ @Override
+ protected boolean transferResponse(Context context, Intent fillIn, byte[] response) {
+ return writePduToContentUri(context, mPduUri, response);
+ }
+
+ @Override
+ protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettingsLoader.Apn apn,
+ Bundle mmsConfig, String userAgent, String uaProfUrl) throws MmsHttpException {
+ final MmsHttpClient httpClient = netMgr.getHttpClient();
+ return httpClient.execute(getHttpRequestUrl(apn), null/*pdu*/, MmsHttpClient.METHOD_GET,
+ !TextUtils.isEmpty(apn.getMmsProxy()), apn.getMmsProxy(), apn.getMmsProxyPort(),
+ mmsConfig, userAgent, uaProfUrl);
+
+ }
+
+ @Override
+ protected String getHttpRequestUrl(final ApnSettingsLoader.Apn apn) {
+ return mLocationUrl;
+ }
+
+ /**
+ * Write pdu bytes to content provider uri
+ *
+ * @param contentUri content provider uri to which bytes should be written
+ * @param pdu Bytes to write
+ * @return true if all bytes successfully written else false
+ */
+ public boolean writePduToContentUri(final Context context, final Uri contentUri,
+ final byte[] pdu) {
+ if (contentUri == null || pdu == null) {
+ return false;
+ }
+ final Callable<Boolean> copyDownloadedPduToOutput = new Callable<Boolean>() {
+ public Boolean call() {
+ ParcelFileDescriptor.AutoCloseOutputStream outStream = null;
+ try {
+ final ContentResolver cr = context.getContentResolver();
+ final ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "w");
+ outStream = new ParcelFileDescriptor.AutoCloseOutputStream(pduFd);
+ outStream.write(pdu);
+ return true;
+ } catch (IOException e) {
+ Log.e(MmsService.TAG, "Writing PDU to downloader: IO exception", e);
+ return false;
+ } finally {
+ if (outStream != null) {
+ try {
+ outStream.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+ }
+ };
+ final Future<Boolean> pendingResult =
+ mPduTransferExecutor.submit(copyDownloadedPduToOutput);
+ try {
+ return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ // Typically a timeout occurred - cancel task
+ pendingResult.cancel(true);
+ }
+ return false;
+ }
+
+ public static final Parcelable.Creator<DownloadRequest> CREATOR
+ = new Parcelable.Creator<DownloadRequest>() {
+ public DownloadRequest createFromParcel(Parcel in) {
+ return new DownloadRequest(in);
+ }
+
+ public DownloadRequest[] newArray(int size) {
+ return new DownloadRequest[size];
+ }
+ };
+
+ private DownloadRequest(Parcel in) {
+ super(in);
+ }
+}
diff --git a/src/android/support/v7/mms/MmsHttpClient.java b/src/android/support/v7/mms/MmsHttpClient.java
new file mode 100644
index 0000000..8ca34da
--- /dev/null
+++ b/src/android/support/v7/mms/MmsHttpClient.java
@@ -0,0 +1,523 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * MMS HTTP client for sending and downloading MMS messages
+ */
+public class MmsHttpClient {
+ static final String METHOD_POST = "POST";
+ static final String METHOD_GET = "GET";
+
+ private static final String HEADER_CONTENT_TYPE = "Content-Type";
+ private static final String HEADER_ACCEPT = "Accept";
+ private static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language";
+ private static final String HEADER_USER_AGENT = "User-Agent";
+
+ // The "Accept" header value
+ private static final String HEADER_VALUE_ACCEPT =
+ "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic";
+ // The "Content-Type" header value
+ private static final String HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET =
+ "application/vnd.wap.mms-message; charset=utf-8";
+ private static final String HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET =
+ "application/vnd.wap.mms-message";
+
+ /*
+ * Macro names
+ */
+ // The raw phone number
+ private static final String MACRO_LINE1 = "LINE1";
+ // The phone number without country code
+ private static final String MACRO_LINE1NOCOUNTRYCODE = "LINE1NOCOUNTRYCODE";
+ // NAI (Network Access Identifier)
+ private static final String MACRO_NAI = "NAI";
+
+ // The possible NAI system property name
+ private static final String NAI_PROPERTY = "persist.radio.cdma.nai";
+
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+
+ /**
+ * Constructor
+ *
+ * @param context The Context object
+ */
+ MmsHttpClient(Context context) {
+ mContext = context;
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
+ /**
+ * Execute an MMS HTTP request, either a POST (sending) or a GET (downloading)
+ *
+ * @param urlString The request URL, for sending it is usually the MMSC, and for downloading
+ * it is the message URL
+ * @param pdu For POST (sending) only, the PDU to send
+ * @param method HTTP method, POST for sending and GET for downloading
+ * @param isProxySet Is there a proxy for the MMSC
+ * @param proxyHost The proxy host
+ * @param proxyPort The proxy port
+ * @param mmsConfig The MMS config to use
+ * @param userAgent The user agent header value
+ * @param uaProfUrl The UA Prof URL header value
+ * @return The HTTP response body
+ * @throws MmsHttpException For any failures
+ */
+ public byte[] execute(String urlString, byte[] pdu, String method, boolean isProxySet,
+ String proxyHost, int proxyPort, Bundle mmsConfig, String userAgent, String uaProfUrl)
+ throws MmsHttpException {
+ Log.d(MmsService.TAG, "HTTP: " + method + " " + Utils.redactUrlForNonVerbose(urlString)
+ + (isProxySet ? (", proxy=" + proxyHost + ":" + proxyPort) : "")
+ + ", PDU size=" + (pdu != null ? pdu.length : 0));
+ checkMethod(method);
+ HttpURLConnection connection = null;
+ try {
+ Proxy proxy = Proxy.NO_PROXY;
+ if (isProxySet) {
+ proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
+ }
+ final URL url = new URL(urlString);
+ // Now get the connection
+ connection = (HttpURLConnection) url.openConnection(proxy);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(
+ mmsConfig.getInt(CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT,
+ CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT_DEFAULT));
+ // ------- COMMON HEADERS ---------
+ // Header: Accept
+ connection.setRequestProperty(HEADER_ACCEPT, HEADER_VALUE_ACCEPT);
+ // Header: Accept-Language
+ connection.setRequestProperty(
+ HEADER_ACCEPT_LANGUAGE, getCurrentAcceptLanguage(Locale.getDefault()));
+ // Header: User-Agent
+ Log.i(MmsService.TAG, "HTTP: User-Agent=" + userAgent);
+ connection.setRequestProperty(HEADER_USER_AGENT, userAgent);
+ // Header: x-wap-profile
+ final String uaProfUrlTagName = mmsConfig.getString(
+ CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME,
+ CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME_DEFAULT);
+ if (uaProfUrl != null) {
+ Log.i(MmsService.TAG, "HTTP: UaProfUrl=" + uaProfUrl);
+ connection.setRequestProperty(uaProfUrlTagName, uaProfUrl);
+ }
+ // Add extra headers specified by mms_config.xml's httpparams
+ addExtraHeaders(connection, mmsConfig);
+ // Different stuff for GET and POST
+ if (METHOD_POST.equals(method)) {
+ if (pdu == null || pdu.length < 1) {
+ Log.e(MmsService.TAG, "HTTP: empty pdu");
+ throw new MmsHttpException(0/*statusCode*/, "Sending empty PDU");
+ }
+ connection.setDoOutput(true);
+ connection.setRequestMethod(METHOD_POST);
+ if (mmsConfig.getBoolean(
+ CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+ CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER_DEFAULT)) {
+ connection.setRequestProperty(HEADER_CONTENT_TYPE,
+ HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET);
+ } else {
+ connection.setRequestProperty(HEADER_CONTENT_TYPE,
+ HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET);
+ }
+ if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+ logHttpHeaders(connection.getRequestProperties());
+ }
+ connection.setFixedLengthStreamingMode(pdu.length);
+ // Sending request body
+ final OutputStream out =
+ new BufferedOutputStream(connection.getOutputStream());
+ out.write(pdu);
+ out.flush();
+ out.close();
+ } else if (METHOD_GET.equals(method)) {
+ if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+ logHttpHeaders(connection.getRequestProperties());
+ }
+ connection.setRequestMethod(METHOD_GET);
+ }
+ // Get response
+ final int responseCode = connection.getResponseCode();
+ final String responseMessage = connection.getResponseMessage();
+ Log.d(MmsService.TAG, "HTTP: " + responseCode + " " + responseMessage);
+ if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+ logHttpHeaders(connection.getHeaderFields());
+ }
+ if (responseCode / 100 != 2) {
+ throw new MmsHttpException(responseCode, responseMessage);
+ }
+ final InputStream in = new BufferedInputStream(connection.getInputStream());
+ final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ final byte[] buf = new byte[4096];
+ int count = 0;
+ while ((count = in.read(buf)) > 0) {
+ byteOut.write(buf, 0, count);
+ }
+ in.close();
+ final byte[] responseBody = byteOut.toByteArray();
+ Log.d(MmsService.TAG, "HTTP: response size="
+ + (responseBody != null ? responseBody.length : 0));
+ return responseBody;
+ } catch (MalformedURLException e) {
+ final String redactedUrl = Utils.redactUrlForNonVerbose(urlString);
+ Log.e(MmsService.TAG, "HTTP: invalid URL " + redactedUrl, e);
+ throw new MmsHttpException(0/*statusCode*/, "Invalid URL " + redactedUrl, e);
+ } catch (ProtocolException e) {
+ final String redactedUrl = Utils.redactUrlForNonVerbose(urlString);
+ Log.e(MmsService.TAG, "HTTP: invalid URL protocol " + redactedUrl, e);
+ throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + redactedUrl, e);
+ } catch (IOException e) {
+ Log.e(MmsService.TAG, "HTTP: IO failure", e);
+ throw new MmsHttpException(0/*statusCode*/, e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ private static void logHttpHeaders(Map<String, List<String>> headers) {
+ final StringBuilder sb = new StringBuilder();
+ if (headers != null) {
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ final String key = entry.getKey();
+ final List<String> values = entry.getValue();
+ if (values != null) {
+ for (String value : values) {
+ sb.append(key).append('=').append(value).append('\n');
+ }
+ }
+ }
+ Log.v(MmsService.TAG, "HTTP: headers\n" + sb.toString());
+ }
+ }
+
+ private static void checkMethod(String method) throws MmsHttpException {
+ if (!METHOD_GET.equals(method) && !METHOD_POST.equals(method)) {
+ throw new MmsHttpException(0/*statusCode*/, "Invalid method " + method);
+ }
+ }
+
+ private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
+
+ /**
+ * Return the Accept-Language header. Use the current locale plus
+ * US if we are in a different locale than US.
+ * This code copied from the browser's WebSettings.java
+ *
+ * @return Current AcceptLanguage String.
+ */
+ public static String getCurrentAcceptLanguage(Locale locale) {
+ final StringBuilder buffer = new StringBuilder();
+ addLocaleToHttpAcceptLanguage(buffer, locale);
+
+ if (!Locale.US.equals(locale)) {
+ if (buffer.length() > 0) {
+ buffer.append(", ");
+ }
+ buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
+ * to new standard.
+ */
+ private static String convertObsoleteLanguageCodeToNew(String langCode) {
+ if (langCode == null) {
+ return null;
+ }
+ if ("iw".equals(langCode)) {
+ // Hebrew
+ return "he";
+ } else if ("in".equals(langCode)) {
+ // Indonesian
+ return "id";
+ } else if ("ji".equals(langCode)) {
+ // Yiddish
+ return "yi";
+ }
+ return langCode;
+ }
+
+ private static void addLocaleToHttpAcceptLanguage(StringBuilder builder, Locale locale) {
+ final String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
+ if (language != null) {
+ builder.append(language);
+ final String country = locale.getCountry();
+ if (country != null) {
+ builder.append("-");
+ builder.append(country);
+ }
+ }
+ }
+
+ private static final Pattern MACRO_P = Pattern.compile("##(\\S+)##");
+ /**
+ * Resolve the macro in HTTP param value text
+ * For example, "something##LINE1##something" is resolved to "something9139531419something"
+ *
+ * @param value The HTTP param value possibly containing macros
+ * @return The HTTP param with macro resolved to real value
+ */
+ private String resolveMacro(String value, Bundle mmsConfig) {
+ if (TextUtils.isEmpty(value)) {
+ return value;
+ }
+ final Matcher matcher = MACRO_P.matcher(value);
+ int nextStart = 0;
+ StringBuilder replaced = null;
+ while (matcher.find()) {
+ if (replaced == null) {
+ replaced = new StringBuilder();
+ }
+ final int matchedStart = matcher.start();
+ if (matchedStart > nextStart) {
+ replaced.append(value.substring(nextStart, matchedStart));
+ }
+ final String macro = matcher.group(1);
+ final String macroValue = getHttpParamMacro(macro, mmsConfig);
+ if (macroValue != null) {
+ replaced.append(macroValue);
+ }
+ nextStart = matcher.end();
+ }
+ if (replaced != null && nextStart < value.length()) {
+ replaced.append(value.substring(nextStart));
+ }
+ return replaced == null ? value : replaced.toString();
+ }
+
+ /**
+ * Add extra HTTP headers from mms_config.xml's httpParams, which is a list of key/value
+ * pairs separated by "|". Each key/value pair is separated by ":". Value may contain
+ * macros like "##LINE1##" or "##NAI##" which is resolved with methods in this class
+ *
+ * @param connection The HttpURLConnection that we add headers to
+ * @param mmsConfig The MmsConfig object
+ */
+ private void addExtraHeaders(HttpURLConnection connection, Bundle mmsConfig) {
+ final String extraHttpParams = mmsConfig.getString(
+ CarrierConfigValuesLoader.CONFIG_HTTP_PARAMS);
+ if (!TextUtils.isEmpty(extraHttpParams)) {
+ // Parse the parameter list
+ String paramList[] = extraHttpParams.split("\\|");
+ for (String paramPair : paramList) {
+ String splitPair[] = paramPair.split(":", 2);
+ if (splitPair.length == 2) {
+ final String name = splitPair[0].trim();
+ final String value = resolveMacro(splitPair[1].trim(), mmsConfig);
+ if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) {
+ // Add the header if the param is valid
+ connection.setRequestProperty(name, value);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the HTTP param macro value.
+ * Example: LINE1 returns the phone number, etc.
+ *
+ * @param macro The macro name
+ * @param mmsConfig The carrier configuration values
+ * @return The value of the defined macro
+ */
+ private String getHttpParamMacro(final String macro, final Bundle mmsConfig) {
+ if (MACRO_LINE1.equals(macro)) {
+ return getSelfNumber();
+ } else if (MACRO_LINE1NOCOUNTRYCODE.equals(macro)) {
+ return PhoneNumberHelper.getNumberNoCountryCode(
+ getSelfNumber(), getSimOrLocaleCountry());
+ } else if (MACRO_NAI.equals(macro)) {
+ return getEncodedNai(mmsConfig.getString(
+ CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX,
+ CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX_DEFAULT));
+ }
+ return null;
+ }
+
+ /**
+ * Get the device phone number
+ *
+ * @return the phone number text
+ */
+ private String getSelfNumber() {
+ if (Utils.supportMSim()) {
+ final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
+ final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo(
+ SmsManager.getDefaultSmsSubscriptionId());
+ if (info != null) {
+ return info.getNumber();
+ } else {
+ return null;
+ }
+ } else {
+ return mTelephonyManager.getLine1Number();
+ }
+ }
+
+ /**
+ * Get the country ISO code from SIM or system locale
+ *
+ * @return the country ISO code
+ */
+ private String getSimOrLocaleCountry() {
+ String country = null;
+ if (Utils.supportMSim()) {
+ final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
+ final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo(
+ SmsManager.getDefaultSmsSubscriptionId());
+ if (info != null) {
+ country = info.getCountryIso();
+ }
+ } else {
+ country = mTelephonyManager.getSimCountryIso();
+ }
+ if (!TextUtils.isEmpty(country)) {
+ return country.toUpperCase();
+ } else {
+ return Locale.getDefault().getCountry();
+ }
+ }
+
+ /**
+ * Get encoded NAI string to use as the HTTP header for some carriers.
+ * On L-MR1+, we call the hidden system API to get this
+ * On L-MR1-, we try to find it via system property.
+ *
+ * @param naiSuffix the suffix to append to NAI before encoding
+ * @return the Base64 encoded NAI string to use as HTTP header
+ */
+ private String getEncodedNai(final String naiSuffix) {
+ String nai;
+ if (Utils.supportMSim()) {
+ nai = getNaiBySystemApi(
+ getSlotId(Utils.getEffectiveSubscriptionId(MmsManager.DEFAULT_SUB_ID)));
+ } else {
+ nai = getNaiBySystemProperty();
+ }
+ if (!TextUtils.isEmpty(nai)) {
+ Log.i(MmsService.TAG, "NAI is not empty");
+ if (!TextUtils.isEmpty(naiSuffix)) {
+ nai = nai + naiSuffix;
+ }
+ byte[] encoded = null;
+ try {
+ encoded = Base64.encode(nai.getBytes("UTF-8"), Base64.NO_WRAP);
+ } catch (UnsupportedEncodingException e) {
+ encoded = Base64.encode(nai.getBytes(), Base64.NO_WRAP);
+ }
+ try {
+ return new String(encoded, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return new String(encoded);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Invoke hidden SubscriptionManager.getSlotId(int)
+ *
+ * @param subId the subId
+ * @return the SIM slot ID
+ */
+ private static int getSlotId(final int subId) {
+ try {
+ final Method method = SubscriptionManager.class.getMethod("getSlotId", Integer.TYPE);
+ if (method != null) {
+ return (Integer) method.invoke(null, subId);
+ }
+ } catch (Exception e) {
+ Log.w(MmsService.TAG, "SubscriptionManager.getSlotId failed " + e);
+ }
+ return -1;
+ }
+
+ /**
+ * Get NAI using hidden TelephonyManager.getNai(int)
+ *
+ * @param slotId the SIM slot ID
+ * @return the NAI string
+ */
+ private String getNaiBySystemApi(final int slotId) {
+ try {
+ final Method method = mTelephonyManager.getClass().getMethod("getNai", Integer.TYPE);
+ if (method != null) {
+ return (String) method.invoke(mTelephonyManager, slotId);
+ }
+ } catch (Exception e) {
+ Log.w(MmsService.TAG, "TelephonyManager.getNai failed " + e);
+ }
+ return null;
+ }
+
+ /**
+ * Get NAI using hidden SystemProperties.get(String)
+ *
+ * @return the NAI string as system property
+ */
+ private static String getNaiBySystemProperty() {
+ try {
+ final Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
+ if (systemPropertiesClass != null) {
+ final Method method = systemPropertiesClass.getMethod("get", String.class);
+ if (method != null) {
+ return (String) method.invoke(null, NAI_PROPERTY);
+ }
+ }
+ } catch (Exception e) {
+ Log.w(MmsService.TAG, "SystemProperties.get failed " + e);
+ }
+ return null;
+ }
+}
diff --git a/src/android/support/v7/mms/MmsHttpException.java b/src/android/support/v7/mms/MmsHttpException.java
new file mode 100644
index 0000000..913ba55
--- /dev/null
+++ b/src/android/support/v7/mms/MmsHttpException.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.support.v7.mms;
+
+/**
+ * HTTP exception
+ */
+public class MmsHttpException extends Exception {
+ // Optional HTTP status code. 0 means ignore. Otherwise this
+ // should be a valid HTTP status code.
+ private final int mStatusCode;
+
+ public MmsHttpException(int statusCode) {
+ super();
+ mStatusCode = statusCode;
+ }
+
+ public MmsHttpException(int statusCode, String message) {
+ super(message);
+ mStatusCode = statusCode;
+ }
+
+ public MmsHttpException(int statusCode, Throwable cause) {
+ super(cause);
+ mStatusCode = statusCode;
+ }
+
+ public MmsHttpException(int statusCode, String message, Throwable cause) {
+ super(message, cause);
+ mStatusCode = statusCode;
+ }
+
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+}
diff --git a/src/android/support/v7/mms/MmsManager.java b/src/android/support/v7/mms/MmsManager.java
new file mode 100644
index 0000000..f9c0a91
--- /dev/null
+++ b/src/android/support/v7/mms/MmsManager.java
@@ -0,0 +1,256 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.util.SparseArray;
+
+/**
+ * The public interface of MMS library
+ */
+public class MmsManager {
+ /**
+ * Default subscription ID
+ */
+ public static final int DEFAULT_SUB_ID = -1;
+
+ // Whether to force legacy MMS sending
+ private static volatile boolean sForceLegacyMms = false;
+
+ // Cached computed overrides for carrier configuration values
+ private static SparseArray<Bundle> sConfigOverridesMap = new SparseArray<>();
+
+ /**
+ * Set the flag about whether to force to use legacy system APIs instead of system MMS API
+ *
+ * @param forceLegacyMms value to set
+ */
+ public static void setForceLegacyMms(boolean forceLegacyMms) {
+ sForceLegacyMms = forceLegacyMms;
+ }
+
+ /**
+ * Set the size of thread pool for request execution.
+ *
+ * Default is 4
+ *
+ * Note: if system MMS API is used, this has no effect
+ *
+ * @param size thread pool size
+ */
+ public static void setThreadPoolSize(int size) {
+ MmsService.setThreadPoolSize(size);
+ }
+
+ /**
+ * Set whether to use wake lock while sending or downloading MMS.
+ *
+ * Default value is true
+ *
+ * Note: if system MMS API is used, this has no effect
+ *
+ * @param useWakeLock true to use wake lock, false otherwise
+ */
+ public static void setUseWakeLock(final boolean useWakeLock) {
+ MmsService.setUseWakeLock(useWakeLock);
+ }
+
+ /**
+ * Set the optional carrier config values loader
+ *
+ * Note: if system MMS API is used, this is used to compute the overrides
+ * of carrier configuration values
+ *
+ * @param loader the carrier config values loader
+ */
+ public static void setCarrierConfigValuesLoader(CarrierConfigValuesLoader loader) {
+ if (loader == null) {
+ throw new IllegalArgumentException("Carrier configuration loader can not be empty");
+ }
+ synchronized (sConfigOverridesMap) {
+ MmsService.setCarrierConfigValuesLoader(loader);
+ sConfigOverridesMap.clear();
+ }
+ }
+
+ /**
+ * Set the optional APN settings loader
+ *
+ * Note: if system MMS API is used, this has no effect
+ *
+ * @param loader the APN settings loader
+ */
+ public static void setApnSettingsLoader(ApnSettingsLoader loader) {
+ if (loader == null) {
+ throw new IllegalArgumentException("APN settings loader can not be empty");
+ }
+ MmsService.setApnSettingsLoader(loader);
+ }
+
+ /**
+ * Set user agent info loader
+ *
+ * Note: if system MMS API is used, this is used to compute the overrides
+ * of carrier configuration values
+
+ * @param loader the user agent info loader
+ */
+ public static void setUserAgentInfoLoader(final UserAgentInfoLoader loader) {
+ if (loader == null) {
+ throw new IllegalArgumentException("User agent info loader can not be empty");
+ }
+ synchronized (sConfigOverridesMap) {
+ MmsService.setUserAgentInfoLoader(loader);
+ sConfigOverridesMap.clear();
+ }
+ }
+
+ /**
+ * Send MMS via platform MMS API (if platform supports and not forced to
+ * use legacy APIs) or legacy APIs
+ *
+ * @param subId the subscription ID of the SIM to use
+ * @param context the Context to use
+ * @param contentUri the content URI of the PDU to be sent
+ * @param locationUrl the optional location URL to use for sending
+ * @param sentIntent the pending intent for returning results
+ */
+ public static void sendMultimediaMessage(int subId, Context context, Uri contentUri,
+ String locationUrl, PendingIntent sentIntent) {
+ if (Utils.hasMmsApi() && !sForceLegacyMms) {
+ subId = Utils.getEffectiveSubscriptionId(subId);
+ final SmsManager smsManager = Utils.getSmsManager(subId);
+ smsManager.sendMultimediaMessage(context, contentUri, locationUrl,
+ getConfigOverrides(subId), sentIntent);
+ } else {
+ MmsService.startRequest(context, new SendRequest(locationUrl, contentUri, sentIntent));
+ }
+ }
+
+ /**
+ * Download MMS via platform MMS API (if platform supports and not forced to
+ * use legacy APIs) or legacy APIs
+ *
+ * @param subId the subscription ID of the SIM to use
+ * @param context the Context to use
+ * @param contentUri the content URI of the PDU to be sent
+ * @param locationUrl the optional location URL to use for sending
+ * @param downloadedIntent the pending intent for returning results
+ */
+ public static void downloadMultimediaMessage(int subId, Context context, String locationUrl,
+ Uri contentUri, PendingIntent downloadedIntent) {
+ if (Utils.hasMmsApi() && !sForceLegacyMms) {
+ subId = Utils.getEffectiveSubscriptionId(subId);
+ final SmsManager smsManager = Utils.getSmsManager(subId);
+ smsManager.downloadMultimediaMessage(context, locationUrl, contentUri,
+ getConfigOverrides(subId), downloadedIntent);
+ } else {
+ MmsService.startRequest(context,
+ new DownloadRequest(locationUrl, contentUri, downloadedIntent));
+ }
+ }
+
+ /**
+ * Get carrier configuration values overrides when platform MMS API is called.
+ * We only need to compute this if customized carrier config values loader or
+ * user agent info loader are set
+ *
+ * @param subId the ID of the SIM to use
+ * @return a Bundle containing the overrides
+ */
+ private static Bundle getConfigOverrides(final int subId) {
+ if (!Utils.hasMmsApi()) {
+ // If MMS API is not present, it is not necessary to compute overrides
+ return null;
+ }
+ Bundle overrides = null;
+ synchronized (sConfigOverridesMap) {
+ overrides = sConfigOverridesMap.get(subId);
+ if (overrides == null) {
+ overrides = new Bundle();
+ sConfigOverridesMap.put(subId, overrides);
+ computeOverridesLocked(subId, overrides);
+ }
+ }
+ return overrides;
+ }
+
+ /**
+ * Compute the overrides, incorporating the user agent info
+ *
+ * @param subId the subId of the SIM to use
+ * @param overrides the computed values overrides
+ */
+ private static void computeOverridesLocked(final int subId, final Bundle overrides) {
+ // Overrides not computed yet
+ final CarrierConfigValuesLoader carrierConfigValuesLoader =
+ MmsService.getCarrierConfigValuesLoader();
+ if (carrierConfigValuesLoader != null &&
+ !(carrierConfigValuesLoader instanceof DefaultCarrierConfigValuesLoader)) {
+ // Compute the overrides for carrier config values first if the config loader
+ // is not the default one.
+ final Bundle systemValues = Utils.getSmsManager(subId).getCarrierConfigValues();
+ final Bundle callerValues =
+ MmsService.getCarrierConfigValuesLoader().get(subId);
+ if (systemValues != null && callerValues != null) {
+ computeConfigDelta(systemValues, callerValues, overrides);
+ } else if (systemValues == null && callerValues != null) {
+ overrides.putAll(callerValues);
+ }
+ }
+ final UserAgentInfoLoader userAgentInfoLoader = MmsService.getUserAgentInfoLoader();
+ if (userAgentInfoLoader != null &&
+ !(userAgentInfoLoader instanceof DefaultUserAgentInfoLoader)) {
+ // Also set the user agent and ua prof url via the overrides
+ // if the user agent loader is not the default one.
+ overrides.putString(UserAgentInfoLoader.CONFIG_USER_AGENT,
+ userAgentInfoLoader.getUserAgent());
+ overrides.putString(UserAgentInfoLoader.CONFIG_UA_PROF_URL,
+ userAgentInfoLoader.getUAProfUrl());
+ }
+ }
+
+ /**
+ * Compute the delta between two sets of carrier configuration values: system and caller
+ *
+ * @param systemValues the system config values
+ * @param callerValues the caller's config values
+ * @param delta the delta of values (caller - system), using caller value to override system's
+ */
+ private static void computeConfigDelta(final Bundle systemValues, final Bundle callerValues,
+ final Bundle delta) {
+ for (final String key : callerValues.keySet()) {
+ final Object callerValue = callerValues.get(key);
+ final Object systemValue = systemValues.get(key);
+ if ((callerValue != null && systemValue != null && !callerValue.equals(systemValue)) ||
+ (callerValue != null && systemValue == null) ||
+ (callerValue == null && systemValue != null)) {
+ if (callerValue == null || callerValue instanceof String) {
+ delta.putString(key, (String) callerValue);
+ } else if (callerValue instanceof Integer) {
+ delta.putInt(key, (Integer) callerValue);
+ } else if (callerValue instanceof Boolean) {
+ delta.putBoolean(key, (Boolean) callerValue);
+ }
+ }
+ }
+ }
+}
diff --git a/src/android/support/v7/mms/MmsNetworkException.java b/src/android/support/v7/mms/MmsNetworkException.java
new file mode 100644
index 0000000..776d8ee
--- /dev/null
+++ b/src/android/support/v7/mms/MmsNetworkException.java
@@ -0,0 +1,39 @@
+/*
+ * 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 android.support.v7.mms;
+
+/**
+ * MMS network exception
+ */
+class MmsNetworkException extends Exception {
+
+ public MmsNetworkException() {
+ super();
+ }
+
+ public MmsNetworkException(String message) {
+ super(message);
+ }
+
+ public MmsNetworkException(Throwable cause) {
+ super(cause);
+ }
+
+ public MmsNetworkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/android/support/v7/mms/MmsNetworkManager.java b/src/android/support/v7/mms/MmsNetworkManager.java
new file mode 100644
index 0000000..0630293
--- /dev/null
+++ b/src/android/support/v7/mms/MmsNetworkManager.java
@@ -0,0 +1,382 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Class manages MMS network connectivity using legacy platform APIs
+ * (deprecated since Android L) on pre-L devices (or when forced to
+ * be used on L and later)
+ */
+class MmsNetworkManager {
+ // Hidden platform constants
+ private static final String FEATURE_ENABLE_MMS = "enableMMS";
+ private static final String REASON_VOICE_CALL_ENDED = "2GVoiceCallEnded";
+ private static final int APN_ALREADY_ACTIVE = 0;
+ private static final int APN_REQUEST_STARTED = 1;
+ private static final int APN_TYPE_NOT_AVAILABLE = 2;
+ private static final int APN_REQUEST_FAILED = 3;
+ private static final int APN_ALREADY_INACTIVE = 4;
+ // A map from platform APN constant to text string
+ private static final String[] APN_RESULT_STRING = new String[]{
+ "already active",
+ "request started",
+ "type not available",
+ "request failed",
+ "already inactive",
+ "unknown",
+ };
+
+ private static final long NETWORK_ACQUIRE_WAIT_INTERVAL_MS = 15000;
+ private static final long DEFAULT_NETWORK_ACQUIRE_TIMEOUT_MS = 180000;
+ private static final String MMS_NETWORK_EXTENSION_TIMER = "mms_network_extension_timer";
+ private static final long MMS_NETWORK_EXTENSION_TIMER_WAIT_MS = 30000;
+
+ private static volatile long sNetworkAcquireTimeoutMs = DEFAULT_NETWORK_ACQUIRE_TIMEOUT_MS;
+
+ /**
+ * Set the network acquire timeout
+ *
+ * @param timeoutMs timeout in millisecond
+ */
+ static void setNetworkAcquireTimeout(final long timeoutMs) {
+ sNetworkAcquireTimeoutMs = timeoutMs;
+ }
+
+ private final Context mContext;
+ private final ConnectivityManager mConnectivityManager;
+
+ // If the connectivity intent receiver is registered
+ private boolean mReceiverRegistered;
+ // Count of requests that are using the MMS network
+ private int mUseCount;
+ // Count of requests that are waiting for connectivity (i.e. in acquireNetwork wait loop)
+ private int mWaitCount;
+ // Timer to extend the network connectivity
+ private Timer mExtensionTimer;
+
+ private final MmsHttpClient mHttpClient;
+
+ private final IntentFilter mConnectivityIntentFilter;
+ private final BroadcastReceiver mConnectivityChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (!ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
+ return;
+ }
+ final int networkType = getConnectivityChangeNetworkType(intent);
+ if (networkType != ConnectivityManager.TYPE_MOBILE_MMS) {
+ return;
+ }
+ onMmsConnectivityChange(context, intent);
+ }
+ };
+
+ MmsNetworkManager(final Context context) {
+ mContext = context;
+ mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mHttpClient = new MmsHttpClient(mContext);
+ mConnectivityIntentFilter = new IntentFilter();
+ mConnectivityIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mUseCount = 0;
+ mWaitCount = 0;
+ }
+
+ ConnectivityManager getConnectivityManager() {
+ return mConnectivityManager;
+ }
+
+ MmsHttpClient getHttpClient() {
+ return mHttpClient;
+ }
+
+ /**
+ * Synchronously acquire MMS network connectivity
+ *
+ * @throws MmsNetworkException If failed permanently or timed out
+ */
+ void acquireNetwork() throws MmsNetworkException {
+ Log.i(MmsService.TAG, "Acquire MMS network");
+ synchronized (this) {
+ try {
+ mUseCount++;
+ mWaitCount++;
+ if (mWaitCount == 1) {
+ // Register the receiver for the first waiting request
+ registerConnectivityChangeReceiverLocked();
+ }
+ long waitMs = sNetworkAcquireTimeoutMs;
+ final long beginMs = SystemClock.elapsedRealtime();
+ do {
+ if (!isMobileDataEnabled()) {
+ // Fast fail if mobile data is not enabled
+ throw new MmsNetworkException("Mobile data is disabled");
+ }
+ // Always try to extend and check the MMS network connectivity
+ // before we start waiting to make sure we don't miss the change
+ // of MMS connectivity. As one example, some devices fail to send
+ // connectivity change intent. So this would make sure we catch
+ // the state change.
+ if (extendMmsConnectivityLocked()) {
+ // Connected
+ return;
+ }
+ try {
+ wait(Math.min(waitMs, NETWORK_ACQUIRE_WAIT_INTERVAL_MS));
+ } catch (final InterruptedException e) {
+ Log.w(MmsService.TAG, "Unexpected exception", e);
+ }
+ // Calculate the remaining time to wait
+ waitMs = sNetworkAcquireTimeoutMs - (SystemClock.elapsedRealtime() - beginMs);
+ } while (waitMs > 0);
+ // Last check
+ if (extendMmsConnectivityLocked()) {
+ return;
+ } else {
+ // Reaching here means timed out.
+ throw new MmsNetworkException("Acquiring MMS network timed out");
+ }
+ } finally {
+ mWaitCount--;
+ if (mWaitCount == 0) {
+ // Receiver is used to listen to connectivity change and unblock
+ // the waiting requests. If nobody's waiting on change, there is
+ // no need for the receiver. The auto extension timer will try
+ // to maintain the connectivity periodically.
+ unregisterConnectivityChangeReceiverLocked();
+ }
+ }
+ }
+ }
+
+ /**
+ * Release MMS network connectivity. This is ref counted. So it only disconnect
+ * when the ref count is 0.
+ */
+ void releaseNetwork() {
+ Log.i(MmsService.TAG, "release MMS network");
+ synchronized (this) {
+ mUseCount--;
+ if (mUseCount == 0) {
+ stopNetworkExtensionTimerLocked();
+ endMmsConnectivity();
+ }
+ }
+ }
+
+ String getApnName() {
+ String apnName = null;
+ final NetworkInfo mmsNetworkInfo = mConnectivityManager.getNetworkInfo(
+ ConnectivityManager.TYPE_MOBILE_MMS);
+ if (mmsNetworkInfo != null) {
+ apnName = mmsNetworkInfo.getExtraInfo();
+ }
+ return apnName;
+ }
+
+ // Process mobile MMS connectivity change, waking up the waiting request thread
+ // in certain conditions:
+ // - Successfully connected
+ // - Failed permanently
+ // - Required another kickoff
+ // We don't initiate connection here but just notifyAll so the waiting request
+ // would wake up and retry connection before next wait.
+ private void onMmsConnectivityChange(final Context context, final Intent intent) {
+ if (mUseCount < 1) {
+ return;
+ }
+ final NetworkInfo mmsNetworkInfo =
+ mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS);
+ // Check availability of the mobile network.
+ if (mmsNetworkInfo != null) {
+ if (REASON_VOICE_CALL_ENDED.equals(mmsNetworkInfo.getReason())) {
+ // This is a very specific fix to handle the case where the phone receives an
+ // incoming call during the time we're trying to setup the mms connection.
+ // When the call ends, restart the process of mms connectivity.
+ // Once the waiting request is unblocked, before the next wait, we would start
+ // MMS network again.
+ unblockWait();
+ } else {
+ final NetworkInfo.State state = mmsNetworkInfo.getState();
+ if (state == NetworkInfo.State.CONNECTED ||
+ (state == NetworkInfo.State.DISCONNECTED && !isMobileDataEnabled())) {
+ // Unblock the waiting request when we either connected
+ // OR
+ // disconnected due to mobile data disabled therefore needs to fast fail
+ // (on some devices if mobile data disabled and starting MMS would cause
+ // an immediate state change to disconnected, so causing a tight loop of
+ // trying and failing)
+ // Once the waiting request is unblocked, before the next wait, we would
+ // check mobile data and start MMS network again. So we should catch
+ // both the success and the fast failure.
+ unblockWait();
+ }
+ }
+ }
+ }
+
+ private void unblockWait() {
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ private void startNetworkExtensionTimerLocked() {
+ if (mExtensionTimer == null) {
+ mExtensionTimer = new Timer(MMS_NETWORK_EXTENSION_TIMER, true/*daemon*/);
+ mExtensionTimer.schedule(
+ new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (this) {
+ if (mUseCount > 0) {
+ try {
+ // Try extending the connectivity
+ extendMmsConnectivityLocked();
+ } catch (final MmsNetworkException e) {
+ // Ignore the exception
+ }
+ }
+ }
+ }
+ },
+ MMS_NETWORK_EXTENSION_TIMER_WAIT_MS);
+ }
+ }
+
+ private void stopNetworkExtensionTimerLocked() {
+ if (mExtensionTimer != null) {
+ mExtensionTimer.cancel();
+ mExtensionTimer = null;
+ }
+ }
+
+ private boolean extendMmsConnectivityLocked() throws MmsNetworkException {
+ final int result = startMmsConnectivity();
+ if (result == APN_ALREADY_ACTIVE) {
+ // Already active
+ startNetworkExtensionTimerLocked();
+ return true;
+ } else if (result != APN_REQUEST_STARTED) {
+ stopNetworkExtensionTimerLocked();
+ throw new MmsNetworkException("Cannot acquire MMS network: " +
+ result + " - " + getMmsConnectivityResultString(result));
+ }
+ return false;
+ }
+
+ private int startMmsConnectivity() {
+ Log.i(MmsService.TAG, "Start MMS connectivity");
+ try {
+ final Method method = mConnectivityManager.getClass().getMethod(
+ "startUsingNetworkFeature", Integer.TYPE, String.class);
+ if (method != null) {
+ return (Integer) method.invoke(
+ mConnectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
+ }
+ } catch (final Exception e) {
+ Log.w(MmsService.TAG, "ConnectivityManager.startUsingNetworkFeature failed " + e);
+ }
+ return APN_REQUEST_FAILED;
+ }
+
+ private void endMmsConnectivity() {
+ Log.i(MmsService.TAG, "End MMS connectivity");
+ try {
+ final Method method = mConnectivityManager.getClass().getMethod(
+ "stopUsingNetworkFeature", Integer.TYPE, String.class);
+ if (method != null) {
+ method.invoke(
+ mConnectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
+ }
+ } catch (final Exception e) {
+ Log.w(MmsService.TAG, "ConnectivityManager.stopUsingNetworkFeature failed " + e);
+ }
+ }
+
+ private void registerConnectivityChangeReceiverLocked() {
+ if (!mReceiverRegistered) {
+ mContext.registerReceiver(mConnectivityChangeReceiver, mConnectivityIntentFilter);
+ mReceiverRegistered = true;
+ }
+ }
+
+ private void unregisterConnectivityChangeReceiverLocked() {
+ if (mReceiverRegistered) {
+ mContext.unregisterReceiver(mConnectivityChangeReceiver);
+ mReceiverRegistered = false;
+ }
+ }
+
+ /**
+ * The absence of a connection type.
+ */
+ private static final int TYPE_NONE = -1;
+
+ /**
+ * Get the network type of the connectivity change
+ *
+ * @param intent the broadcast intent of connectivity change
+ * @return The change's network type
+ */
+ private static int getConnectivityChangeNetworkType(final Intent intent) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, TYPE_NONE);
+ } else {
+ final NetworkInfo info = intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ if (info != null) {
+ return info.getType();
+ }
+ }
+ return TYPE_NONE;
+ }
+
+ private static String getMmsConnectivityResultString(int result) {
+ if (result < 0 || result >= APN_RESULT_STRING.length) {
+ result = APN_RESULT_STRING.length - 1;
+ }
+ return APN_RESULT_STRING[result];
+ }
+
+ private boolean isMobileDataEnabled() {
+ try {
+ final Class cmClass = mConnectivityManager.getClass();
+ final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled");
+ method.setAccessible(true); // Make the method callable
+ // get the setting for "mobile data"
+ return (Boolean) method.invoke(mConnectivityManager);
+ } catch (final Exception e) {
+ Log.w(MmsService.TAG, "TelephonyManager.getMobileDataEnabled failed", e);
+ }
+ return false;
+ }
+}
diff --git a/src/android/support/v7/mms/MmsRequest.java b/src/android/support/v7/mms/MmsRequest.java
new file mode 100644
index 0000000..edf3606
--- /dev/null
+++ b/src/android/support/v7/mms/MmsRequest.java
@@ -0,0 +1,391 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v7.mms.pdu.GenericPdu;
+import android.support.v7.mms.pdu.PduHeaders;
+import android.support.v7.mms.pdu.PduParser;
+import android.support.v7.mms.pdu.SendConf;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * MMS request base class. This handles the execution of any MMS request.
+ */
+abstract class MmsRequest implements Parcelable {
+ /**
+ * Prepare to make the HTTP request - will download message for sending
+ *
+ * @param context the Context
+ * @param mmsConfig carrier config values to use
+ * @return true if loading request PDU from calling app succeeds, false otherwise
+ */
+ protected abstract boolean loadRequest(Context context, Bundle mmsConfig);
+
+ /**
+ * Transfer the received response to the caller
+ *
+ * @param context the Context
+ * @param fillIn the content of pending intent to be returned
+ * @param response the pdu to transfer
+ * @return true if transferring response PDU to calling app succeeds, false otherwise
+ */
+ protected abstract boolean transferResponse(Context context, Intent fillIn, byte[] response);
+
+ /**
+ * Making the HTTP request to MMSC
+ *
+ * @param context The context
+ * @param netMgr The current {@link MmsNetworkManager}
+ * @param apn The APN
+ * @param mmsConfig The carrier configuration values to use
+ * @param userAgent The User-Agent header value
+ * @param uaProfUrl The UA Prof URL header value
+ * @return The HTTP response data
+ * @throws MmsHttpException If any network error happens
+ */
+ protected abstract byte[] doHttp(Context context, MmsNetworkManager netMgr,
+ ApnSettingsLoader.Apn apn, Bundle mmsConfig, String userAgent, String uaProfUrl)
+ throws MmsHttpException;
+
+ /**
+ * Get the HTTP request URL for this MMS request
+ *
+ * @param apn The APN to use
+ * @return The HTTP request URL in text
+ */
+ protected abstract String getHttpRequestUrl(ApnSettingsLoader.Apn apn);
+
+ // Maximum time to spend waiting to read data from a content provider before failing with error.
+ protected static final int TASK_TIMEOUT_MS = 30 * 1000;
+
+ protected final String mLocationUrl;
+ protected final Uri mPduUri;
+ protected final PendingIntent mPendingIntent;
+ // Thread pool for transferring PDU with MMS apps
+ protected final ExecutorService mPduTransferExecutor = Executors.newCachedThreadPool();
+
+ // Whether this request should acquire wake lock
+ private boolean mUseWakeLock;
+
+ protected MmsRequest(final String locationUrl, final Uri pduUri,
+ final PendingIntent pendingIntent) {
+ mLocationUrl = locationUrl;
+ mPduUri = pduUri;
+ mPendingIntent = pendingIntent;
+ mUseWakeLock = true;
+ }
+
+ void setUseWakeLock(final boolean useWakeLock) {
+ mUseWakeLock = useWakeLock;
+ }
+
+ boolean getUseWakeLock() {
+ return mUseWakeLock;
+ }
+
+ /**
+ * Run the MMS request.
+ *
+ * @param context the context to use
+ * @param networkManager the MmsNetworkManager to use to setup MMS network
+ * @param apnSettingsLoader the APN loader
+ * @param carrierConfigValuesLoader the carrier config loader
+ * @param userAgentInfoLoader the user agent info loader
+ */
+ void execute(final Context context, final MmsNetworkManager networkManager,
+ final ApnSettingsLoader apnSettingsLoader,
+ final CarrierConfigValuesLoader carrierConfigValuesLoader,
+ final UserAgentInfoLoader userAgentInfoLoader) {
+ Log.i(MmsService.TAG, "Execute " + this.getClass().getSimpleName());
+ int result = SmsManager.MMS_ERROR_UNSPECIFIED;
+ int httpStatusCode = 0;
+ byte[] response = null;
+ final Bundle mmsConfig = carrierConfigValuesLoader.get(MmsManager.DEFAULT_SUB_ID);
+ if (mmsConfig == null) {
+ Log.e(MmsService.TAG, "Failed to load carrier configuration values");
+ result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR;
+ } else if (!loadRequest(context, mmsConfig)) {
+ Log.e(MmsService.TAG, "Failed to load PDU");
+ result = SmsManager.MMS_ERROR_IO_ERROR;
+ } else {
+ // Everything's OK. Now execute the request.
+ try {
+ // Acquire the MMS network
+ networkManager.acquireNetwork();
+ // Load the potential APNs. In most cases there should be only one APN available.
+ // On some devices on which we can't obtain APN from system, we look up our own
+ // APN list. Since we don't have exact information, we may get a list of potential
+ // APNs to try. Whenever we found a successful APN, we signal it and return.
+ final String apnName = networkManager.getApnName();
+ final List<ApnSettingsLoader.Apn> apns = apnSettingsLoader.get(apnName);
+ if (apns.size() < 1) {
+ throw new ApnException("No valid APN");
+ } else {
+ Log.d(MmsService.TAG, "Trying " + apns.size() + " APNs");
+ }
+ final String userAgent = userAgentInfoLoader.getUserAgent();
+ final String uaProfUrl = userAgentInfoLoader.getUAProfUrl();
+ MmsHttpException lastException = null;
+ for (ApnSettingsLoader.Apn apn : apns) {
+ Log.i(MmsService.TAG, "Using APN ["
+ + "MMSC=" + apn.getMmsc() + ", "
+ + "PROXY=" + apn.getMmsProxy() + ", "
+ + "PORT=" + apn.getMmsProxyPort() + "]");
+ try {
+ final String url = getHttpRequestUrl(apn);
+ // Request a global route for the host to connect
+ requestRoute(networkManager.getConnectivityManager(), apn, url);
+ // Perform the HTTP request
+ response = doHttp(
+ context, networkManager, apn, mmsConfig, userAgent, uaProfUrl);
+ // Additional check of whether this is a success
+ if (isWrongApnResponse(response, mmsConfig)) {
+ throw new MmsHttpException(0/*statusCode*/, "Invalid sending address");
+ }
+ // Notify APN loader this is a valid APN
+ apn.setSuccess();
+ result = Activity.RESULT_OK;
+ break;
+ } catch (MmsHttpException e) {
+ Log.w(MmsService.TAG, "HTTP or network failure", e);
+ lastException = e;
+ }
+ }
+ if (lastException != null) {
+ throw lastException;
+ }
+ } catch (ApnException e) {
+ Log.e(MmsService.TAG, "MmsRequest: APN failure", e);
+ result = SmsManager.MMS_ERROR_INVALID_APN;
+ } catch (MmsNetworkException e) {
+ Log.e(MmsService.TAG, "MmsRequest: MMS network acquiring failure", e);
+ result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS;
+ } catch (MmsHttpException e) {
+ Log.e(MmsService.TAG, "MmsRequest: HTTP or network I/O failure", e);
+ result = SmsManager.MMS_ERROR_HTTP_FAILURE;
+ httpStatusCode = e.getStatusCode();
+ } catch (Exception e) {
+ Log.e(MmsService.TAG, "MmsRequest: unexpected failure", e);
+ result = SmsManager.MMS_ERROR_UNSPECIFIED;
+ } finally {
+ // Release MMS network
+ networkManager.releaseNetwork();
+ }
+ }
+ // Process result and send back via PendingIntent
+ returnResult(context, result, response, httpStatusCode);
+ }
+
+ /**
+ * Check if the response indicates a failure when we send to wrong APN.
+ * Sometimes even if you send to the wrong APN, a response in valid PDU format can still
+ * be sent back but with an error status. Check one specific case here.
+ *
+ * TODO: maybe there are other possibilities.
+ *
+ * @param response the response data
+ * @param mmsConfig the carrier configuration values to use
+ * @return false if we find an invalid response case, otherwise true
+ */
+ static boolean isWrongApnResponse(final byte[] response, final Bundle mmsConfig) {
+ if (response != null && response.length > 0) {
+ try {
+ final GenericPdu pdu = new PduParser(
+ response,
+ mmsConfig.getBoolean(
+ CarrierConfigValuesLoader
+ .CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+ CarrierConfigValuesLoader
+ .CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT))
+ .parse();
+ if (pdu != null && pdu instanceof SendConf) {
+ final SendConf sendConf = (SendConf) pdu;
+ final int responseStatus = sendConf.getResponseStatus();
+ return responseStatus ==
+ PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED ||
+ responseStatus ==
+ PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED;
+ }
+ } catch (RuntimeException e) {
+ Log.w(MmsService.TAG, "Parsing response failed", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the result back via pending intent
+ *
+ * @param context The context
+ * @param result The result code of execution
+ * @param response The response body
+ * @param httpStatusCode The optional http status code in case of http failure
+ */
+ void returnResult(final Context context, int result, final byte[] response,
+ final int httpStatusCode) {
+ if (mPendingIntent == null) {
+ // Result not needed
+ return;
+ }
+ // Extra information to send back with the pending intent
+ final Intent fillIn = new Intent();
+ if (response != null) {
+ if (!transferResponse(context, fillIn, response)) {
+ // Failed to send PDU data back to caller
+ result = SmsManager.MMS_ERROR_IO_ERROR;
+ }
+ }
+ if (result == SmsManager.MMS_ERROR_HTTP_FAILURE && httpStatusCode != 0) {
+ // For HTTP failure, fill in the status code for more information
+ fillIn.putExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, httpStatusCode);
+ }
+ try {
+ mPendingIntent.send(context, result, fillIn);
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(MmsService.TAG, "Sending pending intent canceled", e);
+ }
+ }
+
+ /**
+ * Request the route to the APN (either proxy host or the MMSC host)
+ *
+ * @param connectivityManager the ConnectivityManager to use
+ * @param apn the current APN
+ * @param url the URL to connect to
+ * @throws MmsHttpException for unknown host or route failure
+ */
+ private static void requestRoute(final ConnectivityManager connectivityManager,
+ final ApnSettingsLoader.Apn apn, final String url) throws MmsHttpException {
+ String host = apn.getMmsProxy();
+ if (TextUtils.isEmpty(host)) {
+ final Uri uri = Uri.parse(url);
+ host = uri.getHost();
+ }
+ boolean success = false;
+ // Request route to all resolved host addresses
+ try {
+ for (final InetAddress addr : InetAddress.getAllByName(host)) {
+ final boolean requested = requestRouteToHostAddress(connectivityManager, addr);
+ if (requested) {
+ success = true;
+ Log.i(MmsService.TAG, "Requested route to " + addr);
+ } else {
+ Log.i(MmsService.TAG, "Could not requested route to " + addr);
+ }
+ }
+ if (!success) {
+ throw new MmsHttpException(0/*statusCode*/, "No route requested");
+ }
+ } catch (UnknownHostException e) {
+ Log.w(MmsService.TAG, "Unknown host " + host);
+ throw new MmsHttpException(0/*statusCode*/, "Unknown host");
+ }
+ }
+
+ private static final Integer TYPE_MOBILE_MMS =
+ Integer.valueOf(ConnectivityManager.TYPE_MOBILE_MMS);
+ /**
+ * Wrapper for platform API requestRouteToHostAddress
+ *
+ * We first try the hidden but correct method on ConnectivityManager. If we can't, use
+ * the old but buggy one
+ *
+ * @param connMgr the ConnectivityManager instance
+ * @param inetAddr the InetAddress to request
+ * @return true if route is successfully setup, false otherwise
+ */
+ private static boolean requestRouteToHostAddress(final ConnectivityManager connMgr,
+ final InetAddress inetAddr) {
+ // First try the good method using reflection
+ try {
+ final Method method = connMgr.getClass().getMethod("requestRouteToHostAddress",
+ Integer.TYPE, InetAddress.class);
+ if (method != null) {
+ return (Boolean) method.invoke(connMgr, TYPE_MOBILE_MMS, inetAddr);
+ }
+ } catch (Exception e) {
+ Log.w(MmsService.TAG, "ConnectivityManager.requestRouteToHostAddress failed " + e);
+ }
+ // If we fail, try the old but buggy one
+ if (inetAddr instanceof Inet4Address) {
+ try {
+ final Method method = connMgr.getClass().getMethod("requestRouteToHost",
+ Integer.TYPE, Integer.TYPE);
+ if (method != null) {
+ return (Boolean) method.invoke(connMgr, TYPE_MOBILE_MMS,
+ inetAddressToInt(inetAddr));
+ }
+ } catch (Exception e) {
+ Log.w(MmsService.TAG, "ConnectivityManager.requestRouteToHost failed " + e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Convert a IPv4 address from an InetAddress to an integer
+ *
+ * @param inetAddr is an InetAddress corresponding to the IPv4 address
+ * @return the IP address as an integer in network byte order
+ */
+ private static int inetAddressToInt(final InetAddress inetAddr)
+ throws IllegalArgumentException {
+ final byte [] addr = inetAddr.getAddress();
+ return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
+ ((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeByte((byte) (mUseWakeLock ? 1 : 0));
+ parcel.writeString(mLocationUrl);
+ parcel.writeParcelable(mPduUri, 0);
+ parcel.writeParcelable(mPendingIntent, 0);
+ }
+
+ protected MmsRequest(final Parcel in) {
+ final ClassLoader classLoader = MmsRequest.class.getClassLoader();
+ mUseWakeLock = in.readByte() != 0;
+ mLocationUrl = in.readString();
+ mPduUri = in.readParcelable(classLoader);
+ mPendingIntent = in.readParcelable(classLoader);
+ }
+}
diff --git a/src/android/support/v7/mms/MmsService.java b/src/android/support/v7/mms/MmsService.java
new file mode 100644
index 0000000..650562f
--- /dev/null
+++ b/src/android/support/v7/mms/MmsService.java
@@ -0,0 +1,465 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.Process;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Service to execute MMS requests using deprecated legacy APIs on older platform (prior to L)
+ */
+public class MmsService extends Service {
+ static final String TAG = "MmsLib";
+
+ //The default number of threads allowed to run MMS requests
+ private static final int DEFAULT_THREAD_POOL_SIZE = 4;
+ // Delay before stopping the service
+ private static final int SERVICE_STOP_DELAY_MILLIS = 2000;
+
+ private static final String EXTRA_REQUEST = "request";
+ private static final String EXTRA_MYPID = "mypid";
+
+ private static final String WAKELOCK_ID = "mmslib_wakelock";
+
+ /**
+ * Thread pool size for each request queue
+ */
+ private static volatile int sThreadPoolSize = DEFAULT_THREAD_POOL_SIZE;
+
+ /**
+ * Optional wake lock to use
+ */
+ private static volatile boolean sUseWakeLock = true;
+ private static volatile PowerManager.WakeLock sWakeLock = null;
+ private static final Object sWakeLockLock = new Object();
+
+ /**
+ * Carrier configuration values loader
+ */
+ private static volatile CarrierConfigValuesLoader sCarrierConfigValuesLoader = null;
+
+ /**
+ * APN loader
+ */
+ private static volatile ApnSettingsLoader sApnSettingsLoader = null;
+
+ /**
+ * UserAgent and UA Prof URL loader
+ */
+ private static volatile UserAgentInfoLoader sUserAgentInfoLoader = null;
+
+ /**
+ * Set the size of thread pool for request execution.
+ * Default is DEFAULT_THREAD_POOL_SIZE
+ *
+ * @param size thread pool size
+ */
+ static void setThreadPoolSize(final int size) {
+ sThreadPoolSize = size;
+ }
+
+ /**
+ * Set whether to use wake lock
+ *
+ * @param useWakeLock true to use wake lock, false otherwise
+ */
+ static void setUseWakeLock(final boolean useWakeLock) {
+ sUseWakeLock = useWakeLock;
+ }
+
+ /**
+ * Set the optional carrier config values
+ *
+ * @param loader the carrier config values loader
+ */
+ static void setCarrierConfigValuesLoader(final CarrierConfigValuesLoader loader) {
+ sCarrierConfigValuesLoader = loader;
+ }
+
+ /**
+ * Get the current carrier config values loader
+ *
+ * @return the carrier config values loader currently set
+ */
+ static CarrierConfigValuesLoader getCarrierConfigValuesLoader() {
+ return sCarrierConfigValuesLoader;
+ }
+
+ /**
+ * Set APN settings loader
+ *
+ * @param loader the APN settings loader
+ */
+ static void setApnSettingsLoader(final ApnSettingsLoader loader) {
+ sApnSettingsLoader = loader;
+ }
+
+ /**
+ * Get the current APN settings loader
+ *
+ * @return the APN settings loader currently set
+ */
+ static ApnSettingsLoader getApnSettingsLoader() {
+ return sApnSettingsLoader;
+ }
+
+ /**
+ * Set user agent info loader
+ *
+ * @param loader the user agent info loader
+ */
+ static void setUserAgentInfoLoader(final UserAgentInfoLoader loader) {
+ sUserAgentInfoLoader = loader;
+ }
+
+ /**
+ * Get the current user agent info loader
+ *
+ * @return the user agent info loader currently set
+ */
+ static UserAgentInfoLoader getUserAgentInfoLoader() {
+ return sUserAgentInfoLoader;
+ }
+
+ /**
+ * Make sure loaders are not null. Set to default if that's the case
+ *
+ * @param context the Context to use
+ */
+ private static void ensureLoaders(final Context context) {
+ if (sUserAgentInfoLoader == null) {
+ sUserAgentInfoLoader = new DefaultUserAgentInfoLoader(context);
+ }
+ if (sCarrierConfigValuesLoader == null) {
+ sCarrierConfigValuesLoader = new DefaultCarrierConfigValuesLoader(context);
+ }
+ if (sApnSettingsLoader == null) {
+ sApnSettingsLoader = new DefaultApnSettingsLoader(context);
+ }
+ }
+
+ /**
+ * Acquire the wake lock
+ *
+ * @param context the context to use
+ */
+ private static void acquireWakeLock(final Context context) {
+ synchronized (sWakeLockLock) {
+ if (sWakeLock == null) {
+ final PowerManager pm =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_ID);
+ }
+ sWakeLock.acquire();
+ }
+ }
+
+ /**
+ * Release the wake lock
+ */
+ private static void releaseWakeLock() {
+ boolean releasedEmptyWakeLock = false;
+ synchronized (sWakeLockLock) {
+ if (sWakeLock != null) {
+ sWakeLock.release();
+ } else {
+ releasedEmptyWakeLock = true;
+ }
+ }
+ if (releasedEmptyWakeLock) {
+ Log.w(TAG, "Releasing empty wake lock");
+ }
+ }
+
+ /**
+ * Check if wake lock is not held (e.g. when service stops)
+ */
+ private static void verifyWakeLockNotHeld() {
+ boolean wakeLockHeld = false;
+ synchronized (sWakeLockLock) {
+ wakeLockHeld = sWakeLock != null && sWakeLock.isHeld();
+ }
+ if (wakeLockHeld) {
+ Log.e(TAG, "Wake lock still held!");
+ }
+ }
+
+ // Remember my PID to discard restarted intent
+ private static volatile int sMyPid = -1;
+
+ /**
+ * Get the current PID
+ *
+ * @return the current PID
+ */
+ private static int getMyPid() {
+ if (sMyPid < 0) {
+ sMyPid = Process.myPid();
+ }
+ return sMyPid;
+ }
+
+ /**
+ * Check if the intent is coming from this process
+ *
+ * @param intent the incoming intent for the service
+ * @return true if the intent is from the current process
+ */
+ private static boolean fromThisProcess(final Intent intent) {
+ final int pid = intent.getIntExtra(EXTRA_MYPID, -1);
+ return pid == getMyPid();
+ }
+
+ // Request execution thread pools. One thread pool for sending and one for downloading.
+ // The size of the thread pool controls the parallelism of request execution.
+ // See {@link setThreadPoolSize}
+ private ExecutorService[] mExecutors = new ExecutorService[2];
+
+ // Active request count
+ private int mActiveRequestCount;
+ // The latest intent startId, used for safely stopping service
+ private int mLastStartId;
+
+ private MmsNetworkManager mNetworkManager;
+
+ // Handler for scheduling service stop
+ private final Handler mHandler = new Handler();
+ // Service stop task
+ private final Runnable mServiceStopRunnable = new Runnable() {
+ @Override
+ public void run() {
+ tryStopService();
+ }
+ };
+
+ /**
+ * Start the service with a request
+ *
+ * @param context the Context to use
+ * @param request the request to start
+ */
+ public static void startRequest(final Context context, final MmsRequest request) {
+ final boolean useWakeLock = sUseWakeLock;
+ request.setUseWakeLock(useWakeLock);
+ final Intent intent = new Intent(context, MmsService.class);
+ intent.putExtra(EXTRA_REQUEST, request);
+ intent.putExtra(EXTRA_MYPID, getMyPid());
+ if (useWakeLock) {
+ acquireWakeLock(context);
+ }
+ if (context.startService(intent) == null) {
+ if (useWakeLock) {
+ releaseWakeLock();
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ ensureLoaders(this);
+
+ for (int i = 0; i < mExecutors.length; i++) {
+ mExecutors[i] = Executors.newFixedThreadPool(sThreadPoolSize);
+ }
+
+ mNetworkManager = new MmsNetworkManager(this);
+
+ synchronized (this) {
+ mActiveRequestCount = 0;
+ mLastStartId = -1;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ for (ExecutorService executor : mExecutors) {
+ executor.shutdown();
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // Always remember the latest startId for use when we try releasing the service
+ synchronized (this) {
+ mLastStartId = startId;
+ }
+ boolean scheduled = false;
+ if (intent != null) {
+ // There is a rare situation that right after a intent is started,
+ // the service gets killed. Then the service will restart with
+ // the old intent which we don't want it to run since it will
+ // break our assumption for wake lock. Check the process ID
+ // embedded in the intent to make sure it is indeed from the
+ // the current life of this service.
+ if (fromThisProcess(intent)) {
+ final MmsRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
+ if (request != null) {
+ try {
+ retainService(request, new Runnable() {
+ @Override
+ public void run() {
+ try {
+ request.execute(
+ MmsService.this,
+ mNetworkManager,
+ getApnSettingsLoader(),
+ getCarrierConfigValuesLoader(),
+ getUserAgentInfoLoader());
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected execution failure", e);
+ } finally {
+ if (request.getUseWakeLock()) {
+ releaseWakeLock();
+ }
+ releaseService();
+ }
+ }
+ });
+ scheduled = true;
+ } catch (RejectedExecutionException e) {
+ // Rare thing happened. Send back failure using the pending intent
+ // and also release the wake lock.
+ Log.w(TAG, "Executing request failed " + e);
+ request.returnResult(this, SmsManager.MMS_ERROR_UNSPECIFIED,
+ null/*response*/, 0/*httpStatusCode*/);
+ if (request.getUseWakeLock()) {
+ releaseWakeLock();
+ }
+ }
+ } else {
+ Log.w(TAG, "Empty request");
+ }
+ } else {
+ Log.w(TAG, "Got a restarted intent from previous incarnation");
+ }
+ } else {
+ Log.w(TAG, "Empty intent");
+ }
+ if (!scheduled) {
+ // If the request is not started successfully, we need to try shutdown the service
+ // if nobody is using it.
+ tryScheduleStop();
+ }
+ return START_NOT_STICKY;
+ }
+
+ /**
+ * Retain the service for executing the request in service thread pool
+ *
+ * @param request The request to execute
+ * @param runnable The runnable to run the request in thread pool
+ */
+ private void retainService(final MmsRequest request, final Runnable runnable) {
+ final ExecutorService executor = getRequestExecutor(request);
+ synchronized (this) {
+ executor.execute(runnable);
+ mActiveRequestCount++;
+ }
+ }
+
+ /**
+ * Release the service from the request. If nobody is using it, schedule service stop.
+ */
+ private void releaseService() {
+ synchronized (this) {
+ mActiveRequestCount--;
+ if (mActiveRequestCount <= 0) {
+ mActiveRequestCount = 0;
+ rescheduleServiceStop();
+ }
+ }
+ }
+
+ /**
+ * Schedule the service stop if there is no active request
+ */
+ private void tryScheduleStop() {
+ synchronized (this) {
+ if (mActiveRequestCount == 0) {
+ rescheduleServiceStop();
+ }
+ }
+ }
+
+ /**
+ * Reschedule service stop task
+ */
+ private void rescheduleServiceStop() {
+ mHandler.removeCallbacks(mServiceStopRunnable);
+ mHandler.postDelayed(mServiceStopRunnable, SERVICE_STOP_DELAY_MILLIS);
+ }
+
+ /**
+ * Really try to stop the service if there is not active request
+ */
+ private void tryStopService() {
+ Boolean stopped = null;
+ synchronized (this) {
+ if (mActiveRequestCount == 0) {
+ stopped = stopSelfResult(mLastStartId);
+ }
+ }
+ logServiceStop(stopped);
+ }
+
+ /**
+ * Log the result of service stopping. Also check wake lock status when service stops.
+ *
+ * @param stopped Not empty if service stop is performed: true if really stopped, false
+ * if cancelled.
+ */
+ private void logServiceStop(final Boolean stopped) {
+ if (stopped != null) {
+ if (stopped) {
+ Log.i(TAG, "Service successfully stopped");
+ verifyWakeLockNotHeld();
+ } else {
+ Log.i(TAG, "Service stopping cancelled");
+ }
+ }
+ }
+
+ private ExecutorService getRequestExecutor(final MmsRequest request) {
+ if (request instanceof SendRequest) {
+ // Send
+ return mExecutors[0];
+ } else {
+ // Download
+ return mExecutors[1];
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/src/android/support/v7/mms/MmsXmlResourceParser.java b/src/android/support/v7/mms/MmsXmlResourceParser.java
new file mode 100644
index 0000000..1ee73e2
--- /dev/null
+++ b/src/android/support/v7/mms/MmsXmlResourceParser.java
@@ -0,0 +1,143 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Base class for a parser of XML resources
+ */
+abstract class MmsXmlResourceParser {
+ /**
+ * Parse the content
+ *
+ * @throws IOException
+ * @throws XmlPullParserException
+ */
+ protected abstract void parseRecord() throws IOException, XmlPullParserException;
+
+ /**
+ * Get the root tag of the content
+ *
+ * @return the text of root tag
+ */
+ protected abstract String getRootTag();
+
+ private final StringBuilder mLogStringBuilder = new StringBuilder();
+
+ protected final XmlPullParser mInputParser;
+
+ protected MmsXmlResourceParser(XmlPullParser parser) {
+ mInputParser = parser;
+ }
+
+ void parse() {
+ try {
+ // Find the first element
+ if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("ApnsXmlProcessor: expecting start tag @"
+ + xmlParserDebugContext());
+ }
+ if (!getRootTag().equals(mInputParser.getName())) {
+ Log.w(MmsService.TAG, "Carrier config does not start with " + getRootTag());
+ return;
+ }
+ // We are at the start tag
+ for (;;) {
+ int nextEvent;
+ // Skipping spaces
+ while ((nextEvent = mInputParser.next()) == XmlPullParser.TEXT);
+ if (nextEvent == XmlPullParser.START_TAG) {
+ // Parse one record
+ parseRecord();
+ } else if (nextEvent == XmlPullParser.END_TAG) {
+ break;
+ } else {
+ throw new XmlPullParserException("Expecting start or end tag @"
+ + xmlParserDebugContext());
+ }
+ }
+ } catch (IOException e) {
+ Log.w(MmsService.TAG, "XmlResourceParser: I/O failure", e);
+ } catch (XmlPullParserException e) {
+ Log.w(MmsService.TAG, "XmlResourceParser: parsing failure", e);
+ }
+ }
+
+ /**
+ * Move XML parser forward to next event type or the end of doc
+ *
+ * @param eventType
+ * @return The final event type we meet
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ protected int advanceToNextEvent(int eventType) throws XmlPullParserException, IOException {
+ for (;;) {
+ int nextEvent = mInputParser.next();
+ if (nextEvent == eventType
+ || nextEvent == XmlPullParser.END_DOCUMENT) {
+ return nextEvent;
+ }
+ }
+ }
+
+ /**
+ * @return The debugging information of the parser's current position
+ */
+ protected String xmlParserDebugContext() {
+ mLogStringBuilder.setLength(0);
+ if (mInputParser != null) {
+ try {
+ final int eventType = mInputParser.getEventType();
+ mLogStringBuilder.append(xmlParserEventString(eventType));
+ if (eventType == XmlPullParser.START_TAG
+ || eventType == XmlPullParser.END_TAG
+ || eventType == XmlPullParser.TEXT) {
+ mLogStringBuilder.append('<').append(mInputParser.getName());
+ for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
+ mLogStringBuilder.append(' ')
+ .append(mInputParser.getAttributeName(i))
+ .append('=')
+ .append(mInputParser.getAttributeValue(i));
+ }
+ mLogStringBuilder.append("/>");
+ }
+ return mLogStringBuilder.toString();
+ } catch (XmlPullParserException e) {
+ Log.w(MmsService.TAG, "XmlResourceParser exception", e);
+ }
+ }
+ return "Unknown";
+ }
+
+ private static String xmlParserEventString(int event) {
+ switch (event) {
+ case XmlPullParser.START_DOCUMENT: return "START_DOCUMENT";
+ case XmlPullParser.END_DOCUMENT: return "END_DOCUMENT";
+ case XmlPullParser.START_TAG: return "START_TAG";
+ case XmlPullParser.END_TAG: return "END_TAG";
+ case XmlPullParser.TEXT: return "TEXT";
+ }
+ return Integer.toString(event);
+ }
+}
diff --git a/src/android/support/v7/mms/PhoneNumberHelper.java b/src/android/support/v7/mms/PhoneNumberHelper.java
new file mode 100644
index 0000000..99551c0
--- /dev/null
+++ b/src/android/support/v7/mms/PhoneNumberHelper.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber;
+
+/**
+ * Helper methods for phone number formatting
+ * This is isolated into a standalone class since it depends on libphonenumber
+ */
+public class PhoneNumberHelper {
+ /**
+ * Given a phone number, get its national part without country code
+ *
+ * @param number the original number
+ * @param country the country ISO code
+ * @return the national number
+ */
+ static String getNumberNoCountryCode(final String number, final String country) {
+ if (!TextUtils.isEmpty(number)) {
+ final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+ try {
+ final Phonenumber.PhoneNumber phoneNumber = phoneNumberUtil.parse(number, country);
+ if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
+ return phoneNumberUtil
+ .format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.NATIONAL)
+ .replaceAll("\\D", "");
+ }
+ } catch (final NumberParseException e) {
+ Log.w(MmsService.TAG, "getNumberNoCountryCode: invalid number " + e);
+ }
+ }
+ return number;
+ }
+}
diff --git a/src/android/support/v7/mms/SendRequest.java b/src/android/support/v7/mms/SendRequest.java
new file mode 100644
index 0000000..1b7b1db
--- /dev/null
+++ b/src/android/support/v7/mms/SendRequest.java
@@ -0,0 +1,163 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Request to send an MMS
+ */
+class SendRequest extends MmsRequest {
+ // Max send response PDU size in bytes (exceeding this may cause problem with
+ // system intent delivery).
+ private static final int MAX_SEND_RESPONSE_SIZE = 1000 * 1024;
+
+ private byte[] mPduData;
+
+ SendRequest(final String locationUrl, final Uri pduUri, final PendingIntent sentIntent) {
+ super(locationUrl, pduUri, sentIntent);
+ }
+
+ @Override
+ protected boolean loadRequest(final Context context, final Bundle mmsConfig) {
+ mPduData = readPduFromContentUri(
+ context,
+ mPduUri,
+ mmsConfig.getInt(
+ CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE,
+ CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE_DEFAULT));
+ return (mPduData != null);
+ }
+
+ @Override
+ protected boolean transferResponse(final Context context, final Intent fillIn,
+ final byte[] response) {
+ // SendConf pdus are always small and can be included in the intent
+ if (response != null && fillIn != null) {
+ if (response.length > MAX_SEND_RESPONSE_SIZE) {
+ // If the response PDU is too large, it won't be able to fit in
+ // the PendingIntent to be transferred via system IPC.
+ return false;
+ }
+ fillIn.putExtra(SmsManager.EXTRA_MMS_DATA, response);
+ }
+ return true;
+ }
+
+ @Override
+ protected byte[] doHttp(Context context, MmsNetworkManager netMgr, ApnSettingsLoader.Apn apn,
+ Bundle mmsConfig, String userAgent, String uaProfUrl) throws MmsHttpException {
+ final MmsHttpClient httpClient = netMgr.getHttpClient();
+ return httpClient.execute(getHttpRequestUrl(apn), mPduData, MmsHttpClient.METHOD_POST,
+ !TextUtils.isEmpty(apn.getMmsProxy()), apn.getMmsProxy(), apn.getMmsProxyPort(),
+ mmsConfig, userAgent, uaProfUrl);
+ }
+
+ @Override
+ protected String getHttpRequestUrl(final ApnSettingsLoader.Apn apn) {
+ return !TextUtils.isEmpty(mLocationUrl) ? mLocationUrl : apn.getMmsc();
+ }
+
+ /**
+ * Read pdu from content provider uri
+ *
+ * @param contentUri content provider uri from which to read
+ * @param maxSize maximum number of bytes to read
+ * @return pdu bytes if succeeded else null
+ */
+ public byte[] readPduFromContentUri(final Context context, final Uri contentUri,
+ final int maxSize) {
+ if (contentUri == null) {
+ return null;
+ }
+ final Callable<byte[]> copyPduToArray = new Callable<byte[]>() {
+ public byte[] call() {
+ ParcelFileDescriptor.AutoCloseInputStream inStream = null;
+ try {
+ final ContentResolver cr = context.getContentResolver();
+ final ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "r");
+ inStream = new ParcelFileDescriptor.AutoCloseInputStream(pduFd);
+ // Request one extra byte to make sure file not bigger than maxSize
+ final byte[] readBuf = new byte[maxSize+1];
+ final int bytesRead = inStream.read(readBuf, 0, maxSize+1);
+ if (bytesRead <= 0) {
+ Log.e(MmsService.TAG, "Reading PDU from sender: empty PDU");
+ return null;
+ }
+ if (bytesRead > maxSize) {
+ Log.e(MmsService.TAG, "Reading PDU from sender: PDU too large");
+ return null;
+ }
+ // Copy and return the exact length of bytes
+ final byte[] result = new byte[bytesRead];
+ System.arraycopy(readBuf, 0, result, 0, bytesRead);
+ return result;
+ } catch (IOException e) {
+ Log.e(MmsService.TAG, "Reading PDU from sender: IO exception", e);
+ return null;
+ } finally {
+ if (inStream != null) {
+ try {
+ inStream.close();
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+ }
+ };
+ final Future<byte[]> pendingResult = mPduTransferExecutor.submit(copyPduToArray);
+ try {
+ return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ // Typically a timeout occurred - cancel task
+ pendingResult.cancel(true);
+ }
+ return null;
+ }
+
+ public static final Parcelable.Creator<SendRequest> CREATOR
+ = new Parcelable.Creator<SendRequest>() {
+ public SendRequest createFromParcel(Parcel in) {
+ return new SendRequest(in);
+ }
+
+ public SendRequest[] newArray(int size) {
+ return new SendRequest[size];
+ }
+ };
+
+ private SendRequest(Parcel in) {
+ super(in);
+ }
+}
diff --git a/src/android/support/v7/mms/UserAgentInfoLoader.java b/src/android/support/v7/mms/UserAgentInfoLoader.java
new file mode 100644
index 0000000..214d58d
--- /dev/null
+++ b/src/android/support/v7/mms/UserAgentInfoLoader.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.support.v7.mms;
+
+/**
+ * Interface to load UserAgent and UA Prof URL
+ */
+public interface UserAgentInfoLoader {
+ // Carrier configuration keys for passing as config overrides into system MMS service
+ public static final String CONFIG_USER_AGENT = "userAgent";
+ public static final String CONFIG_UA_PROF_URL = "uaProfUrl";
+
+ /**
+ * Get UserAgent value
+ *
+ * @return the text of UserAgent
+ */
+ String getUserAgent();
+
+ /**
+ * Get UA Profile URL
+ *
+ * @return the URL of UA profile
+ */
+ String getUAProfUrl();
+}
diff --git a/src/android/support/v7/mms/Utils.java b/src/android/support/v7/mms/Utils.java
new file mode 100644
index 0000000..cb55c2e
--- /dev/null
+++ b/src/android/support/v7/mms/Utils.java
@@ -0,0 +1,180 @@
+/*
+ * 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 android.support.v7.mms;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Utility methods
+ */
+class Utils {
+ /**
+ * Check if MMS API is available
+ *
+ * @return true if MMS API is available, false otherwise
+ */
+ static boolean hasMmsApi() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ }
+
+ /**
+ * Check if support multi-SIM
+ *
+ * @return true if MSIM is supported, false otherwise
+ */
+ static boolean supportMSim() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+ }
+
+ /**
+ * Check if support APIs for getting UserAgent and UAProfUrl
+ *
+ * @return true if those APIs are supported, false otherwise
+ */
+ static boolean hasUserAgentApi() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+ }
+
+ /**
+ * Get system SmsManager
+ *
+ * @param subId the subscription ID of the SmsManager
+ * @return the SmsManager for the input subId
+ */
+ static SmsManager getSmsManager(final int subId) {
+ if (supportMSim()) {
+ return SmsManager.getSmsManagerForSubscriptionId(subId);
+ } else {
+ return SmsManager.getDefault();
+ }
+ }
+
+ /**
+ * Get the real subscription ID if the input is -1
+ *
+ * @param subId input subscription ID
+ * @return the default SMS subscription ID if the input is -1, otherwise the original
+ */
+ static int getEffectiveSubscriptionId(int subId) {
+ if (supportMSim()) {
+ if (subId == MmsManager.DEFAULT_SUB_ID) {
+ subId = SmsManager.getDefaultSmsSubscriptionId();
+ }
+ }
+ if (subId < 0) {
+ subId = MmsManager.DEFAULT_SUB_ID;
+ }
+ return subId;
+ }
+
+ /**
+ * Get MCC/MNC of an SIM subscription
+ *
+ * @param context the Context to use
+ * @param subId the SIM subId
+ * @return a non-empty array with exactly two elements, first is mcc and last is mnc.
+ */
+ static int[] getMccMnc(final Context context, final int subId) {
+ final int[] mccMnc = new int[] { 0, 0 };
+ if (Utils.supportMSim()) {
+ final SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+ final SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfo(subId);
+ if (subInfo != null) {
+ mccMnc[0] = subInfo.getMcc();
+ mccMnc[1] = subInfo.getMnc();
+ }
+ } else {
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ final String mccMncString = telephonyManager.getSimOperator();
+ try {
+ mccMnc[0] = Integer.parseInt(mccMncString.substring(0, 3));
+ mccMnc[1] = Integer.parseInt(mccMncString.substring(3));
+ } catch (Exception e) {
+ Log.w(MmsService.TAG, "Invalid mcc/mnc from system " + mccMncString + ": " + e);
+ mccMnc[0] = 0;
+ mccMnc[1] = 0;
+ }
+ }
+ return mccMnc;
+ }
+
+ /**
+ * Get a subscription's Context so we can load resources from it
+ *
+ * @param context the sub-independent Context
+ * @param subId the SIM's subId
+ * @return the sub-dependent Context
+ */
+ static Context getSubDepContext(final Context context, final int subId) {
+ if (!supportMSim()) {
+ return context;
+ }
+ final int[] mccMnc = getMccMnc(context, subId);
+ final int mcc = mccMnc[0];
+ final int mnc = mccMnc[1];
+ if (mcc == 0 && mnc == 0) {
+ return context;
+ }
+ final Configuration subConfig = new Configuration();
+ subConfig.mcc = mcc;
+ subConfig.mnc = mnc;
+ return context.createConfigurationContext(subConfig);
+ }
+
+ /**
+ * Redact the URL for non-VERBOSE logging. Replace url with only the host part and the length
+ * of the input URL string.
+ *
+ * @param urlString
+ * @return
+ */
+ static String redactUrlForNonVerbose(String urlString) {
+ if (Log.isLoggable(MmsService.TAG, Log.VERBOSE)) {
+ // Don't redact for VERBOSE level logging
+ return urlString;
+ }
+ if (TextUtils.isEmpty(urlString)) {
+ return urlString;
+ }
+ String protocol = "http";
+ String host = "";
+ try {
+ final URL url = new URL(urlString);
+ protocol = url.getProtocol();
+ host = url.getHost();
+ } catch (MalformedURLException e) {
+ // Ignore
+ }
+ // Print "http://host[length]"
+ final StringBuilder sb = new StringBuilder();
+ sb.append(protocol).append("://").append(host)
+ .append("[").append(urlString.length()).append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/AcknowledgeInd.java b/src/android/support/v7/mms/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..10f9e35
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/AcknowledgeInd.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-Acknowledge.ind pdu.
+ *
+ * @param mmsVersion current viersion of mms
+ * @param transactionId the transaction-id value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if transactionId is null.
+ */
+ public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+ throws InvalidHeaderValueException {
+ super();
+
+ setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ AcknowledgeInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Report-Allowed field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getReportAllowed() {
+ return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Report-Allowed field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReportAllowed(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/Base64.java b/src/android/support/v7/mms/pdu/Base64.java
new file mode 100644
index 0000000..9f3418f
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/Base64.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+public class Base64 {
+ /**
+ * Used to get the number of Quadruples.
+ */
+ static final int FOURBYTE = 4;
+
+ /**
+ * Byte used to pad output.
+ */
+ static final byte PAD = (byte) '=';
+
+ /**
+ * The base length.
+ */
+ static final int BASELENGTH = 255;
+
+ // Create arrays to hold the base64 characters
+ private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+ // Populating the character arrays
+ static {
+ for (int i = 0; i < BASELENGTH; i++) {
+ base64Alphabet[i] = (byte) -1;
+ }
+ for (int i = 'Z'; i >= 'A'; i--) {
+ base64Alphabet[i] = (byte) (i - 'A');
+ }
+ for (int i = 'z'; i >= 'a'; i--) {
+ base64Alphabet[i] = (byte) (i - 'a' + 26);
+ }
+ for (int i = '9'; i >= '0'; i--) {
+ base64Alphabet[i] = (byte) (i - '0' + 52);
+ }
+
+ base64Alphabet['+'] = 62;
+ base64Alphabet['/'] = 63;
+ }
+
+ /**
+ * Decodes Base64 data into octects
+ *
+ * @param base64Data Byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ public static byte[] decodeBase64(byte[] base64Data) {
+ // RFC 2045 requires that we discard ALL non-Base64 characters
+ base64Data = discardNonBase64(base64Data);
+
+ // handle the edge case, so we don't have to worry about it later
+ if (base64Data.length == 0) {
+ return new byte[0];
+ }
+
+ int numberQuadruple = base64Data.length / FOURBYTE;
+ byte decodedData[] = null;
+ byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+ // Throw away anything not in base64Data
+
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ {
+ // this sizes the output array properly - rlw
+ int lastData = base64Data.length;
+ // ignore the '=' padding
+ while (base64Data[lastData - 1] == PAD) {
+ if (--lastData == 0) {
+ return new byte[0];
+ }
+ }
+ decodedData = new byte[lastData - numberQuadruple];
+ }
+
+ for (int i = 0; i < numberQuadruple; i++) {
+ dataIndex = i * 4;
+ marker0 = base64Data[dataIndex + 2];
+ marker1 = base64Data[dataIndex + 3];
+
+ b1 = base64Alphabet[base64Data[dataIndex]];
+ b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+ if (marker0 != PAD && marker1 != PAD) {
+ //No PAD e.g 3cQl
+ b3 = base64Alphabet[marker0];
+ b4 = base64Alphabet[marker1];
+
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] =
+ (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+ } else if (marker0 == PAD) {
+ //Two PAD e.g. 3c[Pad][Pad]
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ } else if (marker1 == PAD) {
+ //One PAD e.g. 3cQ[Pad]
+ b3 = base64Alphabet[marker0];
+
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] =
+ (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ }
+ encodedIndex += 3;
+ }
+ return decodedData;
+ }
+
+ /**
+ * Check octect wheter it is a base64 encoding.
+ *
+ * @param octect to be checked byte
+ * @return ture if it is base64 encoding, false otherwise.
+ */
+ private static boolean isBase64(byte octect) {
+ if (octect == PAD) {
+ return true;
+ } else if (base64Alphabet[octect] == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Discards any characters outside of the base64 alphabet, per
+ * the requirements on page 25 of RFC 2045 - "Any characters
+ * outside of the base64 alphabet are to be ignored in base64
+ * encoded data."
+ *
+ * @param data The base-64 encoded data to groom
+ * @return The data, less non-base64 characters (see RFC 2045).
+ */
+ static byte[] discardNonBase64(byte[] data) {
+ byte groomedData[] = new byte[data.length];
+ int bytesCopied = 0;
+
+ for (int i = 0; i < data.length; i++) {
+ if (isBase64(data[i])) {
+ groomedData[bytesCopied++] = data[i];
+ }
+ }
+
+ byte packedData[] = new byte[bytesCopied];
+
+ System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+ return packedData;
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/CharacterSets.java b/src/android/support/v7/mms/pdu/CharacterSets.java
new file mode 100644
index 0000000..8ce2a02
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/CharacterSets.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class CharacterSets {
+ /**
+ * IANA assigned MIB enum numbers.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * Any-charset = <Octet 128>
+ * Equivalent to the special RFC2616 charset value "*"
+ */
+ public static final int ANY_CHARSET = 0x00;
+ public static final int US_ASCII = 0x03;
+ public static final int ISO_8859_1 = 0x04;
+ public static final int ISO_8859_2 = 0x05;
+ public static final int ISO_8859_3 = 0x06;
+ public static final int ISO_8859_4 = 0x07;
+ public static final int ISO_8859_5 = 0x08;
+ public static final int ISO_8859_6 = 0x09;
+ public static final int ISO_8859_7 = 0x0A;
+ public static final int ISO_8859_8 = 0x0B;
+ public static final int ISO_8859_9 = 0x0C;
+ public static final int SHIFT_JIS = 0x11;
+ public static final int UTF_8 = 0x6A;
+ public static final int BIG5 = 0x07EA;
+ public static final int UCS2 = 0x03E8;
+ public static final int UTF_16 = 0x03F7;
+
+ /**
+ * If the encoding of given data is unsupported, use UTF_8 to decode it.
+ */
+ public static final int DEFAULT_CHARSET = UTF_8;
+
+ /**
+ * Array of MIB enum numbers.
+ */
+ private static final int[] MIBENUM_NUMBERS = {
+ ANY_CHARSET,
+ US_ASCII,
+ ISO_8859_1,
+ ISO_8859_2,
+ ISO_8859_3,
+ ISO_8859_4,
+ ISO_8859_5,
+ ISO_8859_6,
+ ISO_8859_7,
+ ISO_8859_8,
+ ISO_8859_9,
+ SHIFT_JIS,
+ UTF_8,
+ BIG5,
+ UCS2,
+ UTF_16,
+ };
+
+ /**
+ * The Well-known-charset Mime name.
+ */
+ public static final String MIMENAME_ANY_CHARSET = "*";
+ public static final String MIMENAME_US_ASCII = "us-ascii";
+ public static final String MIMENAME_ISO_8859_1 = "iso-8859-1";
+ public static final String MIMENAME_ISO_8859_2 = "iso-8859-2";
+ public static final String MIMENAME_ISO_8859_3 = "iso-8859-3";
+ public static final String MIMENAME_ISO_8859_4 = "iso-8859-4";
+ public static final String MIMENAME_ISO_8859_5 = "iso-8859-5";
+ public static final String MIMENAME_ISO_8859_6 = "iso-8859-6";
+ public static final String MIMENAME_ISO_8859_7 = "iso-8859-7";
+ public static final String MIMENAME_ISO_8859_8 = "iso-8859-8";
+ public static final String MIMENAME_ISO_8859_9 = "iso-8859-9";
+ public static final String MIMENAME_SHIFT_JIS = "shift_JIS";
+ public static final String MIMENAME_UTF_8 = "utf-8";
+ public static final String MIMENAME_BIG5 = "big5";
+ public static final String MIMENAME_UCS2 = "iso-10646-ucs-2";
+ public static final String MIMENAME_UTF_16 = "utf-16";
+
+ public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+ /**
+ * Array of the names of character sets.
+ */
+ private static final String[] MIME_NAMES = {
+ MIMENAME_ANY_CHARSET,
+ MIMENAME_US_ASCII,
+ MIMENAME_ISO_8859_1,
+ MIMENAME_ISO_8859_2,
+ MIMENAME_ISO_8859_3,
+ MIMENAME_ISO_8859_4,
+ MIMENAME_ISO_8859_5,
+ MIMENAME_ISO_8859_6,
+ MIMENAME_ISO_8859_7,
+ MIMENAME_ISO_8859_8,
+ MIMENAME_ISO_8859_9,
+ MIMENAME_SHIFT_JIS,
+ MIMENAME_UTF_8,
+ MIMENAME_BIG5,
+ MIMENAME_UCS2,
+ MIMENAME_UTF_16,
+ };
+
+ private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
+ private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+ static {
+ // Create the HashMaps.
+ MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
+ NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
+ assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
+ int count = MIBENUM_NUMBERS.length - 1;
+ for(int i = 0; i <= count; i++) {
+ MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+ NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+ }
+ }
+
+ private CharacterSets() {} // Non-instantiatable
+
+ /**
+ * Map an MIBEnum number to the name of the charset which this number
+ * is assigned to by IANA.
+ *
+ * @param mibEnumValue An IANA assigned MIBEnum number.
+ * @return The name string of the charset.
+ * @throws UnsupportedEncodingException
+ */
+ public static String getMimeName(int mibEnumValue)
+ throws UnsupportedEncodingException {
+ String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+ if (name == null) {
+ throw new UnsupportedEncodingException();
+ }
+ return name;
+ }
+
+ /**
+ * Map a well-known charset name to its assigned MIBEnum number.
+ *
+ * @param mimeName The charset name.
+ * @return The MIBEnum number assigned by IANA for this charset.
+ * @throws UnsupportedEncodingException
+ */
+ public static int getMibEnumValue(String mimeName)
+ throws UnsupportedEncodingException {
+ if(null == mimeName) {
+ return -1;
+ }
+
+ Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+ if (mibEnumValue == null) {
+ throw new UnsupportedEncodingException();
+ }
+ return mibEnumValue;
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/ContentType.java b/src/android/support/v7/mms/pdu/ContentType.java
new file mode 100644
index 0000000..7ba961d
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/ContentType.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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 android.support.v7.mms.pdu;
+
+import java.util.ArrayList;
+
+public class ContentType {
+ public static final String MMS_MESSAGE = "application/vnd.wap.mms-message";
+ // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+ // Notification.ind, Delivery.ind).
+ public static final String MMS_GENERIC = "application/vnd.wap.mms-generic";
+ public static final String MULTIPART_MIXED = "application/vnd.wap.multipart.mixed";
+ public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+ public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
+
+ public static final String TEXT_PLAIN = "text/plain";
+ public static final String TEXT_HTML = "text/html";
+ public static final String TEXT_VCALENDAR = "text/x-vCalendar";
+ public static final String TEXT_VCARD = "text/x-vCard";
+
+ public static final String IMAGE_UNSPECIFIED = "image/*";
+ public static final String IMAGE_JPEG = "image/jpeg";
+ public static final String IMAGE_JPG = "image/jpg";
+ public static final String IMAGE_GIF = "image/gif";
+ public static final String IMAGE_WBMP = "image/vnd.wap.wbmp";
+ public static final String IMAGE_PNG = "image/png";
+ public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp";
+
+ public static final String AUDIO_UNSPECIFIED = "audio/*";
+ public static final String AUDIO_AAC = "audio/aac";
+ public static final String AUDIO_AMR = "audio/amr";
+ public static final String AUDIO_IMELODY = "audio/imelody";
+ public static final String AUDIO_MID = "audio/mid";
+ public static final String AUDIO_MIDI = "audio/midi";
+ public static final String AUDIO_MP3 = "audio/mp3";
+ public static final String AUDIO_MPEG3 = "audio/mpeg3";
+ public static final String AUDIO_MPEG = "audio/mpeg";
+ public static final String AUDIO_MPG = "audio/mpg";
+ public static final String AUDIO_MP4 = "audio/mp4";
+ public static final String AUDIO_X_MID = "audio/x-mid";
+ public static final String AUDIO_X_MIDI = "audio/x-midi";
+ public static final String AUDIO_X_MP3 = "audio/x-mp3";
+ public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3";
+ public static final String AUDIO_X_MPEG = "audio/x-mpeg";
+ public static final String AUDIO_X_MPG = "audio/x-mpg";
+ public static final String AUDIO_3GPP = "audio/3gpp";
+ public static final String AUDIO_X_WAV = "audio/x-wav";
+ public static final String AUDIO_OGG = "application/ogg";
+
+ public static final String VIDEO_UNSPECIFIED = "video/*";
+ public static final String VIDEO_3GPP = "video/3gpp";
+ public static final String VIDEO_3G2 = "video/3gpp2";
+ public static final String VIDEO_H263 = "video/h263";
+ public static final String VIDEO_MP4 = "video/mp4";
+
+ public static final String APP_SMIL = "application/smil";
+ public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml";
+ public static final String APP_XHTML = "application/xhtml+xml";
+
+ public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content";
+ public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+ private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+ private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+ private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+ private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+ static {
+ sSupportedContentTypes.add(TEXT_PLAIN);
+ sSupportedContentTypes.add(TEXT_HTML);
+ sSupportedContentTypes.add(TEXT_VCALENDAR);
+ sSupportedContentTypes.add(TEXT_VCARD);
+
+ sSupportedContentTypes.add(IMAGE_JPEG);
+ sSupportedContentTypes.add(IMAGE_GIF);
+ sSupportedContentTypes.add(IMAGE_WBMP);
+ sSupportedContentTypes.add(IMAGE_PNG);
+ sSupportedContentTypes.add(IMAGE_JPG);
+ sSupportedContentTypes.add(IMAGE_X_MS_BMP);
+ //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+ sSupportedContentTypes.add(AUDIO_AAC);
+ sSupportedContentTypes.add(AUDIO_AMR);
+ sSupportedContentTypes.add(AUDIO_IMELODY);
+ sSupportedContentTypes.add(AUDIO_MID);
+ sSupportedContentTypes.add(AUDIO_MIDI);
+ sSupportedContentTypes.add(AUDIO_MP3);
+ sSupportedContentTypes.add(AUDIO_MP4);
+ sSupportedContentTypes.add(AUDIO_MPEG3);
+ sSupportedContentTypes.add(AUDIO_MPEG);
+ sSupportedContentTypes.add(AUDIO_MPG);
+ sSupportedContentTypes.add(AUDIO_X_MID);
+ sSupportedContentTypes.add(AUDIO_X_MIDI);
+ sSupportedContentTypes.add(AUDIO_X_MP3);
+ sSupportedContentTypes.add(AUDIO_X_MPEG3);
+ sSupportedContentTypes.add(AUDIO_X_MPEG);
+ sSupportedContentTypes.add(AUDIO_X_MPG);
+ sSupportedContentTypes.add(AUDIO_X_WAV);
+ sSupportedContentTypes.add(AUDIO_3GPP);
+ sSupportedContentTypes.add(AUDIO_OGG);
+
+ sSupportedContentTypes.add(VIDEO_3GPP);
+ sSupportedContentTypes.add(VIDEO_3G2);
+ sSupportedContentTypes.add(VIDEO_H263);
+ sSupportedContentTypes.add(VIDEO_MP4);
+
+ sSupportedContentTypes.add(APP_SMIL);
+ sSupportedContentTypes.add(APP_WAP_XHTML);
+ sSupportedContentTypes.add(APP_XHTML);
+
+ sSupportedContentTypes.add(APP_DRM_CONTENT);
+ sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+ // add supported image types
+ sSupportedImageTypes.add(IMAGE_JPEG);
+ sSupportedImageTypes.add(IMAGE_GIF);
+ sSupportedImageTypes.add(IMAGE_WBMP);
+ sSupportedImageTypes.add(IMAGE_PNG);
+ sSupportedImageTypes.add(IMAGE_JPG);
+ sSupportedImageTypes.add(IMAGE_X_MS_BMP);
+
+ // add supported audio types
+ sSupportedAudioTypes.add(AUDIO_AAC);
+ sSupportedAudioTypes.add(AUDIO_AMR);
+ sSupportedAudioTypes.add(AUDIO_IMELODY);
+ sSupportedAudioTypes.add(AUDIO_MID);
+ sSupportedAudioTypes.add(AUDIO_MIDI);
+ sSupportedAudioTypes.add(AUDIO_MP3);
+ sSupportedAudioTypes.add(AUDIO_MPEG3);
+ sSupportedAudioTypes.add(AUDIO_MPEG);
+ sSupportedAudioTypes.add(AUDIO_MPG);
+ sSupportedAudioTypes.add(AUDIO_MP4);
+ sSupportedAudioTypes.add(AUDIO_X_MID);
+ sSupportedAudioTypes.add(AUDIO_X_MIDI);
+ sSupportedAudioTypes.add(AUDIO_X_MP3);
+ sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+ sSupportedAudioTypes.add(AUDIO_X_MPEG);
+ sSupportedAudioTypes.add(AUDIO_X_MPG);
+ sSupportedAudioTypes.add(AUDIO_X_WAV);
+ sSupportedAudioTypes.add(AUDIO_3GPP);
+ sSupportedAudioTypes.add(AUDIO_OGG);
+
+ // add supported video types
+ sSupportedVideoTypes.add(VIDEO_3GPP);
+ sSupportedVideoTypes.add(VIDEO_3G2);
+ sSupportedVideoTypes.add(VIDEO_H263);
+ sSupportedVideoTypes.add(VIDEO_MP4);
+ }
+
+ // This class should never be instantiated.
+ private ContentType() {
+ }
+
+ public static boolean isSupportedType(String contentType) {
+ return (null != contentType) && sSupportedContentTypes.contains(contentType);
+ }
+
+ public static boolean isSupportedImageType(String contentType) {
+ return isImageType(contentType) && isSupportedType(contentType);
+ }
+
+ public static boolean isSupportedAudioType(String contentType) {
+ return isAudioType(contentType) && isSupportedType(contentType);
+ }
+
+ public static boolean isSupportedVideoType(String contentType) {
+ return isVideoType(contentType) && isSupportedType(contentType);
+ }
+
+ public static boolean isTextType(String contentType) {
+ return (null != contentType) && contentType.startsWith("text/");
+ }
+
+ public static boolean isImageType(String contentType) {
+ return (null != contentType) && contentType.startsWith("image/");
+ }
+
+ public static boolean isAudioType(String contentType) {
+ return (null != contentType) && contentType.startsWith("audio/");
+ }
+
+ public static boolean isVideoType(String contentType) {
+ return (null != contentType) && contentType.startsWith("video/");
+ }
+
+ public static boolean isDrmType(String contentType) {
+ return (null != contentType)
+ && (contentType.equals(APP_DRM_CONTENT)
+ || contentType.equals(APP_DRM_MESSAGE));
+ }
+
+ public static boolean isUnspecified(String contentType) {
+ return (null != contentType) && contentType.endsWith("*");
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getImageTypes() {
+ return (ArrayList<String>) sSupportedImageTypes.clone();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getAudioTypes() {
+ return (ArrayList<String>) sSupportedAudioTypes.clone();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getVideoTypes() {
+ return (ArrayList<String>) sSupportedVideoTypes.clone();
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ArrayList<String> getSupportedTypes() {
+ return (ArrayList<String>) sSupportedContentTypes.clone();
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/DeliveryInd.java b/src/android/support/v7/mms/pdu/DeliveryInd.java
new file mode 100644
index 0000000..d7d72d9
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/DeliveryInd.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public DeliveryInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ DeliveryInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value, should not be null
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get Status value.
+ *
+ * @return the value
+ */
+ public int getStatus() {
+ return mPduHeaders.getOctet(PduHeaders.STATUS);
+ }
+
+ /**
+ * Set Status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.STATUS);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public EncodedStringValue getStatusText() {return null;}
+ * public void setStatusText(EncodedStringValue value) {}
+ */
+}
diff --git a/src/android/support/v7/mms/pdu/EncodedStringValue.java b/src/android/support/v7/mms/pdu/EncodedStringValue.java
new file mode 100644
index 0000000..d8fcacf
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/EncodedStringValue.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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 android.support.v7.mms.pdu;
+
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+ private static final String TAG = "EncodedStringValue";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * The Char-set value.
+ */
+ private int mCharacterSet;
+
+ /**
+ * The Text-string value.
+ */
+ private byte[] mData;
+
+ /**
+ * Constructor.
+ *
+ * @param charset the Char-set value
+ * @param data the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public EncodedStringValue(int charset, byte[] data) {
+ // TODO: CharSet needs to be validated against MIBEnum.
+ if(null == data) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null.");
+ }
+
+ mCharacterSet = charset;
+ mData = new byte[data.length];
+ System.arraycopy(data, 0, mData, 0, data.length);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param data the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public EncodedStringValue(byte[] data) {
+ this(CharacterSets.DEFAULT_CHARSET, data);
+ }
+
+ public EncodedStringValue(String data) {
+ try {
+ mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+ mCharacterSet = CharacterSets.DEFAULT_CHARSET;
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Default encoding must be supported.", e);
+ }
+ }
+
+ /**
+ * Get Char-set value.
+ *
+ * @return the value
+ */
+ public int getCharacterSet() {
+ return mCharacterSet;
+ }
+
+ /**
+ * Set Char-set value.
+ *
+ * @param charset the Char-set value
+ */
+ public void setCharacterSet(int charset) {
+ // TODO: CharSet needs to be validated against MIBEnum.
+ mCharacterSet = charset;
+ }
+
+ /**
+ * Get Text-string value.
+ *
+ * @return the value
+ */
+ public byte[] getTextString() {
+ byte[] byteArray = new byte[mData.length];
+
+ System.arraycopy(mData, 0, byteArray, 0, mData.length);
+ return byteArray;
+ }
+
+ /**
+ * Set Text-string value.
+ *
+ * @param textString the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public void setTextString(byte[] textString) {
+ if(null == textString) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null.");
+ }
+
+ mData = new byte[textString.length];
+ System.arraycopy(textString, 0, mData, 0, textString.length);
+ }
+
+ /**
+ * Convert this object to a {@link java.lang.String}. If the encoding of
+ * the EncodedStringValue is null or unsupported, it will be
+ * treated as iso-8859-1 encoding.
+ *
+ * @return The decoded String.
+ */
+ public String getString() {
+ if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+ return new String(mData); // system default encoding.
+ } else {
+ try {
+ String name = CharacterSets.getMimeName(mCharacterSet);
+ return new String(mData, name);
+ } catch (UnsupportedEncodingException e) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, e.getMessage(), e);
+ }
+ try {
+ return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException _) {
+ return new String(mData); // system default encoding.
+ }
+ }
+ }
+ }
+
+ /**
+ * Append to Text-string.
+ *
+ * @param textString the textString to append
+ * @throws NullPointerException if the text String is null
+ * or an IOException occured.
+ */
+ public void appendTextString(byte[] textString) {
+ if(null == textString) {
+ throw new NullPointerException("Text-string is null.");
+ }
+
+ if(null == mData) {
+ mData = new byte[textString.length];
+ System.arraycopy(textString, 0, mData, 0, textString.length);
+ } else {
+ ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+ try {
+ newTextString.write(mData);
+ newTextString.write(textString);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new NullPointerException(
+ "appendTextString: failed when write a new Text-string");
+ }
+
+ mData = newTextString.toByteArray();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ super.clone();
+ int len = mData.length;
+ byte[] dstBytes = new byte[len];
+ System.arraycopy(mData, 0, dstBytes, 0, len);
+
+ try {
+ return new EncodedStringValue(mCharacterSet, dstBytes);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+ e.printStackTrace();
+ throw new CloneNotSupportedException(e.getMessage());
+ }
+ }
+
+ /**
+ * Split this encoded string around matches of the given pattern.
+ *
+ * @param pattern the delimiting pattern
+ * @return the array of encoded strings computed by splitting this encoded
+ * string around matches of the given pattern
+ */
+ public EncodedStringValue[] split(String pattern) {
+ String[] temp = getString().split(pattern);
+ EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+ for (int i = 0; i < ret.length; ++i) {
+ try {
+ ret[i] = new EncodedStringValue(mCharacterSet,
+ temp[i].getBytes());
+ } catch (NullPointerException _) {
+ // Can't arrive here
+ return null;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Extract an EncodedStringValue[] from a given String.
+ */
+ public static EncodedStringValue[] extract(String src) {
+ String[] values = src.split(";");
+
+ ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].length() > 0) {
+ list.add(new EncodedStringValue(values[i]));
+ }
+ }
+
+ int len = list.size();
+ if (len > 0) {
+ return list.toArray(new EncodedStringValue[len]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Concatenate an EncodedStringValue[] into a single String.
+ */
+ public static String concat(EncodedStringValue[] addr) {
+ StringBuilder sb = new StringBuilder();
+ int maxIndex = addr.length - 1;
+ for (int i = 0; i <= maxIndex; i++) {
+ sb.append(addr[i].getString());
+ if (i < maxIndex) {
+ sb.append(";");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static EncodedStringValue copy(EncodedStringValue value) {
+ if (value == null) {
+ return null;
+ }
+
+ return new EncodedStringValue(value.mCharacterSet, value.mData);
+ }
+
+ public static EncodedStringValue[] encodeStrings(String[] array) {
+ int count = array.length;
+ if (count > 0) {
+ EncodedStringValue[] encodedArray = new EncodedStringValue[count];
+ for (int i = 0; i < count; i++) {
+ encodedArray[i] = new EncodedStringValue(array[i]);
+ }
+ return encodedArray;
+ }
+ return null;
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/GenericPdu.java b/src/android/support/v7/mms/pdu/GenericPdu.java
new file mode 100644
index 0000000..2f0d167
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/GenericPdu.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+public class GenericPdu {
+ /**
+ * The headers of pdu.
+ */
+ PduHeaders mPduHeaders = null;
+
+ /**
+ * Constructor.
+ */
+ public GenericPdu() {
+ mPduHeaders = new PduHeaders();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param headers Headers for this PDU.
+ */
+ GenericPdu(PduHeaders headers) {
+ mPduHeaders = headers;
+ }
+
+ /**
+ * Get the headers of this PDU.
+ *
+ * @return A PduHeaders of this PDU.
+ */
+ PduHeaders getPduHeaders() {
+ return mPduHeaders;
+ }
+
+ /**
+ * Get X-Mms-Message-Type field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getMessageType() {
+ return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+ }
+
+ /**
+ * Set X-Mms-Message-Type field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if field's value is not Octet.
+ */
+ public void setMessageType(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+ }
+
+ /**
+ * Get X-Mms-MMS-Version field value.
+ *
+ * @return the X-Mms-MMS-Version value
+ */
+ public int getMmsVersion() {
+ return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+ }
+
+ /**
+ * Set X-Mms-MMS-Version field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if field's value is not Octet.
+ */
+ public void setMmsVersion(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/InvalidHeaderValueException.java b/src/android/support/v7/mms/pdu/InvalidHeaderValueException.java
new file mode 100644
index 0000000..4b1de02
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/InvalidHeaderValueException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+ private static final long serialVersionUID = -2053384496042052262L;
+
+ /**
+ * Constructs an InvalidHeaderValueException with no detailed message.
+ */
+ public InvalidHeaderValueException() {
+ super();
+ }
+
+ /**
+ * Constructs an InvalidHeaderValueException with the specified detailed message.
+ *
+ * @param message the detailed message.
+ */
+ public InvalidHeaderValueException(String message) {
+ super(message);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/MmsException.java b/src/android/support/v7/mms/pdu/MmsException.java
new file mode 100644
index 0000000..1fd1a02
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/MmsException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+ private static final long serialVersionUID = -7323249827281485390L;
+
+ /**
+ * Creates a new MmsException.
+ */
+ public MmsException() {
+ super();
+ }
+
+ /**
+ * Creates a new MmsException with the specified detail message.
+ *
+ * @param message the detail message.
+ */
+ public MmsException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new MmsException with the specified cause.
+ *
+ * @param cause the cause.
+ */
+ public MmsException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a new MmsException with the specified detail message and cause.
+ *
+ * @param message the detail message.
+ * @param cause the cause.
+ */
+ public MmsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/MultimediaMessagePdu.java b/src/android/support/v7/mms/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..12f3b6d
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu{
+ /**
+ * The body.
+ */
+ private PduBody mMessageBody;
+
+ /**
+ * Constructor.
+ */
+ public MultimediaMessagePdu() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param header the header of this PDU
+ * @param body the body of this PDU
+ */
+ public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+ super(header);
+ mMessageBody = body;
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ MultimediaMessagePdu(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get body of the PDU.
+ *
+ * @return the body
+ */
+ public PduBody getBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * Set body of the PDU.
+ *
+ * @param body the body
+ */
+ public void setBody(PduBody body) {
+ mMessageBody = body;
+ }
+
+ /**
+ * Get subject.
+ *
+ * @return the value
+ */
+ public EncodedStringValue getSubject() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Set subject.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setSubject(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Add a "To" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addTo(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-Mms-Priority value.
+ *
+ * @return the value
+ */
+ public int getPriority() {
+ return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+ }
+
+ /**
+ * Set X-Mms-Priority value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setPriority(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value in seconds.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/NotificationInd.java b/src/android/support/v7/mms/pdu/NotificationInd.java
new file mode 100644
index 0000000..16aa23d
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/NotificationInd.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public NotificationInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ NotificationInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Content-Class Value.
+ *
+ * @return the value
+ */
+ public int getContentClass() {
+ return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Content-Class Value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setContentClass(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Content-Location value.
+ * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+ * Content-location-value = Uri-value
+ *
+ * @return the value
+ */
+ public byte[] getContentLocation() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+ }
+
+ /**
+ * Set X-Mms-Content-Location value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setContentLocation(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+ }
+
+ /**
+ * Get X-Mms-Expiry value.
+ *
+ * Expiry-value = Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ *
+ * @return the value
+ */
+ public long getExpiry() {
+ return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Set X-Mms-Expiry value.
+ *
+ * @param value the value
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setExpiry(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Message-Size value.
+ * Message-size-value = Long-integer
+ *
+ * @return the value
+ */
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Set X-Mms-Message-Size value.
+ *
+ * @param value the value
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setMessageSize(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Get subject.
+ *
+ * @return the value
+ */
+ public EncodedStringValue getSubject() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Set subject.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setSubject(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id.
+ *
+ * @return the value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report Value.
+ *
+ * @return the value
+ */
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report Value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public byte getDistributionIndicator() {return 0x00;}
+ * public void setDistributionIndicator(byte value) {}
+ *
+ * public ElementDescriptorValue getElementDescriptor() {return null;}
+ * public void getElementDescriptor(ElementDescriptorValue value) {}
+ *
+ * public byte getPriority() {return 0x00;}
+ * public void setPriority(byte value) {}
+ *
+ * public byte getRecommendedRetrievalMode() {return 0x00;}
+ * public void setRecommendedRetrievalMode(byte value) {}
+ *
+ * public byte getRecommendedRetrievalModeText() {return 0x00;}
+ * public void setRecommendedRetrievalModeText(byte value) {}
+ *
+ * public byte[] getReplaceId() {return 0x00;}
+ * public void setReplaceId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ *
+ * public byte getStored() {return 0x00;}
+ * public void setStored(byte value) {}
+ */
+}
diff --git a/src/android/support/v7/mms/pdu/NotifyRespInd.java b/src/android/support/v7/mms/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..cb8e92b
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/NotifyRespInd.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-NotifyResp.ind pdu.
+ *
+ * @param mmsVersion current version of mms
+ * @param transactionId the transaction-id value
+ * @param status the status value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if transactionId is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public NotifyRespInd(int mmsVersion,
+ byte[] transactionId,
+ int status) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ setStatus(status);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ NotifyRespInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Report-Allowed field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getReportAllowed() {
+ return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Report-Allowed field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setReportAllowed(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Status field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.STATUS);
+ }
+
+ /**
+ * GetX-Mms-Status field value.
+ *
+ * @return the X-Mms-Status value
+ */
+ public int getStatus() {
+ return mPduHeaders.getOctet(PduHeaders.STATUS);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * RuntimeException if an undeclared error occurs.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/PduBody.java b/src/android/support/v7/mms/pdu/PduBody.java
new file mode 100644
index 0000000..9d983fc
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/PduBody.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class PduBody {
+ private Vector<PduPart> mParts = null;
+
+ private Map<String, PduPart> mPartMapByContentId = null;
+ private Map<String, PduPart> mPartMapByContentLocation = null;
+ private Map<String, PduPart> mPartMapByName = null;
+ private Map<String, PduPart> mPartMapByFileName = null;
+
+ /**
+ * Constructor.
+ */
+ public PduBody() {
+ mParts = new Vector<PduPart>();
+
+ mPartMapByContentId = new HashMap<String, PduPart>();
+ mPartMapByContentLocation = new HashMap<String, PduPart>();
+ mPartMapByName = new HashMap<String, PduPart>();
+ mPartMapByFileName = new HashMap<String, PduPart>();
+ }
+
+ private void putPartToMaps(PduPart part) {
+ // Put part to mPartMapByContentId.
+ byte[] contentId = part.getContentId();
+ if(null != contentId) {
+ mPartMapByContentId.put(new String(contentId), part);
+ }
+
+ // Put part to mPartMapByContentLocation.
+ byte[] contentLocation = part.getContentLocation();
+ if(null != contentLocation) {
+ String clc = new String(contentLocation);
+ mPartMapByContentLocation.put(clc, part);
+ }
+
+ // Put part to mPartMapByName.
+ byte[] name = part.getName();
+ if(null != name) {
+ String clc = new String(name);
+ mPartMapByName.put(clc, part);
+ }
+
+ // Put part to mPartMapByFileName.
+ byte[] fileName = part.getFilename();
+ if(null != fileName) {
+ String clc = new String(fileName);
+ mPartMapByFileName.put(clc, part);
+ }
+ }
+
+ /**
+ * Appends the specified part to the end of this body.
+ *
+ * @param part part to be appended
+ * @return true when success, false when fail
+ * @throws NullPointerException when part is null
+ */
+ public boolean addPart(PduPart part) {
+ if(null == part) {
+ throw new NullPointerException();
+ }
+
+ putPartToMaps(part);
+ return mParts.add(part);
+ }
+
+ /**
+ * Inserts the specified part at the specified position.
+ *
+ * @param index index at which the specified part is to be inserted
+ * @param part part to be inserted
+ * @throws NullPointerException when part is null
+ */
+ public void addPart(int index, PduPart part) {
+ if(null == part) {
+ throw new NullPointerException();
+ }
+
+ putPartToMaps(part);
+ mParts.add(index, part);
+ }
+
+ /**
+ * Removes the part at the specified position.
+ *
+ * @param index index of the part to return
+ * @return part at the specified index
+ */
+ public PduPart removePart(int index) {
+ return mParts.remove(index);
+ }
+
+ /**
+ * Remove all of the parts.
+ */
+ public void removeAll() {
+ mParts.clear();
+ }
+
+ /**
+ * Get the part at the specified position.
+ *
+ * @param index index of the part to return
+ * @return part at the specified index
+ */
+ public PduPart getPart(int index) {
+ return mParts.get(index);
+ }
+
+ /**
+ * Get the index of the specified part.
+ *
+ * @param part the part object
+ * @return index the index of the first occurrence of the part in this body
+ */
+ public int getPartIndex(PduPart part) {
+ return mParts.indexOf(part);
+ }
+
+ /**
+ * Get the number of parts.
+ *
+ * @return the number of parts
+ */
+ public int getPartsNum() {
+ return mParts.size();
+ }
+
+ /**
+ * Get pdu part by content id.
+ *
+ * @param cid the value of content id.
+ * @return the pdu part.
+ */
+ public PduPart getPartByContentId(String cid) {
+ return mPartMapByContentId.get(cid);
+ }
+
+ /**
+ * Get pdu part by Content-Location. Content-Location of part is
+ * the same as filename and name(param of content-type).
+ *
+ * @param contentLocation the content location.
+ * @return the pdu part.
+ */
+ public PduPart getPartByContentLocation(String contentLocation) {
+ return mPartMapByContentLocation.get(contentLocation);
+ }
+
+ /**
+ * Get pdu part by name.
+ *
+ * @param name the value of filename.
+ * @return the pdu part.
+ */
+ public PduPart getPartByName(String name) {
+ return mPartMapByName.get(name);
+ }
+
+ /**
+ * Get pdu part by filename.
+ *
+ * @param filename the value of filename.
+ * @return the pdu part.
+ */
+ public PduPart getPartByFileName(String filename) {
+ return mPartMapByFileName.get(filename);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/PduContentTypes.java b/src/android/support/v7/mms/pdu/PduContentTypes.java
new file mode 100644
index 0000000..7cd0ecf
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/PduContentTypes.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+public class PduContentTypes {
+ /**
+ * All content types. From:
+ * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+ */
+ static final String[] contentTypes = {
+ "*/*", /* 0x00 */
+ "text/*", /* 0x01 */
+ "text/html", /* 0x02 */
+ "text/plain", /* 0x03 */
+ "text/x-hdml", /* 0x04 */
+ "text/x-ttml", /* 0x05 */
+ "text/x-vCalendar", /* 0x06 */
+ "text/x-vCard", /* 0x07 */
+ "text/vnd.wap.wml", /* 0x08 */
+ "text/vnd.wap.wmlscript", /* 0x09 */
+ "text/vnd.wap.wta-event", /* 0x0A */
+ "multipart/*", /* 0x0B */
+ "multipart/mixed", /* 0x0C */
+ "multipart/form-data", /* 0x0D */
+ "multipart/byterantes", /* 0x0E */
+ "multipart/alternative", /* 0x0F */
+ "application/*", /* 0x10 */
+ "application/java-vm", /* 0x11 */
+ "application/x-www-form-urlencoded", /* 0x12 */
+ "application/x-hdmlc", /* 0x13 */
+ "application/vnd.wap.wmlc", /* 0x14 */
+ "application/vnd.wap.wmlscriptc", /* 0x15 */
+ "application/vnd.wap.wta-eventc", /* 0x16 */
+ "application/vnd.wap.uaprof", /* 0x17 */
+ "application/vnd.wap.wtls-ca-certificate", /* 0x18 */
+ "application/vnd.wap.wtls-user-certificate", /* 0x19 */
+ "application/x-x509-ca-cert", /* 0x1A */
+ "application/x-x509-user-cert", /* 0x1B */
+ "image/*", /* 0x1C */
+ "image/gif", /* 0x1D */
+ "image/jpeg", /* 0x1E */
+ "image/tiff", /* 0x1F */
+ "image/png", /* 0x20 */
+ "image/vnd.wap.wbmp", /* 0x21 */
+ "application/vnd.wap.multipart.*", /* 0x22 */
+ "application/vnd.wap.multipart.mixed", /* 0x23 */
+ "application/vnd.wap.multipart.form-data", /* 0x24 */
+ "application/vnd.wap.multipart.byteranges", /* 0x25 */
+ "application/vnd.wap.multipart.alternative", /* 0x26 */
+ "application/xml", /* 0x27 */
+ "text/xml", /* 0x28 */
+ "application/vnd.wap.wbxml", /* 0x29 */
+ "application/x-x968-cross-cert", /* 0x2A */
+ "application/x-x968-ca-cert", /* 0x2B */
+ "application/x-x968-user-cert", /* 0x2C */
+ "text/vnd.wap.si", /* 0x2D */
+ "application/vnd.wap.sic", /* 0x2E */
+ "text/vnd.wap.sl", /* 0x2F */
+ "application/vnd.wap.slc", /* 0x30 */
+ "text/vnd.wap.co", /* 0x31 */
+ "application/vnd.wap.coc", /* 0x32 */
+ "application/vnd.wap.multipart.related", /* 0x33 */
+ "application/vnd.wap.sia", /* 0x34 */
+ "text/vnd.wap.connectivity-xml", /* 0x35 */
+ "application/vnd.wap.connectivity-wbxml", /* 0x36 */
+ "application/pkcs7-mime", /* 0x37 */
+ "application/vnd.wap.hashed-certificate", /* 0x38 */
+ "application/vnd.wap.signed-certificate", /* 0x39 */
+ "application/vnd.wap.cert-response", /* 0x3A */
+ "application/xhtml+xml", /* 0x3B */
+ "application/wml+xml", /* 0x3C */
+ "text/css", /* 0x3D */
+ "application/vnd.wap.mms-message", /* 0x3E */
+ "application/vnd.wap.rollover-certificate", /* 0x3F */
+ "application/vnd.wap.locc+wbxml", /* 0x40 */
+ "application/vnd.wap.loc+xml", /* 0x41 */
+ "application/vnd.syncml.dm+wbxml", /* 0x42 */
+ "application/vnd.syncml.dm+xml", /* 0x43 */
+ "application/vnd.syncml.notification", /* 0x44 */
+ "application/vnd.wap.xhtml+xml", /* 0x45 */
+ "application/vnd.wv.csp.cir", /* 0x46 */
+ "application/vnd.oma.dd+xml", /* 0x47 */
+ "application/vnd.oma.drm.message", /* 0x48 */
+ "application/vnd.oma.drm.content", /* 0x49 */
+ "application/vnd.oma.drm.rights+xml", /* 0x4A */
+ "application/vnd.oma.drm.rights+wbxml", /* 0x4B */
+ "application/vnd.wv.csp+xml", /* 0x4C */
+ "application/vnd.wv.csp+wbxml", /* 0x4D */
+ "application/vnd.syncml.ds.notification", /* 0x4E */
+ "audio/*", /* 0x4F */
+ "video/*", /* 0x50 */
+ "application/vnd.oma.dd2+xml", /* 0x51 */
+ "application/mikey" /* 0x52 */
+ };
+}
diff --git a/src/android/support/v7/mms/pdu/PduHeaders.java b/src/android/support/v7/mms/pdu/PduHeaders.java
new file mode 100644
index 0000000..0271e58
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/PduHeaders.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PduHeaders {
+ /**
+ * All pdu header fields.
+ */
+ public static final int BCC = 0x81;
+ public static final int CC = 0x82;
+ public static final int CONTENT_LOCATION = 0x83;
+ public static final int CONTENT_TYPE = 0x84;
+ public static final int DATE = 0x85;
+ public static final int DELIVERY_REPORT = 0x86;
+ public static final int DELIVERY_TIME = 0x87;
+ public static final int EXPIRY = 0x88;
+ public static final int FROM = 0x89;
+ public static final int MESSAGE_CLASS = 0x8A;
+ public static final int MESSAGE_ID = 0x8B;
+ public static final int MESSAGE_TYPE = 0x8C;
+ public static final int MMS_VERSION = 0x8D;
+ public static final int MESSAGE_SIZE = 0x8E;
+ public static final int PRIORITY = 0x8F;
+
+ public static final int READ_REPLY = 0x90;
+ public static final int READ_REPORT = 0x90;
+ public static final int REPORT_ALLOWED = 0x91;
+ public static final int RESPONSE_STATUS = 0x92;
+ public static final int RESPONSE_TEXT = 0x93;
+ public static final int SENDER_VISIBILITY = 0x94;
+ public static final int STATUS = 0x95;
+ public static final int SUBJECT = 0x96;
+ public static final int TO = 0x97;
+ public static final int TRANSACTION_ID = 0x98;
+ public static final int RETRIEVE_STATUS = 0x99;
+ public static final int RETRIEVE_TEXT = 0x9A;
+ public static final int READ_STATUS = 0x9B;
+ public static final int REPLY_CHARGING = 0x9C;
+ public static final int REPLY_CHARGING_DEADLINE = 0x9D;
+ public static final int REPLY_CHARGING_ID = 0x9E;
+ public static final int REPLY_CHARGING_SIZE = 0x9F;
+
+ public static final int PREVIOUSLY_SENT_BY = 0xA0;
+ public static final int PREVIOUSLY_SENT_DATE = 0xA1;
+ public static final int STORE = 0xA2;
+ public static final int MM_STATE = 0xA3;
+ public static final int MM_FLAGS = 0xA4;
+ public static final int STORE_STATUS = 0xA5;
+ public static final int STORE_STATUS_TEXT = 0xA6;
+ public static final int STORED = 0xA7;
+ public static final int ATTRIBUTES = 0xA8;
+ public static final int TOTALS = 0xA9;
+ public static final int MBOX_TOTALS = 0xAA;
+ public static final int QUOTAS = 0xAB;
+ public static final int MBOX_QUOTAS = 0xAC;
+ public static final int MESSAGE_COUNT = 0xAD;
+ public static final int CONTENT = 0xAE;
+ public static final int START = 0xAF;
+
+ public static final int ADDITIONAL_HEADERS = 0xB0;
+ public static final int DISTRIBUTION_INDICATOR = 0xB1;
+ public static final int ELEMENT_DESCRIPTOR = 0xB2;
+ public static final int LIMIT = 0xB3;
+ public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4;
+ public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+ public static final int STATUS_TEXT = 0xB6;
+ public static final int APPLIC_ID = 0xB7;
+ public static final int REPLY_APPLIC_ID = 0xB8;
+ public static final int AUX_APPLIC_ID = 0xB9;
+ public static final int CONTENT_CLASS = 0xBA;
+ public static final int DRM_CONTENT = 0xBB;
+ public static final int ADAPTATION_ALLOWED = 0xBC;
+ public static final int REPLACE_ID = 0xBD;
+ public static final int CANCEL_ID = 0xBE;
+ public static final int CANCEL_STATUS = 0xBF;
+
+ /**
+ * X-Mms-Message-Type field types.
+ */
+ public static final int MESSAGE_TYPE_SEND_REQ = 0x80;
+ public static final int MESSAGE_TYPE_SEND_CONF = 0x81;
+ public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82;
+ public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83;
+ public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
+ public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85;
+ public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86;
+ public static final int MESSAGE_TYPE_READ_REC_IND = 0x87;
+ public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88;
+ public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89;
+ public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A;
+ public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B;
+ public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C;
+ public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D;
+ public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E;
+ public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F;
+ public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90;
+ public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91;
+ public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92;
+ public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93;
+ public static final int MESSAGE_TYPE_DELETE_REQ = 0x94;
+ public static final int MESSAGE_TYPE_DELETE_CONF = 0x95;
+ public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96;
+ public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97;
+
+ /**
+ * X-Mms-Delivery-Report |
+ * X-Mms-Read-Report |
+ * X-Mms-Report-Allowed |
+ * X-Mms-Sender-Visibility |
+ * X-Mms-Store |
+ * X-Mms-Stored |
+ * X-Mms-Totals |
+ * X-Mms-Quotas |
+ * X-Mms-Distribution-Indicator |
+ * X-Mms-DRM-Content |
+ * X-Mms-Adaptation-Allowed |
+ * field types.
+ */
+ public static final int VALUE_YES = 0x80;
+ public static final int VALUE_NO = 0x81;
+
+ /**
+ * Delivery-Time |
+ * Expiry and Reply-Charging-Deadline |
+ * field type components.
+ */
+ public static final int VALUE_ABSOLUTE_TOKEN = 0x80;
+ public static final int VALUE_RELATIVE_TOKEN = 0x81;
+
+ /**
+ * X-Mms-MMS-Version field types.
+ */
+ public static final int MMS_VERSION_1_3 = ((1 << 4) | 3);
+ public static final int MMS_VERSION_1_2 = ((1 << 4) | 2);
+ public static final int MMS_VERSION_1_1 = ((1 << 4) | 1);
+ public static final int MMS_VERSION_1_0 = ((1 << 4) | 0);
+
+ // Current version is 1.2.
+ public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2;
+
+ /**
+ * From field type components.
+ */
+ public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80;
+ public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81;
+
+ public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+ public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+ /**
+ * X-Mms-Status Field.
+ */
+ public static final int STATUS_EXPIRED = 0x80;
+ public static final int STATUS_RETRIEVED = 0x81;
+ public static final int STATUS_REJECTED = 0x82;
+ public static final int STATUS_DEFERRED = 0x83;
+ public static final int STATUS_UNRECOGNIZED = 0x84;
+ public static final int STATUS_INDETERMINATE = 0x85;
+ public static final int STATUS_FORWARDED = 0x86;
+ public static final int STATUS_UNREACHABLE = 0x87;
+
+ /**
+ * MM-Flags field type components.
+ */
+ public static final int MM_FLAGS_ADD_TOKEN = 0x80;
+ public static final int MM_FLAGS_REMOVE_TOKEN = 0x81;
+ public static final int MM_FLAGS_FILTER_TOKEN = 0x82;
+
+ /**
+ * X-Mms-Message-Class field types.
+ */
+ public static final int MESSAGE_CLASS_PERSONAL = 0x80;
+ public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81;
+ public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82;
+ public static final int MESSAGE_CLASS_AUTO = 0x83;
+
+ public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+ public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+ public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+ public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+ /**
+ * X-Mms-Priority field types.
+ */
+ public static final int PRIORITY_LOW = 0x80;
+ public static final int PRIORITY_NORMAL = 0x81;
+ public static final int PRIORITY_HIGH = 0x82;
+
+ /**
+ * X-Mms-Response-Status field types.
+ */
+ public static final int RESPONSE_STATUS_OK = 0x80;
+ public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81;
+ public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+ public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83;
+ public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+ public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85;
+ public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86;
+ public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+ public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4;
+
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED = 0xE3;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE4;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED = 0xE5;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET = 0xE6;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED = 0xE8;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED = 0xE9;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED = 0xEA;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID = 0xEB;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_END = 0xFF;
+
+ /**
+ * X-Mms-Retrieve-Status field types.
+ */
+ public static final int RETRIEVE_STATUS_OK = 0x80;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+ public static final int RETRIEVE_STATUS_ERROR_END = 0xFF;
+
+ /**
+ * X-Mms-Sender-Visibility field types.
+ */
+ public static final int SENDER_VISIBILITY_HIDE = 0x80;
+ public static final int SENDER_VISIBILITY_SHOW = 0x81;
+
+ /**
+ * X-Mms-Read-Status field types.
+ */
+ public static final int READ_STATUS_READ = 0x80;
+ public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+ /**
+ * X-Mms-Cancel-Status field types.
+ */
+ public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+ public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81;
+
+ /**
+ * X-Mms-Reply-Charging field types.
+ */
+ public static final int REPLY_CHARGING_REQUESTED = 0x80;
+ public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+ public static final int REPLY_CHARGING_ACCEPTED = 0x82;
+ public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83;
+
+ /**
+ * X-Mms-MM-State field types.
+ */
+ public static final int MM_STATE_DRAFT = 0x80;
+ public static final int MM_STATE_SENT = 0x81;
+ public static final int MM_STATE_NEW = 0x82;
+ public static final int MM_STATE_RETRIEVED = 0x83;
+ public static final int MM_STATE_FORWARDED = 0x84;
+
+ /**
+ * X-Mms-Recommended-Retrieval-Mode field types.
+ */
+ public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+ /**
+ * X-Mms-Content-Class field types.
+ */
+ public static final int CONTENT_CLASS_TEXT = 0x80;
+ public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81;
+ public static final int CONTENT_CLASS_IMAGE_RICH = 0x82;
+ public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83;
+ public static final int CONTENT_CLASS_VIDEO_RICH = 0x84;
+ public static final int CONTENT_CLASS_MEGAPIXEL = 0x85;
+ public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86;
+ public static final int CONTENT_CLASS_CONTENT_RICH = 0x87;
+
+ /**
+ * X-Mms-Store-Status field types.
+ */
+ public static final int STORE_STATUS_SUCCESS = 0x80;
+ public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+ public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1;
+ public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4;
+ public static final int STORE_STATUS_ERROR_END = 0xFF;
+
+ /**
+ * The map contains the value of all headers.
+ */
+ private HashMap<Integer, Object> mHeaderMap = null;
+
+ /**
+ * Constructor of PduHeaders.
+ */
+ public PduHeaders() {
+ mHeaderMap = new HashMap<Integer, Object>();
+ }
+
+ /**
+ * Get octet value by header field.
+ *
+ * @param field the field
+ * @return the octet value of the pdu header
+ * with specified header field. Return 0 if
+ * the value is not set.
+ */
+ protected int getOctet(int field) {
+ Integer octet = (Integer) mHeaderMap.get(field);
+ if (null == octet) {
+ return 0;
+ }
+
+ return octet;
+ }
+
+ /**
+ * Set octet value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ protected void setOctet(int value, int field)
+ throws InvalidHeaderValueException{
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ switch (field) {
+ case REPORT_ALLOWED:
+ case ADAPTATION_ALLOWED:
+ case DELIVERY_REPORT:
+ case DRM_CONTENT:
+ case DISTRIBUTION_INDICATOR:
+ case QUOTAS:
+ case READ_REPORT:
+ case STORE:
+ case STORED:
+ case TOTALS:
+ case SENDER_VISIBILITY:
+ if ((VALUE_YES != value) && (VALUE_NO != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case READ_STATUS:
+ if ((READ_STATUS_READ != value) &&
+ (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case CANCEL_STATUS:
+ if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+ (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case PRIORITY:
+ if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case STATUS:
+ if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case REPLY_CHARGING:
+ if ((value < REPLY_CHARGING_REQUESTED)
+ || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case MM_STATE:
+ if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case RECOMMENDED_RETRIEVAL_MODE:
+ if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case CONTENT_CLASS:
+ if ((value < CONTENT_CLASS_TEXT)
+ || (value > CONTENT_CLASS_CONTENT_RICH)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case RETRIEVE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+ if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+ (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+ (value <= RETRIEVE_STATUS_ERROR_END)) {
+ value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+ } else if ((value < RETRIEVE_STATUS_OK) ||
+ ((value > RETRIEVE_STATUS_OK) &&
+ (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > RETRIEVE_STATUS_ERROR_END)) {
+ value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case STORE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+ if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+ (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+ (value <= STORE_STATUS_ERROR_END)) {
+ value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+ } else if ((value < STORE_STATUS_SUCCESS) ||
+ ((value > STORE_STATUS_SUCCESS) &&
+ (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > STORE_STATUS_ERROR_END)) {
+ value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case RESPONSE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+ if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+ (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+ (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+ (value < RESPONSE_STATUS_OK) ||
+ ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+ (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+ value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case MMS_VERSION:
+ if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
+ value = CURRENT_MMS_VERSION; // Current version is the default value.
+ }
+ break;
+ case MESSAGE_TYPE:
+ if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ default:
+ // This header value should not be Octect.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Get TextString value by header field.
+ *
+ * @param field the field
+ * @return the TextString value of the pdu header
+ * with specified header field
+ */
+ protected byte[] getTextString(int field) {
+ return (byte[]) mHeaderMap.get(field);
+ }
+
+ /**
+ * Set TextString value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the TextString value of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setTextString(byte[] value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case TRANSACTION_ID:
+ case REPLY_CHARGING_ID:
+ case AUX_APPLIC_ID:
+ case APPLIC_ID:
+ case REPLY_APPLIC_ID:
+ case MESSAGE_ID:
+ case REPLACE_ID:
+ case CANCEL_ID:
+ case CONTENT_LOCATION:
+ case MESSAGE_CLASS:
+ case CONTENT_TYPE:
+ break;
+ default:
+ // This header value should not be Text-String.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Get EncodedStringValue value by header field.
+ *
+ * @param field the field
+ * @return the EncodedStringValue value of the pdu header
+ * with specified header field
+ */
+ protected EncodedStringValue getEncodedStringValue(int field) {
+ return (EncodedStringValue) mHeaderMap.get(field);
+ }
+
+ /**
+ * Get TO, CC or BCC header value.
+ *
+ * @param field the field
+ * @return the EncodeStringValue array of the pdu header
+ * with specified header field
+ */
+ protected EncodedStringValue[] getEncodedStringValues(int field) {
+ ArrayList<EncodedStringValue> list =
+ (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+ if (null == list) {
+ return null;
+ }
+ EncodedStringValue[] values = new EncodedStringValue[list.size()];
+ return list.toArray(values);
+ }
+
+ /**
+ * Set EncodedStringValue value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the EncodedStringValue value of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setEncodedStringValue(EncodedStringValue value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case SUBJECT:
+ case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+ case RETRIEVE_TEXT:
+ case STATUS_TEXT:
+ case STORE_STATUS_TEXT:
+ case RESPONSE_TEXT:
+ case FROM:
+ case PREVIOUSLY_SENT_BY:
+ case MM_FLAGS:
+ break;
+ default:
+ // This header value should not be Encoded-String-Value.
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Set TO, CC or BCC header value.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the EncodedStringValue value array of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case BCC:
+ case CC:
+ case TO:
+ break;
+ default:
+ // This header value should not be Encoded-String-Value.
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+ for (int i = 0; i < value.length; i++) {
+ list.add(value[i]);
+ }
+ mHeaderMap.put(field, list);
+ }
+
+ /**
+ * Append one EncodedStringValue to another.
+ *
+ * @param value the EncodedStringValue to append
+ * @param field the field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void appendEncodedStringValue(EncodedStringValue value,
+ int field) {
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case BCC:
+ case CC:
+ case TO:
+ break;
+ default:
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ ArrayList<EncodedStringValue> list =
+ (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+ if (null == list) {
+ list = new ArrayList<EncodedStringValue>();
+ }
+ list.add(value);
+ mHeaderMap.put(field, list);
+ }
+
+ /**
+ * Get LongInteger value by header field.
+ *
+ * @param field the field
+ * @return the LongInteger value of the pdu header
+ * with specified header field. if return -1, the
+ * field is not existed in pdu header.
+ */
+ protected long getLongInteger(int field) {
+ Long longInteger = (Long) mHeaderMap.get(field);
+ if (null == longInteger) {
+ return -1;
+ }
+
+ return longInteger.longValue();
+ }
+
+ /**
+ * Set LongInteger value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ */
+ protected void setLongInteger(long value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ switch (field) {
+ case DATE:
+ case REPLY_CHARGING_SIZE:
+ case MESSAGE_SIZE:
+ case MESSAGE_COUNT:
+ case START:
+ case LIMIT:
+ case DELIVERY_TIME:
+ case EXPIRY:
+ case REPLY_CHARGING_DEADLINE:
+ case PREVIOUSLY_SENT_DATE:
+ break;
+ default:
+ // This header value should not be LongInteger.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/PduParser.java b/src/android/support/v7/mms/pdu/PduParser.java
new file mode 100755
index 0000000..7d30df5
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/PduParser.java
@@ -0,0 +1,2008 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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 android.support.v7.mms.pdu;
+
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduParser {
+ /**
+ * The next are WAP values defined in WSP specification.
+ */
+ private static final int QUOTE = 127;
+ private static final int LENGTH_QUOTE = 31;
+ private static final int TEXT_MIN = 32;
+ private static final int TEXT_MAX = 127;
+ private static final int SHORT_INTEGER_MAX = 127;
+ private static final int SHORT_LENGTH_MAX = 30;
+ private static final int LONG_INTEGER_LENGTH_MAX = 8;
+ private static final int QUOTED_STRING_FLAG = 34;
+ private static final int END_STRING_FLAG = 0x00;
+ //The next two are used by the interface "parseWapString" to
+ //distinguish Text-String and Quoted-String.
+ private static final int TYPE_TEXT_STRING = 0;
+ private static final int TYPE_QUOTED_STRING = 1;
+ private static final int TYPE_TOKEN_STRING = 2;
+
+ /**
+ * Specify the part position.
+ */
+ private static final int THE_FIRST_PART = 0;
+ private static final int THE_LAST_PART = 1;
+
+ /**
+ * The pdu data.
+ */
+ private ByteArrayInputStream mPduDataStream = null;
+
+ /**
+ * Store pdu headers
+ */
+ private PduHeaders mHeaders = null;
+
+ /**
+ * Store pdu parts.
+ */
+ private PduBody mBody = null;
+
+ /**
+ * Store the "type" parameter in "Content-Type" header field.
+ */
+ private static byte[] mTypeParam = null;
+
+ /**
+ * Store the "start" parameter in "Content-Type" header field.
+ */
+ private static byte[] mStartParam = null;
+
+ /**
+ * The log tag.
+ */
+ private static final String LOG_TAG = "PduParser";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * Whether to parse content-disposition part header
+ */
+ private final boolean mParseContentDisposition;
+
+ /**
+ * Constructor.
+ *
+ * @param pduDataStream pdu data to be parsed
+ * @param parseContentDisposition whether to parse the Content-Disposition part header
+ */
+ public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
+ mPduDataStream = new ByteArrayInputStream(pduDataStream);
+ mParseContentDisposition = parseContentDisposition;
+ }
+
+ /**
+ * Parse the pdu.
+ *
+ * @return the pdu structure if parsing successfully.
+ * null if parsing error happened or mandatory fields are not set.
+ */
+ public GenericPdu parse(){
+ if (mPduDataStream == null) {
+ return null;
+ }
+
+ /* parse headers */
+ mHeaders = parseHeaders(mPduDataStream);
+ if (null == mHeaders) {
+ // Parse headers failed.
+ return null;
+ }
+
+ /* get the message type */
+ int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+ /* check mandatory header fields */
+ if (false == checkMandatoryHeader(mHeaders)) {
+ log("check mandatory headers failed!");
+ return null;
+ }
+
+ if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+ (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
+ /* need to parse the parts */
+ mBody = parseParts(mPduDataStream);
+ if (null == mBody) {
+ // Parse parts failed.
+ return null;
+ }
+ }
+
+ switch (messageType) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
+ }
+ SendReq sendReq = new SendReq(mHeaders, mBody);
+ return sendReq;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
+ }
+ SendConf sendConf = new SendConf(mHeaders);
+ return sendConf;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
+ }
+ NotificationInd notificationInd =
+ new NotificationInd(mHeaders);
+ return notificationInd;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
+ }
+ NotifyRespInd notifyRespInd =
+ new NotifyRespInd(mHeaders);
+ return notifyRespInd;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
+ }
+ RetrieveConf retrieveConf =
+ new RetrieveConf(mHeaders, mBody);
+
+ byte[] contentType = retrieveConf.getContentType();
+ if (null == contentType) {
+ return null;
+ }
+ String ctTypeStr = new String(contentType);
+ if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
+ || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
+ || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+ // The MMS content type must be "application/vnd.wap.multipart.mixed"
+ // or "application/vnd.wap.multipart.related"
+ // or "application/vnd.wap.multipart.alternative"
+ return retrieveConf;
+ } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+ // "application/vnd.wap.multipart.alternative"
+ // should take only the first part.
+ PduPart firstPart = mBody.getPart(0);
+ mBody.removeAll();
+ mBody.addPart(0, firstPart);
+ return retrieveConf;
+ }
+ return null;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
+ }
+ DeliveryInd deliveryInd =
+ new DeliveryInd(mHeaders);
+ return deliveryInd;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
+ }
+ AcknowledgeInd acknowledgeInd =
+ new AcknowledgeInd(mHeaders);
+ return acknowledgeInd;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
+ }
+ ReadOrigInd readOrigInd =
+ new ReadOrigInd(mHeaders);
+ return readOrigInd;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
+ }
+ ReadRecInd readRecInd =
+ new ReadRecInd(mHeaders);
+ return readRecInd;
+ default:
+ log("Parser doesn't support this message type in this version!");
+ return null;
+ }
+ }
+
+ /**
+ * Parse pdu headers.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return headers in PduHeaders structure, null when parse fail
+ */
+ protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
+ if (pduDataStream == null) {
+ return null;
+ }
+ boolean keepParsing = true;
+ PduHeaders headers = new PduHeaders();
+
+ while (keepParsing && (pduDataStream.available() > 0)) {
+ pduDataStream.mark(1);
+ int headerField = extractByteValue(pduDataStream);
+ /* parse custom text header */
+ if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
+ pduDataStream.reset();
+ byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
+ }
+ /* we should ignore it at the moment */
+ continue;
+ }
+ switch (headerField) {
+ case PduHeaders.MESSAGE_TYPE:
+ {
+ int messageType = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType);
+ }
+ switch (messageType) {
+ // We don't support these kind of messages now.
+ case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+ case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+ return null;
+ }
+ try {
+ headers.setOctet(messageType, headerField);
+ } catch(InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + messageType +
+ " into the header filed: " + headerField);
+ return null;
+ } catch(RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+ /* Octect value */
+ case PduHeaders.REPORT_ALLOWED:
+ case PduHeaders.ADAPTATION_ALLOWED:
+ case PduHeaders.DELIVERY_REPORT:
+ case PduHeaders.DRM_CONTENT:
+ case PduHeaders.DISTRIBUTION_INDICATOR:
+ case PduHeaders.QUOTAS:
+ case PduHeaders.READ_REPORT:
+ case PduHeaders.STORE:
+ case PduHeaders.STORED:
+ case PduHeaders.TOTALS:
+ case PduHeaders.SENDER_VISIBILITY:
+ case PduHeaders.READ_STATUS:
+ case PduHeaders.CANCEL_STATUS:
+ case PduHeaders.PRIORITY:
+ case PduHeaders.STATUS:
+ case PduHeaders.REPLY_CHARGING:
+ case PduHeaders.MM_STATE:
+ case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+ case PduHeaders.CONTENT_CLASS:
+ case PduHeaders.RETRIEVE_STATUS:
+ case PduHeaders.STORE_STATUS:
+ /**
+ * The following field has a different value when
+ * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+ * For now we ignore this fact, since we do not support these PDUs
+ */
+ case PduHeaders.RESPONSE_STATUS:
+ {
+ int value = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " +
+ value);
+ }
+
+ try {
+ headers.setOctet(value, headerField);
+ } catch(InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + value +
+ " into the header filed: " + headerField);
+ return null;
+ } catch(RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Long-Integer */
+ case PduHeaders.DATE:
+ case PduHeaders.REPLY_CHARGING_SIZE:
+ case PduHeaders.MESSAGE_SIZE:
+ {
+ try {
+ long value = parseLongInteger(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " +
+ value);
+ }
+ headers.setLongInteger(value, headerField);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Integer-Value */
+ case PduHeaders.MESSAGE_COUNT:
+ case PduHeaders.START:
+ case PduHeaders.LIMIT:
+ {
+ try {
+ long value = parseIntegerValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " +
+ value);
+ }
+ headers.setLongInteger(value, headerField);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Text-String */
+ case PduHeaders.TRANSACTION_ID:
+ case PduHeaders.REPLY_CHARGING_ID:
+ case PduHeaders.AUX_APPLIC_ID:
+ case PduHeaders.APPLIC_ID:
+ case PduHeaders.REPLY_APPLIC_ID:
+ /**
+ * The next three header fields are email addresses
+ * as defined in RFC2822,
+ * not including the characters "<" and ">"
+ */
+ case PduHeaders.MESSAGE_ID:
+ case PduHeaders.REPLACE_ID:
+ case PduHeaders.CANCEL_ID:
+ /**
+ * The following field has a different value when
+ * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+ * For now we ignore this fact, since we do not support these PDUs
+ */
+ case PduHeaders.CONTENT_LOCATION:
+ {
+ byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != value) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " +
+ new String(value));
+ }
+ headers.setTextString(value, headerField);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Encoded-string-value */
+ case PduHeaders.SUBJECT:
+ case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+ case PduHeaders.RETRIEVE_TEXT:
+ case PduHeaders.STATUS_TEXT:
+ case PduHeaders.STORE_STATUS_TEXT:
+ /* the next one is not support
+ * M-Mbox-Delete.conf and M-Delete.conf now */
+ case PduHeaders.RESPONSE_TEXT:
+ {
+ EncodedStringValue value =
+ parseEncodedStringValue(pduDataStream);
+ if (null != value) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField
+ + " value: " + value.getString());
+ }
+ headers.setEncodedStringValue(value, headerField);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Addressing model */
+ case PduHeaders.BCC:
+ case PduHeaders.CC:
+ case PduHeaders.TO:
+ {
+ EncodedStringValue value =
+ parseEncodedStringValue(pduDataStream);
+ if (null != value) {
+ byte[] address = value.getTextString();
+ if (null != address) {
+ String str = new String(address);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
+ + " value: " + str);
+ }
+ int endIndex = str.indexOf("/");
+ if (endIndex > 0) {
+ str = str.substring(0, endIndex);
+ }
+ try {
+ value.setTextString(str.getBytes());
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ return null;
+ }
+ }
+
+ try {
+ headers.appendEncodedStringValue(value, headerField);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+ case PduHeaders.DELIVERY_TIME:
+ case PduHeaders.EXPIRY:
+ case PduHeaders.REPLY_CHARGING_DEADLINE:
+ {
+ /* parse Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Absolute-token or Relative-token */
+ int token = extractByteValue(pduDataStream);
+
+ /* Date-value or Delta-seconds-value */
+ long timeValue;
+ try {
+ timeValue = parseLongInteger(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+ /* need to convert the Delta-seconds-value
+ * into Date-value */
+ timeValue = System.currentTimeMillis()/1000 + timeValue;
+ }
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: time value: " + headerField
+ + " value: " + timeValue);
+ }
+ headers.setLongInteger(timeValue, headerField);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.FROM: {
+ /* From-value =
+ * Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ */
+ EncodedStringValue from = null;
+ parseValueLength(pduDataStream); /* parse value-length */
+
+ /* Address-present-token or Insert-address-token */
+ int fromToken = extractByteValue(pduDataStream);
+
+ /* Address-present-token or Insert-address-token */
+ if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+ /* Encoded-string-value */
+ from = parseEncodedStringValue(pduDataStream);
+ if (null != from) {
+ byte[] address = from.getTextString();
+ if (null != address) {
+ String str = new String(address);
+ int endIndex = str.indexOf("/");
+ if (endIndex > 0) {
+ str = str.substring(0, endIndex);
+ }
+ try {
+ from.setTextString(str.getBytes());
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ return null;
+ }
+ }
+ }
+ } else {
+ try {
+ from = new EncodedStringValue(
+ PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+ } catch(NullPointerException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: from address: " + headerField
+ + " value: " + from.getString());
+ }
+ headers.setEncodedStringValue(from, PduHeaders.FROM);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.MESSAGE_CLASS: {
+ /* Message-class-value = Class-identifier | Token-text */
+ pduDataStream.mark(1);
+ int messageClass = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField
+ + " value: " + messageClass);
+ }
+
+ if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+ /* Class-identifier */
+ try {
+ if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ }
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ } else {
+ /* Token-text */
+ pduDataStream.reset();
+ byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != messageClassString) {
+ try {
+ headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+ }
+ break;
+ }
+
+ case PduHeaders.MMS_VERSION: {
+ int version = parseShortInteger(pduDataStream);
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField
+ + " value: " + version);
+ }
+ headers.setOctet(version, PduHeaders.MMS_VERSION);
+ } catch(InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + version +
+ " into the header filed: " + headerField);
+ return null;
+ } catch(RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.PREVIOUSLY_SENT_BY: {
+ /* Previously-sent-by-value =
+ * Value-length Forwarded-count-value Encoded-string-value */
+ /* parse value-length */
+ parseValueLength(pduDataStream);
+
+ /* parse Forwarded-count-value */
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* parse Encoded-string-value */
+ EncodedStringValue previouslySentBy =
+ parseEncodedStringValue(pduDataStream);
+ if (null != previouslySentBy) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField
+ + " value: " + previouslySentBy.getString());
+ }
+ headers.setEncodedStringValue(previouslySentBy,
+ PduHeaders.PREVIOUSLY_SENT_BY);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ case PduHeaders.PREVIOUSLY_SENT_DATE: {
+ /* Previously-sent-date-value =
+ * Value-length Forwarded-count-value Date-value */
+ /* parse value-length */
+ parseValueLength(pduDataStream);
+
+ /* parse Forwarded-count-value */
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* Date-value */
+ try {
+ long perviouslySentDate = parseLongInteger(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField
+ + " value: " + perviouslySentDate);
+ }
+ headers.setLongInteger(perviouslySentDate,
+ PduHeaders.PREVIOUSLY_SENT_DATE);
+ } catch(RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.MM_FLAGS: {
+ /* MM-flags-value =
+ * Value-length
+ * ( Add-token | Remove-token | Filter-token )
+ * Encoded-string-value
+ */
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField
+ + " NOT REALLY SUPPORTED");
+ }
+
+ /* parse Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Add-token | Remove-token | Filter-token */
+ extractByteValue(pduDataStream);
+
+ /* Encoded-string-value */
+ parseEncodedStringValue(pduDataStream);
+
+ /* not store this header filed in "headers",
+ * because now PduHeaders doesn't support it */
+ break;
+ }
+
+ /* Value-length
+ * (Message-total-token | Size-total-token) Integer-Value */
+ case PduHeaders.MBOX_TOTALS:
+ case PduHeaders.MBOX_QUOTAS:
+ {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField);
+ }
+ /* Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Message-total-token | Size-total-token */
+ extractByteValue(pduDataStream);
+
+ /*Integer-Value*/
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch(RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* not store these headers filed in "headers",
+ because now PduHeaders doesn't support them */
+ break;
+ }
+
+ case PduHeaders.ELEMENT_DESCRIPTOR: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField);
+ }
+ parseContentType(pduDataStream, null);
+
+ /* not store this header filed in "headers",
+ because now PduHeaders doesn't support it */
+ break;
+ }
+
+ case PduHeaders.CONTENT_TYPE: {
+ HashMap<Integer, Object> map =
+ new HashMap<Integer, Object>();
+ byte[] contentType =
+ parseContentType(pduDataStream, map);
+
+ if (null != contentType) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField +
+ Arrays.toString(contentType));
+ }
+ headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+ } catch(NullPointerException e) {
+ log("null pointer error!");
+ } catch(RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+
+ /* get start parameter */
+ mStartParam = (byte[]) map.get(PduPart.P_START);
+
+ /* get charset parameter */
+ mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
+
+ keepParsing = false;
+ break;
+ }
+
+ case PduHeaders.CONTENT:
+ case PduHeaders.ADDITIONAL_HEADERS:
+ case PduHeaders.ATTRIBUTES:
+ default: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField);
+ }
+ log("Unknown header");
+ }
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * Parse pdu parts.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return parts in PduBody structure
+ */
+ protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
+ if (pduDataStream == null) {
+ return null;
+ }
+
+ int count = parseUnsignedInt(pduDataStream); // get the number of parts
+ PduBody body = new PduBody();
+
+ for (int i = 0 ; i < count ; i++) {
+ int headerLength = parseUnsignedInt(pduDataStream);
+ int dataLength = parseUnsignedInt(pduDataStream);
+ PduPart part = new PduPart();
+ int startPos = pduDataStream.available();
+ if (startPos <= 0) {
+ // Invalid part.
+ return null;
+ }
+
+ /* parse part's content-type */
+ HashMap<Integer, Object> map = new HashMap<Integer, Object>();
+ byte[] contentType = parseContentType(pduDataStream, map);
+ if (null != contentType) {
+ part.setContentType(contentType);
+ } else {
+ part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+ }
+
+ /* get name parameter */
+ byte[] name = (byte[]) map.get(PduPart.P_NAME);
+ if (null != name) {
+ part.setName(name);
+ }
+
+ /* get charset parameter */
+ Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+ if (null != charset) {
+ part.setCharset(charset);
+ }
+
+ /* parse part's headers */
+ int endPos = pduDataStream.available();
+ int partHeaderLen = headerLength - (startPos - endPos);
+ if (partHeaderLen > 0) {
+ if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+ // Parse part header faild.
+ return null;
+ }
+ } else if (partHeaderLen < 0) {
+ // Invalid length of content-type.
+ return null;
+ }
+
+ /* FIXME: check content-id, name, filename and content location,
+ * if not set anyone of them, generate a default content-location
+ */
+ if ((null == part.getContentLocation())
+ && (null == part.getName())
+ && (null == part.getFilename())
+ && (null == part.getContentId())) {
+ part.setContentLocation(Long.toOctalString(
+ System.currentTimeMillis()).getBytes());
+ }
+
+ /* get part's data */
+ if (dataLength > 0) {
+ byte[] partData = new byte[dataLength];
+ String partContentType = new String(part.getContentType());
+ pduDataStream.read(partData, 0, dataLength);
+ if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
+ // parse "multipart/vnd.wap.multipart.alternative".
+ PduBody childBody = parseParts(new ByteArrayInputStream(partData));
+ // take the first part of children.
+ part = childBody.getPart(0);
+ } else {
+ // Check Content-Transfer-Encoding.
+ byte[] partDataEncoding = part.getContentTransferEncoding();
+ if (null != partDataEncoding) {
+ String encoding = new String(partDataEncoding);
+ if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+ // Decode "base64" into "binary".
+ partData = Base64.decodeBase64(partData);
+ } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+ // Decode "quoted-printable" into "binary".
+ partData = QuotedPrintable.decodeQuotedPrintable(partData);
+ } else {
+ // "binary" is the default encoding.
+ }
+ }
+ if (null == partData) {
+ log("Decode part data error!");
+ return null;
+ }
+ part.setData(partData);
+ }
+ }
+
+ /* add this part to body */
+ if (THE_FIRST_PART == checkPartPosition(part)) {
+ /* this is the first part */
+ body.addPart(0, part);
+ } else {
+ /* add the part to the end */
+ body.addPart(part);
+ }
+ }
+
+ return body;
+ }
+
+ /**
+ * Log status.
+ *
+ * @param text log information
+ */
+ private static void log(String text) {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, text);
+ }
+ }
+
+ /**
+ * Parse unsigned integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the integer, -1 when failed
+ */
+ protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * The maximum size of a uintvar is 32 bits.
+ * So it will be encoded in no more than 5 octets.
+ */
+ assert(null != pduDataStream);
+ int result = 0;
+ int temp = pduDataStream.read();
+ if (temp == -1) {
+ return temp;
+ }
+
+ while((temp & 0x80) != 0) {
+ result = result << 7;
+ result |= temp & 0x7F;
+ temp = pduDataStream.read();
+ if (temp == -1) {
+ return temp;
+ }
+ }
+
+ result = result << 7;
+ result |= temp & 0x7F;
+
+ return result;
+ }
+
+ /**
+ * Parse value length.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the integer
+ */
+ protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Value-length = Short-length | (Length-quote Length)
+ * Short-length = <Any octet 0-30>
+ * Length-quote = <Octet 31>
+ * Length = Uintvar-integer
+ * Uintvar-integer = 1*5 OCTET
+ */
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ int first = temp & 0xFF;
+
+ if (first <= SHORT_LENGTH_MAX) {
+ return first;
+ } else if (first == LENGTH_QUOTE) {
+ return parseUnsignedInt(pduDataStream);
+ }
+
+ throw new RuntimeException("Value length > LENGTH_QUOTE!");
+ }
+
+ /**
+ * Parse encoded string value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the EncodedStringValue
+ */
+ protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
+ /**
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+ assert(null != pduDataStream);
+ pduDataStream.mark(1);
+ EncodedStringValue returnValue = null;
+ int charset = 0;
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ int first = temp & 0xFF;
+ if (first == 0) {
+ return new EncodedStringValue("");
+ }
+
+ pduDataStream.reset();
+ if (first < TEXT_MIN) {
+ parseValueLength(pduDataStream);
+
+ charset = parseShortInteger(pduDataStream); //get the "Charset"
+ }
+
+ byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+ try {
+ if (0 != charset) {
+ returnValue = new EncodedStringValue(charset, textString);
+ } else {
+ returnValue = new EncodedStringValue(textString);
+ }
+ } catch(Exception e) {
+ return null;
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * Parse Text-String or Quoted-String.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+ * @return the string without End-of-string in byte array
+ */
+ protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+ int stringType) {
+ assert(null != pduDataStream);
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Text-string = [Quote] *TEXT End-of-string
+ * If the first character in the TEXT is in the range of 128-255,
+ * a Quote character must precede it.
+ * Otherwise the Quote character must be omitted.
+ * The Quote is not part of the contents.
+ * Quote = <Octet 127>
+ * End-of-string = <Octet 0>
+ *
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ *
+ * Token-text = Token End-of-string
+ */
+
+ // Mark supposed beginning of Text-string
+ // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+ pduDataStream.mark(1);
+
+ // Check first char
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ if ((TYPE_QUOTED_STRING == stringType) &&
+ (QUOTED_STRING_FLAG == temp)) {
+ // Mark again if QUOTED_STRING_FLAG and ignore it
+ pduDataStream.mark(1);
+ } else if ((TYPE_TEXT_STRING == stringType) &&
+ (QUOTE == temp)) {
+ // Mark again if QUOTE and ignore it
+ pduDataStream.mark(1);
+ } else {
+ // Otherwise go back to origin
+ pduDataStream.reset();
+ }
+
+ // We are now definitely at the beginning of string
+ /**
+ * Return *TOKEN or *TEXT (Text-String without QUOTE,
+ * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+ */
+ return getWapString(pduDataStream, stringType);
+ }
+
+ /**
+ * Check TOKEN data defined in RFC2616.
+ * @param ch checking data
+ * @return true when ch is TOKEN, false when ch is not TOKEN
+ */
+ protected static boolean isTokenCharacter(int ch) {
+ /**
+ * Token = 1*<any CHAR except CTLs or separators>
+ * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+ * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+ * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+ * | "{"(123) | "}"(125) | SP(32) | HT(9)
+ * CHAR = <any US-ASCII character (octets 0 - 127)>
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ */
+ if((ch < 33) || (ch > 126)) {
+ return false;
+ }
+
+ switch(ch) {
+ case '"': /* '"' */
+ case '(': /* '(' */
+ case ')': /* ')' */
+ case ',': /* ',' */
+ case '/': /* '/' */
+ case ':': /* ':' */
+ case ';': /* ';' */
+ case '<': /* '<' */
+ case '=': /* '=' */
+ case '>': /* '>' */
+ case '?': /* '?' */
+ case '@': /* '@' */
+ case '[': /* '[' */
+ case '\\': /* '\' */
+ case ']': /* ']' */
+ case '{': /* '{' */
+ case '}': /* '}' */
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check TEXT data defined in RFC2616.
+ * @param ch checking data
+ * @return true when ch is TEXT, false when ch is not TEXT
+ */
+ protected static boolean isText(int ch) {
+ /**
+ * TEXT = <any OCTET except CTLs,
+ * but including LWS>
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ * LWS = [CRLF] 1*( SP | HT )
+ * CRLF = CR LF
+ * CR = <US-ASCII CR, carriage return (13)>
+ * LF = <US-ASCII LF, linefeed (10)>
+ */
+ if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+ return true;
+ }
+
+ switch(ch) {
+ case '\t': /* '\t' */
+ case '\n': /* '\n' */
+ case '\r': /* '\r' */
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+ int stringType) {
+ assert(null != pduDataStream);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ while((-1 != temp) && ('\0' != temp)) {
+ // check each of the character
+ if (stringType == TYPE_TOKEN_STRING) {
+ if (isTokenCharacter(temp)) {
+ out.write(temp);
+ }
+ } else {
+ if (isText(temp)) {
+ out.write(temp);
+ }
+ }
+
+ temp = pduDataStream.read();
+ assert(-1 != temp);
+ }
+
+ if (out.size() > 0) {
+ return out.toByteArray();
+ }
+
+ return null;
+ }
+
+ /**
+ * Extract a byte value from the input stream.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the byte
+ */
+ protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ return temp & 0xFF;
+ }
+
+ /**
+ * Parse Short-Integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the byte
+ */
+ protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Short-integer = OCTET
+ * Integers in range 0-127 shall be encoded as a one
+ * octet value with the most significant bit set to one (1xxx xxxx)
+ * and with the value in the remaining least significant bits.
+ */
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ return temp & 0x7F;
+ }
+
+ /**
+ * Parse Long-Integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return long integer
+ */
+ protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Long-integer = Short-length Multi-octet-integer
+ * The Short-length indicates the length of the Multi-octet-integer
+ * Multi-octet-integer = 1*30 OCTET
+ * The content octets shall be an unsigned integer value
+ * with the most significant octet encoded first (big-endian representation).
+ * The minimum number of octets must be used to encode the value.
+ * Short-length = <Any octet 0-30>
+ */
+ assert(null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ int count = temp & 0xFF;
+
+ if (count > LONG_INTEGER_LENGTH_MAX) {
+ throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+ }
+
+ long result = 0;
+
+ for (int i = 0 ; i < count ; i++) {
+ temp = pduDataStream.read();
+ assert(-1 != temp);
+ result <<= 8;
+ result += (temp & 0xFF);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse Integer-Value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return long integer
+ */
+ protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Integer-Value = Short-integer | Long-integer
+ */
+ assert(null != pduDataStream);
+ pduDataStream.mark(1);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ pduDataStream.reset();
+ if (temp > SHORT_INTEGER_MAX) {
+ return parseShortInteger(pduDataStream);
+ } else {
+ return parseLongInteger(pduDataStream);
+ }
+ }
+
+ /**
+ * To skip length of the wap value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param length area size
+ * @return the values in this area
+ */
+ protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+ assert(null != pduDataStream);
+ byte[] area = new byte[length];
+ int readLen = pduDataStream.read(area, 0, length);
+ if (readLen < length) { //The actually read length is lower than the length
+ return -1;
+ } else {
+ return readLen;
+ }
+ }
+
+ /**
+ * Parse content type parameters. For now we just support
+ * four parameters used in mms: "type", "start", "name", "charset".
+ *
+ * @param pduDataStream pdu data input stream
+ * @param map to store parameters of Content-Type field
+ * @param length length of all the parameters
+ */
+ protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+ HashMap<Integer, Object> map, Integer length) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Parameter = Typed-parameter | Untyped-parameter
+ * Typed-parameter = Well-known-parameter-token Typed-value
+ * the actual expected type of the value is implied by the well-known parameter
+ * Well-known-parameter-token = Integer-value
+ * the code values used for parameters are specified in the Assigned Numbers appendix
+ * Typed-value = Compact-value | Text-value
+ * In addition to the expected type, there may be no value.
+ * If the value cannot be encoded using the expected type, it shall be encoded as text.
+ * Compact-value = Integer-value |
+ * Date-value | Delta-seconds-value | Q-value | Version-value |
+ * Uri-value
+ * Untyped-parameter = Token-text Untyped-value
+ * the type of the value is unknown, but it shall be encoded as an integer,
+ * if that is possible.
+ * Untyped-value = Integer-value | Text-value
+ */
+ assert(null != pduDataStream);
+ assert(length > 0);
+
+ int startPos = pduDataStream.available();
+ int tempPos = 0;
+ int lastLen = length;
+ while(0 < lastLen) {
+ int param = pduDataStream.read();
+ assert(-1 != param);
+ lastLen--;
+
+ switch (param) {
+ /**
+ * From rfc2387, chapter 3.1
+ * The type parameter must be specified and its value is the MIME media
+ * type of the "root" body part. It permits a MIME user agent to
+ * determine the content-type without reference to the enclosed body
+ * part. If the value of the type parameter and the root body part's
+ * content-type differ then the User Agent's behavior is undefined.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * type = Constrained-encoding
+ * Constrained-encoding = Extension-Media | Short-integer
+ * Extension-media = *TEXT End-of-string
+ */
+ case PduPart.P_TYPE:
+ case PduPart.P_CT_MR_TYPE:
+ pduDataStream.mark(1);
+ int first = extractByteValue(pduDataStream);
+ pduDataStream.reset();
+ if (first > TEXT_MAX) {
+ // Short-integer (well-known type)
+ int index = parseShortInteger(pduDataStream);
+
+ if (index < PduContentTypes.contentTypes.length) {
+ byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+ map.put(PduPart.P_TYPE, type);
+ } else {
+ //not support this type, ignore it.
+ }
+ } else {
+ // Text-String (extension-media)
+ byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != type) && (null != map)) {
+ map.put(PduPart.P_TYPE, type);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+ * Start Parameter Referring to Presentation
+ *
+ * From rfc2387, chapter 3.2
+ * The start parameter, if given, is the content-ID of the compound
+ * object's "root". If not present the "root" is the first body part in
+ * the Multipart/Related entity. The "root" is the element the
+ * applications processes first.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * start = Text-String
+ */
+ case PduPart.P_START:
+ case PduPart.P_DEP_START:
+ byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != start) && (null != map)) {
+ map.put(PduPart.P_START, start);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf
+ * In creation, the character set SHALL be either us-ascii
+ * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+ * In retrieval, both us-ascii and utf-8 SHALL be supported.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * charset = Well-known-charset|Text-String
+ * Well-known-charset = Any-charset | Integer-value
+ * Both are encoded using values from Character Set
+ * Assignments table in Assigned Numbers
+ * Any-charset = <Octet 128>
+ * Equivalent to the special RFC2616 charset value "*"
+ */
+ case PduPart.P_CHARSET:
+ pduDataStream.mark(1);
+ int firstValue = extractByteValue(pduDataStream);
+ pduDataStream.reset();
+ //Check first char
+ if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+ (END_STRING_FLAG == firstValue)) {
+ //Text-String (extension-charset)
+ byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ try {
+ int charsetInt = CharacterSets.getMibEnumValue(
+ new String(charsetStr));
+ map.put(PduPart.P_CHARSET, charsetInt);
+ } catch (UnsupportedEncodingException e) {
+ // Not a well-known charset, use "*".
+ Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+ map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+ }
+ } else {
+ //Well-known-charset
+ int charset = (int) parseIntegerValue(pduDataStream);
+ if (map != null) {
+ map.put(PduPart.P_CHARSET, charset);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf
+ * A name for multipart object SHALL be encoded using name-parameter
+ * for Content-Type header in WSP multipart headers.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * name = Text-String
+ */
+ case PduPart.P_DEP_NAME:
+ case PduPart.P_NAME:
+ byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != name) && (null != map)) {
+ map.put(PduPart.P_NAME, name);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ default:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Content-Type parameter");
+ }
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Content-Type");
+ } else {
+ lastLen = 0;
+ }
+ break;
+ }
+ }
+
+ if (0 != lastLen) {
+ Log.e(LOG_TAG, "Corrupt Content-Type");
+ }
+ }
+
+ /**
+ * Parse content type.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param map to store parameters in Content-Type header field
+ * @return Content-Type value
+ */
+ protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+ HashMap<Integer, Object> map) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Content-type-value = Constrained-media | Content-general-form
+ * Content-general-form = Value-length Media-type
+ * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+ */
+ assert(null != pduDataStream);
+
+ byte[] contentType = null;
+ pduDataStream.mark(1);
+ int temp = pduDataStream.read();
+ assert(-1 != temp);
+ pduDataStream.reset();
+
+ int cur = (temp & 0xFF);
+
+ if (cur < TEXT_MIN) {
+ int length = parseValueLength(pduDataStream);
+ int startPos = pduDataStream.available();
+ pduDataStream.mark(1);
+ temp = pduDataStream.read();
+ assert(-1 != temp);
+ pduDataStream.reset();
+ int first = (temp & 0xFF);
+
+ if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ } else if (first > TEXT_MAX) {
+ int index = parseShortInteger(pduDataStream);
+
+ if (index < PduContentTypes.contentTypes.length) { //well-known type
+ contentType = (PduContentTypes.contentTypes[index]).getBytes();
+ } else {
+ pduDataStream.reset();
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ }
+ } else {
+ Log.e(LOG_TAG, "Corrupt content-type");
+ return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+ }
+
+ int endPos = pduDataStream.available();
+ int parameterLen = length - (startPos - endPos);
+ if (parameterLen > 0) {//have parameters
+ parseContentTypeParams(pduDataStream, map, parameterLen);
+ }
+
+ if (parameterLen < 0) {
+ Log.e(LOG_TAG, "Corrupt MMS message");
+ return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+ }
+ } else if (cur <= TEXT_MAX) {
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ } else {
+ contentType =
+ (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+ }
+
+ return contentType;
+ }
+
+ /**
+ * Parse part's headers.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param part to store the header informations of the part
+ * @param length length of the headers
+ * @return true if parse successfully, false otherwise
+ */
+ protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+ PduPart part, int length) {
+ assert(null != pduDataStream);
+ assert(null != part);
+ assert(length > 0);
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+ * A name for multipart object SHALL be encoded using name-parameter
+ * for Content-Type header in WSP multipart headers.
+ * In decoding, name-parameter of Content-Type SHALL be used if available.
+ * If name-parameter of Content-Type is not available,
+ * filename parameter of Content-Disposition header SHALL be used if available.
+ * If neither name-parameter of Content-Type header nor filename parameter
+ * of Content-Disposition header is available,
+ * Content-Location header SHALL be used if available.
+ *
+ * Within SMIL part the reference to the media object parts SHALL use
+ * either Content-ID or Content-Location mechanism [RFC2557]
+ * and the corresponding WSP part headers in media object parts
+ * contain the corresponding definitions.
+ */
+ int startPos = pduDataStream.available();
+ int tempPos = 0;
+ int lastLen = length;
+ while(0 < lastLen) {
+ int header = pduDataStream.read();
+ assert(-1 != header);
+ lastLen--;
+
+ if (header > TEXT_MAX) {
+ // Number assigned headers.
+ switch (header) {
+ case PduPart.P_CONTENT_LOCATION:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-location-value = Uri-value
+ */
+ byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != contentLocation) {
+ part.setContentLocation(contentLocation);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ case PduPart.P_CONTENT_ID:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-ID-value = Quoted-string
+ */
+ byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+ if (null != contentId) {
+ part.setContentId(contentId);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ case PduPart.P_DEP_CONTENT_DISPOSITION:
+ case PduPart.P_CONTENT_DISPOSITION:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-disposition-value = Value-length Disposition *(Parameter)
+ * Disposition = Form-data | Attachment | Inline | Token-text
+ * Form-data = <Octet 128>
+ * Attachment = <Octet 129>
+ * Inline = <Octet 130>
+ */
+
+ /*
+ * some carrier mmsc servers do not support content_disposition
+ * field correctly
+ */
+ if (mParseContentDisposition) {
+ int len = parseValueLength(pduDataStream);
+ pduDataStream.mark(1);
+ int thisStartPos = pduDataStream.available();
+ int thisEndPos = 0;
+ int value = pduDataStream.read();
+
+ if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
+ part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+ } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+ part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+ } else if (value == PduPart.P_DISPOSITION_INLINE) {
+ part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+ } else {
+ pduDataStream.reset();
+ /* Token-text */
+ part.setContentDisposition(parseWapString(pduDataStream
+ , TYPE_TEXT_STRING));
+ }
+
+ /* get filename parameter and skip other parameters */
+ thisEndPos = pduDataStream.available();
+ if (thisStartPos - thisEndPos < len) {
+ value = pduDataStream.read();
+ if (value == PduPart.P_FILENAME) { //filename is text-string
+ part.setFilename(parseWapString(pduDataStream
+ , TYPE_TEXT_STRING));
+ }
+
+ /* skip other parameters */
+ thisEndPos = pduDataStream.available();
+ if (thisStartPos - thisEndPos < len) {
+ int last = len - (thisStartPos - thisEndPos);
+ byte[] temp = new byte[last];
+ pduDataStream.read(temp, 0, last);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ }
+ break;
+ default:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Part headers: " + header);
+ }
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+ lastLen = 0;
+ break;
+ }
+ } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+ // Not assigned header.
+ byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+ // Check the header whether it is "Content-Transfer-Encoding".
+ if (true ==
+ PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
+ part.setContentTransferEncoding(tempValue);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ } else {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Part headers: " + header);
+ }
+ // Skip all headers of this part.
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+ lastLen = 0;
+ }
+ }
+
+ if (0 != lastLen) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check the position of a specified part.
+ *
+ * @param part the part to be checked
+ * @return part position, THE_FIRST_PART when it's the
+ * first one, THE_LAST_PART when it's the last one.
+ */
+ private static int checkPartPosition(PduPart part) {
+ assert(null != part);
+ if ((null == mTypeParam) &&
+ (null == mStartParam)) {
+ return THE_LAST_PART;
+ }
+
+ /* check part's content-id */
+ if (null != mStartParam) {
+ byte[] contentId = part.getContentId();
+ if (null != contentId) {
+ if (true == Arrays.equals(mStartParam, contentId)) {
+ return THE_FIRST_PART;
+ }
+ }
+ // This is not the first part, so append to end (keeping the original order)
+ // Check b/19607294 for details of this change
+ return THE_LAST_PART;
+ }
+
+ /* check part's content-type */
+ if (null != mTypeParam) {
+ byte[] contentType = part.getContentType();
+ if (null != contentType) {
+ if (true == Arrays.equals(mTypeParam, contentType)) {
+ return THE_FIRST_PART;
+ }
+ }
+ }
+
+ return THE_LAST_PART;
+ }
+
+ /**
+ * Check mandatory headers of a pdu.
+ *
+ * @param headers pdu headers
+ * @return true if the pdu has all of the mandatory headers, false otherwise.
+ */
+ protected static boolean checkMandatoryHeader(PduHeaders headers) {
+ if (null == headers) {
+ return false;
+ }
+
+ /* get message type */
+ int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+ /* check Mms-Version field */
+ int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+ if (0 == mmsVersion) {
+ // Every message should have Mms-Version field.
+ return false;
+ }
+
+ /* check mandatory header fields */
+ switch (messageType) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ // Content-Type field.
+ byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+ if (null == srContentType) {
+ return false;
+ }
+
+ // From field.
+ EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == srFrom) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == srTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ // Response-Status field.
+ int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+ if (0 == scResponseStatus) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == scTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ // Content-Location field.
+ byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+ if (null == niContentLocation) {
+ return false;
+ }
+
+ // Expiry field.
+ long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+ if (-1 == niExpiry) {
+ return false;
+ }
+
+ // Message-Class field.
+ byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+ if (null == niMessageClass) {
+ return false;
+ }
+
+ // Message-Size field.
+ long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ if (-1 == niMessageSize) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == niTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ // Status field.
+ int nriStatus = headers.getOctet(PduHeaders.STATUS);
+ if (0 == nriStatus) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == nriTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ // Content-Type field.
+ byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+ if (null == rcContentType) {
+ return false;
+ }
+
+ // Date field.
+ long rcDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == rcDate) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ // Date field.
+ long diDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == diDate) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == diMessageId) {
+ return false;
+ }
+
+ // Status field.
+ int diStatus = headers.getOctet(PduHeaders.STATUS);
+ if (0 == diStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == diTo) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ // Transaction-Id field.
+ byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == aiTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ // Date field.
+ long roDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == roDate) {
+ return false;
+ }
+
+ // From field.
+ EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == roFrom) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == roMessageId) {
+ return false;
+ }
+
+ // Read-Status field.
+ int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+ if (0 == roReadStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == roTo) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ // From field.
+ EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == rrFrom) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == rrMessageId) {
+ return false;
+ }
+
+ // Read-Status field.
+ int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+ if (0 == rrReadStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == rrTo) {
+ return false;
+ }
+
+ break;
+ default:
+ // Parser doesn't support this message type in this version.
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/PduPart.java b/src/android/support/v7/mms/pdu/PduPart.java
new file mode 100644
index 0000000..ec8e1a2
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/PduPart.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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 android.support.v7.mms.pdu;
+
+import android.net.Uri;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+ /**
+ * Well-Known Parameters.
+ */
+ public static final int P_Q = 0x80;
+ public static final int P_CHARSET = 0x81;
+ public static final int P_LEVEL = 0x82;
+ public static final int P_TYPE = 0x83;
+ public static final int P_DEP_NAME = 0x85;
+ public static final int P_DEP_FILENAME = 0x86;
+ public static final int P_DIFFERENCES = 0x87;
+ public static final int P_PADDING = 0x88;
+ // This value of "TYPE" s used with Content-Type: multipart/related
+ public static final int P_CT_MR_TYPE = 0x89;
+ public static final int P_DEP_START = 0x8A;
+ public static final int P_DEP_START_INFO = 0x8B;
+ public static final int P_DEP_COMMENT = 0x8C;
+ public static final int P_DEP_DOMAIN = 0x8D;
+ public static final int P_MAX_AGE = 0x8E;
+ public static final int P_DEP_PATH = 0x8F;
+ public static final int P_SECURE = 0x90;
+ public static final int P_SEC = 0x91;
+ public static final int P_MAC = 0x92;
+ public static final int P_CREATION_DATE = 0x93;
+ public static final int P_MODIFICATION_DATE = 0x94;
+ public static final int P_READ_DATE = 0x95;
+ public static final int P_SIZE = 0x96;
+ public static final int P_NAME = 0x97;
+ public static final int P_FILENAME = 0x98;
+ public static final int P_START = 0x99;
+ public static final int P_START_INFO = 0x9A;
+ public static final int P_COMMENT = 0x9B;
+ public static final int P_DOMAIN = 0x9C;
+ public static final int P_PATH = 0x9D;
+
+ /**
+ * Header field names.
+ */
+ public static final int P_CONTENT_TYPE = 0x91;
+ public static final int P_CONTENT_LOCATION = 0x8E;
+ public static final int P_CONTENT_ID = 0xC0;
+ public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+ public static final int P_CONTENT_DISPOSITION = 0xC5;
+ // The next header is unassigned header, use reserved header(0x48) value.
+ public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+ /**
+ * Content=Transfer-Encoding string.
+ */
+ public static final String CONTENT_TRANSFER_ENCODING =
+ "Content-Transfer-Encoding";
+
+ /**
+ * Value of Content-Transfer-Encoding.
+ */
+ public static final String P_BINARY = "binary";
+ public static final String P_7BIT = "7bit";
+ public static final String P_8BIT = "8bit";
+ public static final String P_BASE64 = "base64";
+ public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+ /**
+ * Value of disposition can be set to PduPart when the value is octet in
+ * the PDU.
+ * "from-data" instead of Form-data<Octet 128>.
+ * "attachment" instead of Attachment<Octet 129>.
+ * "inline" instead of Inline<Octet 130>.
+ */
+ static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+ static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+ static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+ /**
+ * Content-Disposition value.
+ */
+ public static final int P_DISPOSITION_FROM_DATA = 0x80;
+ public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+ public static final int P_DISPOSITION_INLINE = 0x82;
+
+ /**
+ * Header of part.
+ */
+ private Map<Integer, Object> mPartHeader = null;
+
+ /**
+ * Data uri.
+ */
+ private Uri mUri = null;
+
+ /**
+ * Part data.
+ */
+ private byte[] mPartData = null;
+
+ private static final String TAG = "PduPart";
+
+ /**
+ * Empty Constructor.
+ */
+ public PduPart() {
+ mPartHeader = new HashMap<Integer, Object>();
+ }
+
+ /**
+ * Set part data. The data are stored as byte array.
+ *
+ * @param data the data
+ */
+ public void setData(byte[] data) {
+ if(data == null) {
+ return;
+ }
+
+ mPartData = new byte[data.length];
+ System.arraycopy(data, 0, mPartData, 0, data.length);
+ }
+
+ /**
+ * @return A copy of the part data or null if the data wasn't set or
+ * the data is stored as Uri.
+ * @see #getDataUri
+ */
+ public byte[] getData() {
+ if(mPartData == null) {
+ return null;
+ }
+
+ byte[] byteArray = new byte[mPartData.length];
+ System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
+ return byteArray;
+ }
+
+ /**
+ * @return The length of the data, if this object have data, else 0.
+ */
+ public int getDataLength() {
+ if(mPartData != null){
+ return mPartData.length;
+ } else {
+ return 0;
+ }
+ }
+
+
+ /**
+ * Set data uri. The data are stored as Uri.
+ *
+ * @param uri the uri
+ */
+ public void setDataUri(Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * @return The Uri of the part data or null if the data wasn't set or
+ * the data is stored as byte array.
+ * @see #getData
+ */
+ public Uri getDataUri() {
+ return mUri;
+ }
+
+ /**
+ * Set Content-id value
+ *
+ * @param contentId the content-id value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentId(byte[] contentId) {
+ if((contentId == null) || (contentId.length == 0)) {
+ throw new IllegalArgumentException(
+ "Content-Id may not be null or empty.");
+ }
+
+ if ((contentId.length > 1)
+ && ((char) contentId[0] == '<')
+ && ((char) contentId[contentId.length - 1] == '>')) {
+ mPartHeader.put(P_CONTENT_ID, contentId);
+ return;
+ }
+
+ // Insert beginning '<' and trailing '>' for Content-Id.
+ byte[] buffer = new byte[contentId.length + 2];
+ buffer[0] = (byte) (0xff & '<');
+ buffer[buffer.length - 1] = (byte) (0xff & '>');
+ System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+ mPartHeader.put(P_CONTENT_ID, buffer);
+ }
+
+ /**
+ * Get Content-id value.
+ *
+ * @return the value
+ */
+ public byte[] getContentId() {
+ return (byte[]) mPartHeader.get(P_CONTENT_ID);
+ }
+
+ /**
+ * Set Char-set value.
+ *
+ * @param charset the value
+ */
+ public void setCharset(int charset) {
+ mPartHeader.put(P_CHARSET, charset);
+ }
+
+ /**
+ * Get Char-set value
+ *
+ * @return the charset value. Return 0 if charset was not set.
+ */
+ public int getCharset() {
+ Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+ if(charset == null) {
+ return 0;
+ } else {
+ return charset.intValue();
+ }
+ }
+
+ /**
+ * Set Content-Location value.
+ *
+ * @param contentLocation the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentLocation(byte[] contentLocation) {
+ if(contentLocation == null) {
+ throw new NullPointerException("null content-location");
+ }
+
+ mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+ }
+
+ /**
+ * Get Content-Location value.
+ *
+ * @return the value
+ * return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+ * return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+ * return PduPart.disposition[2] instead of <Octet 130> (Inline).
+ */
+ public byte[] getContentLocation() {
+ return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+ }
+
+ /**
+ * Set Content-Disposition value.
+ * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+ * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+ * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+ *
+ * @param contentDisposition the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentDisposition(byte[] contentDisposition) {
+ if(contentDisposition == null) {
+ throw new NullPointerException("null content-disposition");
+ }
+
+ mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+ }
+
+ /**
+ * Get Content-Disposition value.
+ *
+ * @return the value
+ */
+ public byte[] getContentDisposition() {
+ return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+ }
+
+ /**
+ * Set Content-Type value.
+ *
+ * @param contentType the content type
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentType(byte[] contentType) {
+ if(contentType == null) {
+ throw new NullPointerException("null content-type");
+ }
+
+ mPartHeader.put(P_CONTENT_TYPE, contentType);
+ }
+
+ /**
+ * Get Content-Type value of part.
+ *
+ * @return the value
+ */
+ public byte[] getContentType() {
+ return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-Transfer-Encoding value
+ *
+ * @param contentTransferEncoding the Content-Transfer-Encoding value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentTransferEncoding(byte[] contentTransferEncoding) {
+ if(contentTransferEncoding == null) {
+ throw new NullPointerException("null content-transfer-encoding");
+ }
+
+ mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+ }
+
+ /**
+ * Get Content-Transfer-Encoding value.
+ *
+ * @return the value
+ */
+ public byte[] getContentTransferEncoding() {
+ return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+ }
+
+ /**
+ * Set Content-type parameter: name.
+ *
+ * @param name the name value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setName(byte[] name) {
+ if(null == name) {
+ throw new NullPointerException("null content-id");
+ }
+
+ mPartHeader.put(P_NAME, name);
+ }
+
+ /**
+ * Get content-type parameter: name.
+ *
+ * @return the name
+ */
+ public byte[] getName() {
+ return (byte[]) mPartHeader.get(P_NAME);
+ }
+
+ /**
+ * Get Content-disposition parameter: filename
+ *
+ * @param fileName the filename value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFilename(byte[] fileName) {
+ if(null == fileName) {
+ throw new NullPointerException("null content-id");
+ }
+
+ mPartHeader.put(P_FILENAME, fileName);
+ }
+
+ /**
+ * Set Content-disposition parameter: filename
+ *
+ * @return the filename
+ */
+ public byte[] getFilename() {
+ return (byte[]) mPartHeader.get(P_FILENAME);
+ }
+
+ public String generateLocation() {
+ // Assumption: At least one of the content-location / name / filename
+ // or content-id should be set. This is guaranteed by the PduParser
+ // for incoming messages and by MM composer for outgoing messages.
+ byte[] location = (byte[]) mPartHeader.get(P_NAME);
+ if(null == location) {
+ location = (byte[]) mPartHeader.get(P_FILENAME);
+
+ if (null == location) {
+ location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+ }
+ }
+
+ if (null == location) {
+ byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+ return "cid:" + new String(contentId);
+ } else {
+ return new String(location);
+ }
+ }
+}
+
diff --git a/src/android/support/v7/mms/pdu/QuotedPrintable.java b/src/android/support/v7/mms/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..ea0abc5
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/QuotedPrintable.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+ private static byte ESCAPE_CHAR = '=';
+
+ /**
+ * Decodes an array quoted-printable characters into an array of original bytes.
+ * Escaped characters are converted back to their original representation.
+ *
+ * <p>
+ * This function implements a subset of
+ * quoted-printable encoding specification (rule #1 and rule #2)
+ * as defined in RFC 1521.
+ * </p>
+ *
+ * @param bytes array of quoted-printable characters
+ * @return array of original bytes,
+ * null if quoted-printable decoding is unsuccessful.
+ */
+ public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i];
+ if (b == ESCAPE_CHAR) {
+ try {
+ if('\r' == (char)bytes[i + 1] &&
+ '\n' == (char)bytes[i + 2]) {
+ i += 2;
+ continue;
+ }
+ int u = Character.digit((char) bytes[++i], 16);
+ int l = Character.digit((char) bytes[++i], 16);
+ if (u == -1 || l == -1) {
+ return null;
+ }
+ buffer.write((char) ((u << 4) + l));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ } else {
+ buffer.write(b);
+ }
+ }
+ return buffer.toByteArray();
+ }
+}
diff --git a/src/android/support/v7/mms/pdu/ReadOrigInd.java b/src/android/support/v7/mms/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..500a59d
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/ReadOrigInd.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+public class ReadOrigInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public ReadOrigInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ ReadOrigInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-MMS-Read-status value.
+ *
+ * @return the value
+ */
+ public int getReadStatus() {
+ return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Set X-MMS-Read-status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ */
+}
diff --git a/src/android/support/v7/mms/pdu/ReadRecInd.java b/src/android/support/v7/mms/pdu/ReadRecInd.java
new file mode 100644
index 0000000..9683d00
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/ReadRecInd.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+public class ReadRecInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-ReadRec.ind pdu.
+ *
+ * @param from the from value
+ * @param messageId the message ID value
+ * @param mmsVersion current viersion of mms
+ * @param readStatus the read status value
+ * @param to the to value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if messageId or to is null.
+ */
+ public ReadRecInd(EncodedStringValue from,
+ byte[] messageId,
+ int mmsVersion,
+ int readStatus,
+ EncodedStringValue[] to) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+ setFrom(from);
+ setMessageId(messageId);
+ setMmsVersion(mmsVersion);
+ setTo(to);
+ setReadStatus(readStatus);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ ReadRecInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-MMS-Read-status value.
+ *
+ * @return the value
+ */
+ public int getReadStatus() {
+ return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Set X-MMS-Read-status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ */
+}
diff --git a/src/android/support/v7/mms/pdu/RetrieveConf.java b/src/android/support/v7/mms/pdu/RetrieveConf.java
new file mode 100644
index 0000000..752ca04
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/RetrieveConf.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+/**
+ * M-Retrive.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public RetrieveConf() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ RetrieveConf(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Constructor with given headers and body
+ *
+ * @param headers Headers for this PDU.
+ * @param body Body of this PDu.
+ */
+ RetrieveConf(PduHeaders headers, PduBody body) {
+ super(headers, body);
+ }
+
+ /**
+ * Get CC value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getCc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+ }
+
+ /**
+ * Add a "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addCc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+ }
+
+ /**
+ * Get Content-type value.
+ *
+ * @return the value
+ */
+ public byte[] getContentType() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentType(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report value.
+ *
+ * @return the value
+ */
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-Mms-Read-Report value.
+ *
+ * @return the value
+ */
+ public int getReadReport() {
+ return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Read-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Get X-Mms-Retrieve-Status value.
+ *
+ * @return the value
+ */
+ public int getRetrieveStatus() {
+ return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
+ }
+
+ /**
+ * Set X-Mms-Retrieve-Status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+ }
+
+ /**
+ * Get X-Mms-Retrieve-Text value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue getRetrieveText() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+ }
+
+ /**
+ * Set X-Mms-Retrieve-Text value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setRetrieveText(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id.
+ *
+ * @return the value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getContentClass() {return 0x00;}
+ * public void setApplicId(byte value) {}
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public byte getDistributionIndicator() {return 0x00;}
+ * public void setDistributionIndicator(byte value) {}
+ *
+ * public PreviouslySentByValue getPreviouslySentBy() {return null;}
+ * public void setPreviouslySentBy(PreviouslySentByValue value) {}
+ *
+ * public PreviouslySentDateValue getPreviouslySentDate() {}
+ * public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+ *
+ * public MmFlagsValue getMmFlags() {return null;}
+ * public void setMmFlags(MmFlagsValue value) {}
+ *
+ * public MmStateValue getMmState() {return null;}
+ * public void getMmState(MmStateValue value) {}
+ *
+ * public byte[] getReplaceId() {return 0x00;}
+ * public void setReplaceId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ */
+}
diff --git a/src/android/support/v7/mms/pdu/SendConf.java b/src/android/support/v7/mms/pdu/SendConf.java
new file mode 100644
index 0000000..5486398
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/SendConf.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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 android.support.v7.mms.pdu;
+
+public class SendConf extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public SendConf() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ SendConf(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-Mms-Response-Status.
+ *
+ * @return the value
+ */
+ public int getResponseStatus() {
+ return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+ }
+
+ /**
+ * Set X-Mms-Response-Status.
+ *
+ * @param value the values
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setResponseStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getContentLocation() {return null;}
+ * public void setContentLocation(byte[] value) {}
+ *
+ * public EncodedStringValue getResponseText() {return null;}
+ * public void setResponseText(EncodedStringValue value) {}
+ *
+ * public byte getStoreStatus() {return 0x00;}
+ * public void setStoreStatus(byte value) {}
+ *
+ * public byte[] getStoreStatusText() {return null;}
+ * public void setStoreStatusText(byte[] value) {}
+ */
+}
diff --git a/src/android/support/v7/mms/pdu/SendReq.java b/src/android/support/v7/mms/pdu/SendReq.java
new file mode 100644
index 0000000..0b78cc3
--- /dev/null
+++ b/src/android/support/v7/mms/pdu/SendReq.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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 android.support.v7.mms.pdu;
+
+import android.util.Log;
+
+public class SendReq extends MultimediaMessagePdu {
+ private static final String TAG = "SendReq";
+
+ public SendReq() {
+ super();
+
+ try {
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+ // FIXME: Content-type must be decided according to whether
+ // SMIL part present.
+ setContentType("application/vnd.wap.multipart.related".getBytes());
+ setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+ setTransactionId(generateTransactionId());
+ } catch (InvalidHeaderValueException e) {
+ // Impossible to reach here since all headers we set above are valid.
+ Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private byte[] generateTransactionId() {
+ String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+ return transactionId.getBytes();
+ }
+
+ /**
+ * Constructor, used when composing a M-Send.req pdu.
+ *
+ * @param contentType the content type value
+ * @param from the from value
+ * @param mmsVersion current viersion of mms
+ * @param transactionId the transaction-id value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if contentType, form or transactionId is null.
+ */
+ public SendReq(byte[] contentType,
+ EncodedStringValue from,
+ int mmsVersion,
+ byte[] transactionId) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ setContentType(contentType);
+ setFrom(from);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ SendReq(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Constructor with given headers and body
+ *
+ * @param headers Headers for this PDU.
+ * @param body Body of this PDu.
+ */
+ SendReq(PduHeaders headers, PduBody body) {
+ super(headers, body);
+ }
+
+ /**
+ * Get Bcc value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getBcc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+ }
+
+ /**
+ * Add a "BCC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addBcc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+ }
+
+ /**
+ * Set "BCC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setBcc(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+ }
+
+ /**
+ * Get CC value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getCc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+ }
+
+ /**
+ * Add a "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addCc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+ }
+
+ /**
+ * Set "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setCc(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+ }
+
+ /**
+ * Get Content-type value.
+ *
+ * @return the value
+ */
+ public byte[] getContentType() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentType(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report value.
+ *
+ * @return the value
+ */
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Get X-Mms-Expiry value.
+ *
+ * Expiry-value = Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ *
+ * @return the value
+ */
+ public long getExpiry() {
+ return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Set X-Mms-Expiry value.
+ *
+ * @param value the value
+ */
+ public void setExpiry(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Get X-Mms-MessageSize value.
+ *
+ * Expiry-value = size of message
+ *
+ * @return the value
+ */
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Set X-Mms-MessageSize value.
+ *
+ * @param value the value
+ */
+ public void setMessageSize(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Read-Report value.
+ *
+ * @return the value
+ */
+ public int getReadReport() {
+ return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Read-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set "To" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte getAdaptationAllowed() {return 0};
+ * public void setAdaptationAllowed(btye value) {};
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getContentClass() {return 0x00;}
+ * public void setApplicId(byte value) {}
+ *
+ * public long getDeliveryTime() {return 0};
+ * public void setDeliveryTime(long value) {};
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public MmFlagsValue getMmFlags() {return null;}
+ * public void setMmFlags(MmFlagsValue value) {}
+ *
+ * public MmStateValue getMmState() {return null;}
+ * public void getMmState(MmStateValue value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getStore() {return 0x00;}
+ * public void setStore(byte value) {}
+ */
+}
diff --git a/src/com/android/messaging/BugleApplication.java b/src/com/android/messaging/BugleApplication.java
new file mode 100644
index 0000000..a5aea9f
--- /dev/null
+++ b/src/com/android/messaging/BugleApplication.java
@@ -0,0 +1,262 @@
+/*
+ * 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;
+
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.v7.mms.CarrierConfigValuesLoader;
+import android.support.v7.mms.MmsManager;
+import android.telephony.CarrierConfigManager;
+
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.receiver.SmsReceiver;
+import com.android.messaging.sms.ApnDatabase;
+import com.android.messaging.sms.BugleApnSettingsLoader;
+import com.android.messaging.sms.BugleUserAgentInfoLoader;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.Trace;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.lang.Thread.UncaughtExceptionHandler;
+
+/**
+ * The application object
+ */
+public class BugleApplication extends Application implements UncaughtExceptionHandler {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private UncaughtExceptionHandler sSystemUncaughtExceptionHandler;
+ private static boolean sRunningTests = false;
+
+ @VisibleForTesting
+ protected static void setTestsRunning() {
+ sRunningTests = true;
+ }
+
+ /**
+ * @return true if we're running unit tests.
+ */
+ public static boolean isRunningTests() {
+ return sRunningTests;
+ }
+
+ @Override
+ public void onCreate() {
+ Trace.beginSection("app.onCreate");
+ super.onCreate();
+
+ // Note onCreate is called in both test and real application environments
+ if (!sRunningTests) {
+ // Only create the factory if not running tests
+ FactoryImpl.register(getApplicationContext(), this);
+ } else {
+ LogUtil.e(TAG, "BugleApplication.onCreate: FactoryImpl.register skipped for test run");
+ }
+
+ sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+ Thread.setDefaultUncaughtExceptionHandler(this);
+ Trace.endSection();
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ // Update conversation drawables when changing writing systems
+ // (Right-To-Left / Left-To-Right)
+ ConversationDrawables.get().updateDrawables();
+ }
+
+ // Called by the "real" factory from FactoryImpl.register() (i.e. not run in tests)
+ public void initializeSync(final Factory factory) {
+ Trace.beginSection("app.initializeSync");
+ final Context context = factory.getApplicationContext();
+ final BugleGservices bugleGservices = factory.getBugleGservices();
+ final BuglePrefs buglePrefs = factory.getApplicationPrefs();
+ final DataModel dataModel = factory.getDataModel();
+ final CarrierConfigValuesLoader carrierConfigValuesLoader =
+ factory.getCarrierConfigValuesLoader();
+
+ maybeStartProfiling();
+
+ BugleApplication.updateAppConfig(context);
+
+ // Initialize MMS lib
+ initMmsLib(context, bugleGservices, carrierConfigValuesLoader);
+ // Initialize APN database
+ ApnDatabase.initializeAppContext(context);
+ // Fixup messages in flight if we crashed and send any pending
+ dataModel.onApplicationCreated();
+ // Register carrier config change receiver
+ if (OsUtil.isAtLeastM()) {
+ registerCarrierConfigChangeReceiver(context);
+ }
+
+ Trace.endSection();
+ }
+
+ private static void registerCarrierConfigChangeReceiver(final Context context) {
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ LogUtil.i(TAG, "Carrier config changed. Reloading MMS config.");
+ MmsConfig.loadAsync();
+ }
+ }, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+ }
+
+ private static void initMmsLib(final Context context, final BugleGservices bugleGservices,
+ final CarrierConfigValuesLoader carrierConfigValuesLoader) {
+ MmsManager.setApnSettingsLoader(new BugleApnSettingsLoader(context));
+ MmsManager.setCarrierConfigValuesLoader(carrierConfigValuesLoader);
+ MmsManager.setUserAgentInfoLoader(new BugleUserAgentInfoLoader(context));
+ MmsManager.setUseWakeLock(true);
+ // If Gservices is configured not to use mms api, force MmsManager to always use
+ // legacy mms sending logic
+ MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
+ BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
+ BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
+ bugleGservices.registerForChanges(new Runnable() {
+ @Override
+ public void run() {
+ MmsManager.setForceLegacyMms(!bugleGservices.getBoolean(
+ BugleGservicesKeys.USE_MMS_API_IF_PRESENT,
+ BugleGservicesKeys.USE_MMS_API_IF_PRESENT_DEFAULT));
+ }
+ });
+ }
+
+ public static void updateAppConfig(final Context context) {
+ // Make sure we set the correct state for the SMS/MMS receivers
+ SmsReceiver.updateSmsReceiveHandler(context);
+ }
+
+ // Called from thread started in FactoryImpl.register() (i.e. not run in tests)
+ public void initializeAsync(final Factory factory) {
+ // Handle shared prefs upgrade & Load MMS Configuration
+ Trace.beginSection("app.initializeAsync");
+ maybeHandleSharedPrefsUpgrade(factory);
+ MmsConfig.load();
+ Trace.endSection();
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "BugleApplication.onLowMemory");
+ }
+ Factory.get().reclaimMemory();
+ }
+
+ @Override
+ public void uncaughtException(final Thread thread, final Throwable ex) {
+ final boolean background = getMainLooper().getThread() != thread;
+ if (background) {
+ LogUtil.e(TAG, "Uncaught exception in background thread " + thread, ex);
+
+ final Handler handler = new Handler(getMainLooper());
+ handler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
+ }
+ });
+ } else {
+ sSystemUncaughtExceptionHandler.uncaughtException(thread, ex);
+ }
+ }
+
+ private void maybeStartProfiling() {
+ // App startup profiling support. To use it:
+ // adb shell setprop log.tag.BugleProfile DEBUG
+ // # Start the app, wait for a 30s, download trace file:
+ // adb pull /data/data/com.android.messaging/cache/startup.trace /tmp
+ // # Open trace file (using adt/tools/traceview)
+ if (android.util.Log.isLoggable(LogUtil.PROFILE_TAG, android.util.Log.DEBUG)) {
+ // Start method tracing with a big enough buffer and let it run for 30s.
+ // Note we use a logging tag as we don't want to wait for gservices to start up.
+ final File file = DebugUtils.getDebugFile("startup.trace", true);
+ if (file != null) {
+ android.os.Debug.startMethodTracing(file.getAbsolutePath(), 160 * 1024 * 1024);
+ new Handler(Looper.getMainLooper()).postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ android.os.Debug.stopMethodTracing();
+ // Allow world to see trace file
+ DebugUtils.ensureReadable(file);
+ LogUtil.d(LogUtil.PROFILE_TAG, "Tracing complete - "
+ + file.getAbsolutePath());
+ }
+ }, 30000);
+ }
+ }
+ }
+
+ private void maybeHandleSharedPrefsUpgrade(final Factory factory) {
+ final int existingVersion = factory.getApplicationPrefs().getInt(
+ BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
+ BuglePrefsKeys.SHARED_PREFERENCES_VERSION_DEFAULT);
+ final int targetVersion = Integer.parseInt(getString(R.string.pref_version));
+ if (targetVersion > existingVersion) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Upgrading shared prefs from " + existingVersion +
+ " to " + targetVersion);
+ try {
+ // Perform upgrade on application-wide prefs.
+ factory.getApplicationPrefs().onUpgrade(existingVersion, targetVersion);
+ // Perform upgrade on each subscription's prefs.
+ PhoneUtils.forEachActiveSubscription(new PhoneUtils.SubscriptionRunnable() {
+ @Override
+ public void runForSubscription(final int subId) {
+ factory.getSubscriptionPrefs(subId)
+ .onUpgrade(existingVersion, targetVersion);
+ }
+ });
+ factory.getApplicationPrefs().putInt(BuglePrefsKeys.SHARED_PREFERENCES_VERSION,
+ targetVersion);
+ } catch (final Exception ex) {
+ // Upgrade failed. Don't crash the app because we can always fall back to the
+ // default settings.
+ LogUtil.e(LogUtil.BUGLE_TAG, "Failed to upgrade shared prefs", ex);
+ }
+ } else if (targetVersion < existingVersion) {
+ // We don't care about downgrade since real user shouldn't encounter this, so log it
+ // and ignore any prefs migration.
+ LogUtil.e(LogUtil.BUGLE_TAG, "Shared prefs downgrade requested and ignored. " +
+ "oldVersion = " + existingVersion + ", newVersion = " + targetVersion);
+ }
+ }
+}
diff --git a/src/com/android/messaging/Factory.java b/src/com/android/messaging/Factory.java
new file mode 100644
index 0000000..c542a54
--- /dev/null
+++ b/src/com/android/messaging/Factory.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver;
+import com.android.messaging.datamodel.media.MediaCacheManager;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.sms.BugleCarrierConfigValuesLoader;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.MediaUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+public abstract class Factory {
+
+ // Making this volatile because on the unit tests, setInstance is called from a unit test
+ // thread, and then it's read on the UI thread.
+ private static volatile Factory sInstance;
+ @VisibleForTesting
+ protected static boolean sRegistered;
+ @VisibleForTesting
+ protected static boolean sInitialized;
+
+ public static Factory get() {
+ return sInstance;
+ }
+
+ protected static void setInstance(final Factory factory) {
+ // Not allowed to call this after real application initialization is complete
+ Assert.isTrue(!sRegistered);
+ Assert.isTrue(!sInitialized);
+ sInstance = factory;
+ }
+ public abstract void onRequiredPermissionsAcquired();
+
+ public abstract Context getApplicationContext();
+ public abstract DataModel getDataModel();
+ public abstract BugleGservices getBugleGservices();
+ public abstract BuglePrefs getApplicationPrefs();
+ public abstract BuglePrefs getSubscriptionPrefs(int subId);
+ public abstract BuglePrefs getWidgetPrefs();
+ public abstract UIIntents getUIIntents();
+ public abstract MemoryCacheManager getMemoryCacheManager();
+ public abstract MediaResourceManager getMediaResourceManager();
+ public abstract MediaCacheManager getMediaCacheManager();
+ public abstract ContactContentObserver getContactContentObserver();
+ public abstract PhoneUtils getPhoneUtils(int subId);
+ public abstract MediaUtil getMediaUtil();
+ public abstract BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader();
+ // Note this needs to run from any thread
+ public abstract void reclaimMemory();
+
+ public abstract void onActivityResume();
+}
diff --git a/src/com/android/messaging/FactoryImpl.java b/src/com/android/messaging/FactoryImpl.java
new file mode 100644
index 0000000..a862308
--- /dev/null
+++ b/src/com/android/messaging/FactoryImpl.java
@@ -0,0 +1,245 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Process;
+import android.telephony.SmsManager;
+import android.util.SparseArray;
+
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DataModelImpl;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.media.BugleMediaCacheManager;
+import com.android.messaging.datamodel.media.MediaCacheManager;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.sms.BugleCarrierConfigValuesLoader;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.UIIntentsImpl;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleApplicationPrefs;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesImpl;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BugleSubscriptionPrefs;
+import com.android.messaging.util.BugleWidgetPrefs;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaUtil;
+import com.android.messaging.util.MediaUtilImpl;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+class FactoryImpl extends Factory {
+ private BugleApplication mApplication;
+ private DataModel mDataModel;
+ private BugleGservices mBugleGservices;
+ private BugleApplicationPrefs mBugleApplicationPrefs;
+ private BugleWidgetPrefs mBugleWidgetPrefs;
+ private Context mApplicationContext;
+ private UIIntents mUIIntents;
+ private MemoryCacheManager mMemoryCacheManager;
+ private MediaResourceManager mMediaResourceManager;
+ private MediaCacheManager mMediaCacheManager;
+ private ContactContentObserver mContactContentObserver;
+ private PhoneUtils mPhoneUtils;
+ private MediaUtil mMediaUtil;
+ private SparseArray<BugleSubscriptionPrefs> mSubscriptionPrefs;
+ private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader;
+
+ // Cached instance for Pre-L_MR1
+ private static final Object PHONEUTILS_INSTANCE_LOCK = new Object();
+ private static PhoneUtils sPhoneUtilsInstancePreLMR1 = null;
+ // Cached subId->instance for L_MR1 and beyond
+ private static final ConcurrentHashMap<Integer, PhoneUtils> sPhoneUtilsInstanceCacheLMR1 =
+ new ConcurrentHashMap<>();
+
+ private FactoryImpl() {
+ }
+
+ public static Factory register(final Context applicationContext,
+ final BugleApplication application) {
+ // This only gets called once (from BugleApplication.onCreate), but its not called in tests.
+ Assert.isTrue(!sRegistered);
+ Assert.isNull(Factory.get());
+
+ final FactoryImpl factory = new FactoryImpl();
+ Factory.setInstance(factory);
+ sRegistered = true;
+
+ // At this point Factory is published. Services can now get initialized and depend on
+ // Factory.get().
+ factory.mApplication = application;
+ factory.mApplicationContext = applicationContext;
+ factory.mMemoryCacheManager = new MemoryCacheManager();
+ factory.mMediaCacheManager = new BugleMediaCacheManager();
+ factory.mMediaResourceManager = new MediaResourceManager();
+ factory.mBugleGservices = new BugleGservicesImpl(applicationContext);
+ factory.mBugleApplicationPrefs = new BugleApplicationPrefs(applicationContext);
+ factory.mDataModel = new DataModelImpl(applicationContext);
+ factory.mBugleWidgetPrefs = new BugleWidgetPrefs(applicationContext);
+ factory.mUIIntents = new UIIntentsImpl();
+ factory.mContactContentObserver = new ContactContentObserver();
+ factory.mMediaUtil = new MediaUtilImpl();
+ factory.mSubscriptionPrefs = new SparseArray<BugleSubscriptionPrefs>();
+ factory.mCarrierConfigValuesLoader = new BugleCarrierConfigValuesLoader(applicationContext);
+
+ Assert.initializeGservices(factory.mBugleGservices);
+ LogUtil.initializeGservices(factory.mBugleGservices);
+
+ if (OsUtil.hasRequiredPermissions()) {
+ factory.onRequiredPermissionsAcquired();
+ }
+
+ return factory;
+ }
+
+ @Override
+ public void onRequiredPermissionsAcquired() {
+ if (sInitialized) {
+ return;
+ }
+ sInitialized = true;
+
+ mApplication.initializeSync(this);
+
+ final Thread asyncInitialization = new Thread() {
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ mApplication.initializeAsync(FactoryImpl.this);
+ }
+ };
+ asyncInitialization.start();
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mApplicationContext;
+ }
+
+ @Override
+ public DataModel getDataModel() {
+ return mDataModel;
+ }
+
+ @Override
+ public BugleGservices getBugleGservices() {
+ return mBugleGservices;
+ }
+
+ @Override
+ public BuglePrefs getApplicationPrefs() {
+ return mBugleApplicationPrefs;
+ }
+
+ @Override
+ public BuglePrefs getWidgetPrefs() {
+ return mBugleWidgetPrefs;
+ }
+
+ @Override
+ public BuglePrefs getSubscriptionPrefs(int subId) {
+ subId = PhoneUtils.getDefault().getEffectiveSubId(subId);
+ BugleSubscriptionPrefs pref = mSubscriptionPrefs.get(subId);
+ if (pref == null) {
+ synchronized (this) {
+ if ((pref = mSubscriptionPrefs.get(subId)) == null) {
+ pref = new BugleSubscriptionPrefs(getApplicationContext(), subId);
+ mSubscriptionPrefs.put(subId, pref);
+ }
+ }
+ }
+ return pref;
+ }
+
+ @Override
+ public UIIntents getUIIntents() {
+ return mUIIntents;
+ }
+
+ @Override
+ public MemoryCacheManager getMemoryCacheManager() {
+ return mMemoryCacheManager;
+ }
+
+ @Override
+ public MediaResourceManager getMediaResourceManager() {
+ return mMediaResourceManager;
+ }
+
+ @Override
+ public MediaCacheManager getMediaCacheManager() {
+ return mMediaCacheManager;
+ }
+
+ @Override
+ public ContactContentObserver getContactContentObserver() {
+ return mContactContentObserver;
+ }
+
+ @Override
+ public PhoneUtils getPhoneUtils(int subId) {
+ if (OsUtil.isAtLeastL_MR1()) {
+ if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) {
+ subId = SmsManager.getDefaultSmsSubscriptionId();
+ }
+ if (subId < 0) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "PhoneUtils.getForLMR1(): invalid subId = " + subId);
+ subId = ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+ PhoneUtils instance = sPhoneUtilsInstanceCacheLMR1.get(subId);
+ if (instance == null) {
+ instance = new PhoneUtils.PhoneUtilsLMR1(subId);
+ sPhoneUtilsInstanceCacheLMR1.putIfAbsent(subId, instance);
+ }
+ return instance;
+ } else {
+ Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
+ if (sPhoneUtilsInstancePreLMR1 == null) {
+ synchronized (PHONEUTILS_INSTANCE_LOCK) {
+ if (sPhoneUtilsInstancePreLMR1 == null) {
+ sPhoneUtilsInstancePreLMR1 = new PhoneUtils.PhoneUtilsPreLMR1();
+ }
+ }
+ }
+ return sPhoneUtilsInstancePreLMR1;
+ }
+ }
+
+ @Override
+ public void reclaimMemory() {
+ mMemoryCacheManager.reclaimMemory();
+ }
+
+ @Override
+ public void onActivityResume() {
+ }
+
+ @Override
+ public MediaUtil getMediaUtil() {
+ return mMediaUtil;
+ }
+
+ @Override
+ public BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader() {
+ return mCarrierConfigValuesLoader;
+ }
+}
diff --git a/src/com/android/messaging/annotation/VisibleForAnimation.java b/src/com/android/messaging/annotation/VisibleForAnimation.java
new file mode 100644
index 0000000..f97d827
--- /dev/null
+++ b/src/com/android/messaging/annotation/VisibleForAnimation.java
@@ -0,0 +1,23 @@
+/*
+ * 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.annotation;
+
+/**
+ * An annotation for class members that are made visible for Android's ObjectAnimator to work
+ * properly through reflection.
+ */
+public @interface VisibleForAnimation {
+}
diff --git a/src/com/android/messaging/datamodel/BitmapPool.java b/src/com/android/messaging/datamodel/BitmapPool.java
new file mode 100644
index 0000000..1ec4f76
--- /dev/null
+++ b/src/com/android/messaging/datamodel/BitmapPool.java
@@ -0,0 +1,364 @@
+/*
+ * 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.datamodel;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.io.InputStream;
+
+/**
+ * Class for creating / loading / reusing bitmaps. This class allow the user to create a new bitmap,
+ * reuse an bitmap from the pool and to return a bitmap for future reuse. The pool of bitmaps
+ * allows for faster decode and more efficient memory usage.
+ * Note: consumers should not create BitmapPool directly, but instead get the pool they want from
+ * the BitmapPoolManager.
+ */
+public class BitmapPool implements MemoryCache {
+ public static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF;
+
+ protected static final boolean VERBOSE = false;
+
+ /**
+ * Number of reuse failures to skip before reporting.
+ */
+ private static final int FAILED_REPORTING_FREQUENCY = 100;
+
+ /**
+ * Count of reuse failures which have occurred.
+ */
+ private static volatile int sFailedBitmapReuseCount = 0;
+
+ /**
+ * Overall pool data structure which currently only supports rectangular bitmaps. The size of
+ * one of the sides is used to index into the SparseArray.
+ */
+ private final SparseArray<SingleSizePool> mPool;
+ private final Object mPoolLock = new Object();
+ private final String mPoolName;
+ private final int mMaxSize;
+
+ /**
+ * Inner structure which holds a pool of bitmaps all the same size (i.e. all have the same
+ * width as each other and height as each other, but not necessarily the same).
+ */
+ private class SingleSizePool {
+ int mNumItems;
+ final Bitmap[] mBitmaps;
+
+ SingleSizePool(final int maxPoolSize) {
+ mNumItems = 0;
+ mBitmaps = new Bitmap[maxPoolSize];
+ }
+ }
+
+ /**
+ * Creates a pool of reused bitmaps with helper decode methods which will attempt to use the
+ * reclaimed bitmaps. This will help speed up the creation of bitmaps by using already allocated
+ * bitmaps.
+ * @param maxSize The overall max size of the pool. When the pool exceeds this size, all calls
+ * to reclaimBitmap(Bitmap) will result in recycling the bitmap.
+ * @param name Name of the bitmap pool and only used for logging. Can not be null.
+ */
+ BitmapPool(final int maxSize, @NonNull final String name) {
+ Assert.isTrue(maxSize > 0);
+ Assert.isTrue(!TextUtils.isEmpty(name));
+ mPoolName = name;
+ mMaxSize = maxSize;
+ mPool = new SparseArray<SingleSizePool>();
+ }
+
+ @Override
+ public void reclaim() {
+ synchronized (mPoolLock) {
+ for (int p = 0; p < mPool.size(); p++) {
+ final SingleSizePool singleSizePool = mPool.valueAt(p);
+ for (int i = 0; i < singleSizePool.mNumItems; i++) {
+ singleSizePool.mBitmaps[i].recycle();
+ singleSizePool.mBitmaps[i] = null;
+ }
+ singleSizePool.mNumItems = 0;
+ }
+ mPool.clear();
+ }
+ }
+
+ /**
+ * Creates a new BitmapFactory.Options.
+ */
+ public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled,
+ final int inputDensity, final int targetDensity) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inScaled = scaled;
+ options.inDensity = inputDensity;
+ options.inTargetDensity = targetDensity;
+ options.inSampleSize = 1;
+ options.inJustDecodeBounds = false;
+ options.inMutable = true;
+ return options;
+ }
+
+ /**
+ * @return The pool key for the provided image dimensions or 0 if either width or height is
+ * greater than the max supported image dimension.
+ */
+ private int getPoolKey(final int width, final int height) {
+ if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) {
+ return 0;
+ }
+ return (width << 16) | height;
+ }
+
+ /**
+ *
+ * @return A bitmap in the pool with the specified dimensions or null if no bitmap with the
+ * specified dimension is available.
+ */
+ private Bitmap findPoolBitmap(final int width, final int height) {
+ final int poolKey = getPoolKey(width, height);
+ if (poolKey != 0) {
+ synchronized (mPoolLock) {
+ // Take a bitmap from the pool if one is available
+ final SingleSizePool singlePool = mPool.get(poolKey);
+ if (singlePool != null && singlePool.mNumItems > 0) {
+ singlePool.mNumItems--;
+ final Bitmap foundBitmap = singlePool.mBitmaps[singlePool.mNumItems];
+ singlePool.mBitmaps[singlePool.mNumItems] = null;
+ return foundBitmap;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Internal function to try and find a bitmap in the pool which matches the desired width and
+ * height and then set that in the bitmap options properly.
+ *
+ * TODO: Why do we take a width/height? Shouldn't this already be in the
+ * BitmapFactory.Options instance? Can we assert that they match?
+ * @param optionsTmp The BitmapFactory.Options to update with the bitmap for the system to try
+ * to reuse.
+ * @param width The width of the reusable bitmap.
+ * @param height The height of the reusable bitmap.
+ */
+ private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width,
+ final int height) {
+ if (optionsTmp.inJustDecodeBounds) {
+ return;
+ }
+ optionsTmp.inBitmap = findPoolBitmap(width, height);
+ }
+
+ /**
+ * Load a resource into a bitmap. Uses a bitmap from the pool if possible to reduce memory
+ * turnover.
+ * @param resourceId Resource id to load.
+ * @param resources Application resources. Cannot be null.
+ * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot
+ * be null.
+ * @param width The width of the bitmap.
+ * @param height The height of the bitmap.
+ * @return The decoded Bitmap with the resource drawn in it.
+ */
+ public Bitmap decodeSampledBitmapFromResource(final int resourceId,
+ @NonNull final Resources resources, @NonNull final BitmapFactory.Options optionsTmp,
+ final int width, final int height) {
+ Assert.notNull(resources);
+ Assert.notNull(optionsTmp);
+ Assert.isTrue(width > 0);
+ Assert.isTrue(height > 0);
+ assignPoolBitmap(optionsTmp, width, height);
+ Bitmap b = null;
+ try {
+ b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp);
+ } catch (final IllegalArgumentException e) {
+ // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
+ if (optionsTmp.inBitmap != null) {
+ optionsTmp.inBitmap = null;
+ b = BitmapFactory.decodeResource(resources, resourceId, optionsTmp);
+ sFailedBitmapReuseCount++;
+ if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
+ LogUtil.w(LogUtil.BUGLE_TAG,
+ "Pooled bitmap consistently not being reused count = " +
+ sFailedBitmapReuseCount);
+ }
+ }
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding resource " + resourceId);
+ reclaim();
+ }
+ return b;
+ }
+
+ /**
+ * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce memory
+ * turnover.
+ * @param inputStream InputStream load. Cannot be null.
+ * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). Cannot
+ * be null.
+ * @param width The width of the bitmap.
+ * @param height The height of the bitmap.
+ * @return The decoded Bitmap with the resource drawn in it.
+ */
+ public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream,
+ @NonNull final BitmapFactory.Options optionsTmp,
+ final int width, final int height) {
+ Assert.notNull(inputStream);
+ Assert.isTrue(width > 0);
+ Assert.isTrue(height > 0);
+ assignPoolBitmap(optionsTmp, width, height);
+ Bitmap b = null;
+ try {
+ b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
+ } catch (final IllegalArgumentException e) {
+ // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
+ if (optionsTmp.inBitmap != null) {
+ optionsTmp.inBitmap = null;
+ b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
+ sFailedBitmapReuseCount++;
+ if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
+ LogUtil.w(LogUtil.BUGLE_TAG,
+ "Pooled bitmap consistently not being reused count = " +
+ sFailedBitmapReuseCount);
+ }
+ }
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Oom decoding inputStream");
+ reclaim();
+ }
+ return b;
+ }
+
+ /**
+ * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce memory
+ * turnover.
+ * @param bytes Encoded bytes to draw on the bitmap. Cannot be null.
+ * @param optionsTmp The bitmap will set here and the input should be generated from
+ * getBitmapOptionsForPool(). Cannot be null.
+ * @param width The width of the bitmap.
+ * @param height The height of the bitmap.
+ * @return A Bitmap with the encoded bytes drawn in it.
+ */
+ public Bitmap decodeByteArray(@NonNull final byte[] bytes,
+ @NonNull final BitmapFactory.Options optionsTmp, final int width,
+ final int height) throws OutOfMemoryError {
+ Assert.notNull(bytes);
+ Assert.notNull(optionsTmp);
+ Assert.isTrue(width > 0);
+ Assert.isTrue(height > 0);
+ assignPoolBitmap(optionsTmp, width, height);
+ Bitmap b = null;
+ try {
+ b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
+ } catch (final IllegalArgumentException e) {
+ if (VERBOSE) {
+ LogUtil.v(LogUtil.BUGLE_TAG, "BitmapPool(" + mPoolName +
+ ") Unable to use pool bitmap");
+ }
+ // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
+ // (i.e. without the bitmap from the pool)
+ if (optionsTmp.inBitmap != null) {
+ optionsTmp.inBitmap = null;
+ b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
+ sFailedBitmapReuseCount++;
+ if (sFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
+ LogUtil.w(LogUtil.BUGLE_TAG,
+ "Pooled bitmap consistently not being reused count = " +
+ sFailedBitmapReuseCount);
+ }
+ }
+ }
+ return b;
+ }
+
+ /**
+ * Creates a bitmap with the given size, this will reuse a bitmap in the pool, if one is
+ * available, otherwise this will create a new one.
+ * @param width The desired width of the bitmap.
+ * @param height The desired height of the bitmap.
+ * @return A bitmap with the desired width and height, this maybe a reused bitmap from the pool.
+ */
+ public Bitmap createOrReuseBitmap(final int width, final int height) {
+ Bitmap b = findPoolBitmap(width, height);
+ if (b == null) {
+ b = createBitmap(width, height);
+ }
+ return b;
+ }
+
+ /**
+ * This will create a new bitmap regardless of pool state.
+ * @param width The desired width of the bitmap.
+ * @param height The desired height of the bitmap.
+ * @return A bitmap with the desired width and height.
+ */
+ private Bitmap createBitmap(final int width, final int height) {
+ return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ /**
+ * Called when a bitmap is finished being used so that it can be used for another bitmap in the
+ * future or recycled. Any bitmaps returned should not be used by the caller again.
+ * @param b The bitmap to return to the pool for future usage or recycled. This cannot be null.
+ */
+ public void reclaimBitmap(@NonNull final Bitmap b) {
+ Assert.notNull(b);
+ final int poolKey = getPoolKey(b.getWidth(), b.getHeight());
+ if (poolKey == 0 || !b.isMutable()) {
+ // Unsupported image dimensions or a immutable bitmap.
+ b.recycle();
+ return;
+ }
+ synchronized (mPoolLock) {
+ SingleSizePool singleSizePool = mPool.get(poolKey);
+ if (singleSizePool == null) {
+ singleSizePool = new SingleSizePool(mMaxSize);
+ mPool.append(poolKey, singleSizePool);
+ }
+ if (singleSizePool.mNumItems < singleSizePool.mBitmaps.length) {
+ singleSizePool.mBitmaps[singleSizePool.mNumItems] = b;
+ singleSizePool.mNumItems++;
+ } else {
+ b.recycle();
+ }
+ }
+ }
+
+ /**
+ * @return whether the pool is full for a given width and height.
+ */
+ public boolean isFull(final int width, final int height) {
+ final int poolKey = getPoolKey(width, height);
+ synchronized (mPoolLock) {
+ final SingleSizePool singleSizePool = mPool.get(poolKey);
+ if (singleSizePool != null &&
+ singleSizePool.mNumItems >= singleSizePool.mBitmaps.length) {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/BoundCursorLoader.java b/src/com/android/messaging/datamodel/BoundCursorLoader.java
new file mode 100644
index 0000000..84d38e6
--- /dev/null
+++ b/src/com/android/messaging/datamodel/BoundCursorLoader.java
@@ -0,0 +1,46 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.net.Uri;
+
+/**
+ * Extension to basic cursor loader that has an attached binding id
+ */
+public class BoundCursorLoader extends CursorLoader {
+ private final String mBindingId;
+
+ /**
+ * Create cursor loader for associated binding id
+ */
+ public BoundCursorLoader(final String bindingId, final Context context, final Uri uri,
+ final String[] projection, final String selection, final String[] selectionArgs,
+ final String sortOrder) {
+ super(context, uri, projection, selection, selectionArgs, sortOrder);
+ mBindingId = bindingId;
+ }
+
+ /**
+ * Binding id associated with this loader - consume can check to verify data still valid
+ * @return
+ */
+ public String getBindingId() {
+ return mBindingId;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java b/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
new file mode 100644
index 0000000..8c40177
--- /dev/null
+++ b/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
@@ -0,0 +1,1919 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDoneException;
+import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.SimpleArrayMap;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.ParticipantRefresh.ConversationParticipantsQuery;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.UriUtil;
+import com.android.messaging.widget.WidgetConversationProvider;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import javax.annotation.Nullable;
+
+
+/**
+ * This class manages updating our local database
+ */
+public class BugleDatabaseOperations {
+
+ private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
+
+ // Global cache of phone numbers -> participant id mapping since this call is expensive.
+ private static final ArrayMap<String, String> sNormalizedPhoneNumberToParticipantIdCache =
+ new ArrayMap<String, String>();
+
+ /**
+ * Convert list of recipient strings (email/phone number) into list of ConversationParticipants
+ *
+ * @param recipients The recipient list
+ * @param refSubId The subId used to normalize phone numbers in the recipients
+ */
+ static ArrayList<ParticipantData> getConversationParticipantsFromRecipients(
+ final List<String> recipients, final int refSubId) {
+ // Generate a list of partially formed participants
+ final ArrayList<ParticipantData> participants = new
+ ArrayList<ParticipantData>();
+
+ if (recipients != null) {
+ for (final String recipient : recipients) {
+ participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, refSubId));
+ }
+ }
+ return participants;
+ }
+
+ /**
+ * Sanitize a given list of conversation participants by de-duping and stripping out self
+ * phone number in group conversation.
+ */
+ @DoesNotRunOnMainThread
+ public static void sanitizeConversationParticipants(final List<ParticipantData> participants) {
+ Assert.isNotMainThread();
+ if (participants.size() > 0) {
+ // First remove redundant phone numbers
+ final HashSet<String> recipients = new HashSet<String>();
+ for (int i = participants.size() - 1; i >= 0; i--) {
+ final String recipient = participants.get(i).getNormalizedDestination();
+ if (!recipients.contains(recipient)) {
+ recipients.add(recipient);
+ } else {
+ participants.remove(i);
+ }
+ }
+ if (participants.size() > 1) {
+ // Remove self phone number from group conversation.
+ final HashSet<String> selfNumbers =
+ PhoneUtils.getDefault().getNormalizedSelfNumbers();
+ int removed = 0;
+ // Do this two-pass scan to avoid unnecessary memory allocation.
+ // Prescan to count the self numbers in the list
+ for (final ParticipantData p : participants) {
+ if (selfNumbers.contains(p.getNormalizedDestination())) {
+ removed++;
+ }
+ }
+ // If all are self numbers, maybe that's what the user wants, just leave
+ // the participants as is. Otherwise, do another scan to remove self numbers.
+ if (removed < participants.size()) {
+ for (int i = participants.size() - 1; i >= 0; i--) {
+ final String recipient = participants.get(i).getNormalizedDestination();
+ if (selfNumbers.contains(recipient)) {
+ participants.remove(i);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert list of ConversationParticipants into recipient strings (email/phone number)
+ */
+ @DoesNotRunOnMainThread
+ public static ArrayList<String> getRecipientsFromConversationParticipants(
+ final List<ParticipantData> participants) {
+ Assert.isNotMainThread();
+ // First find the thread id for this list of participants.
+ final ArrayList<String> recipients = new ArrayList<String>();
+
+ for (final ParticipantData participant : participants) {
+ recipients.add(participant.getSendDestination());
+ }
+ return recipients;
+ }
+
+ /**
+ * Get or create a conversation based on the message's thread id
+ *
+ * NOTE: There are phones on which you can't get the recipients from the thread id for SMS
+ * until you have a message, so use getOrCreateConversationFromRecipient instead.
+ *
+ * TODO: Should this be in MMS/SMS code?
+ *
+ * @param db the database
+ * @param threadId The message's thread
+ * @param senderBlocked Flag whether sender of message is in blocked people list
+ * @param refSubId The reference subId for canonicalize phone numbers
+ * @return conversationId
+ */
+ @DoesNotRunOnMainThread
+ public static String getOrCreateConversationFromThreadId(final DatabaseWrapper db,
+ final long threadId, final boolean senderBlocked, final int refSubId) {
+ Assert.isNotMainThread();
+ final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
+ final ArrayList<ParticipantData> participants =
+ getConversationParticipantsFromRecipients(recipients, refSubId);
+
+ return getOrCreateConversation(db, threadId, senderBlocked, participants, false, false,
+ null);
+ }
+
+ /**
+ * Get or create a conversation based on provided recipient
+ *
+ * @param db the database
+ * @param threadId The message's thread
+ * @param senderBlocked Flag whether sender of message is in blocked people list
+ * @param recipient recipient for thread
+ * @return conversationId
+ */
+ @DoesNotRunOnMainThread
+ public static String getOrCreateConversationFromRecipient(final DatabaseWrapper db,
+ final long threadId, final boolean senderBlocked, final ParticipantData recipient) {
+ Assert.isNotMainThread();
+ final ArrayList<ParticipantData> recipients = new ArrayList<>(1);
+ recipients.add(recipient);
+ return getOrCreateConversation(db, threadId, senderBlocked, recipients, false, false, null);
+ }
+
+ /**
+ * Get or create a conversation based on provided participants
+ *
+ * @param db the database
+ * @param threadId The message's thread
+ * @param archived Flag whether the conversation should be created archived
+ * @param participants list of conversation participants
+ * @param noNotification If notification should be disabled
+ * @param noVibrate If vibrate on notification should be disabled
+ * @param soundUri If there is custom sound URI
+ * @return a conversation id
+ */
+ @DoesNotRunOnMainThread
+ public static String getOrCreateConversation(final DatabaseWrapper db, final long threadId,
+ final boolean archived, final ArrayList<ParticipantData> participants,
+ boolean noNotification, boolean noVibrate, String soundUri) {
+ Assert.isNotMainThread();
+
+ // Check to see if this conversation is already in out local db cache
+ String conversationId = BugleDatabaseOperations.getExistingConversation(db, threadId,
+ false);
+
+ if (conversationId == null) {
+ final String conversationName = ConversationListItemData.generateConversationName(
+ participants);
+
+ // Create the conversation with the default self participant which always maps to
+ // the system default subscription.
+ final ParticipantData self = ParticipantData.getSelfParticipant(
+ ParticipantData.DEFAULT_SELF_SUB_ID);
+
+ db.beginTransaction();
+ try {
+ // Look up the "self" participantId (creating if necessary)
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+ // Create a new conversation
+ conversationId = BugleDatabaseOperations.createConversationInTransaction(
+ db, threadId, conversationName, selfId, participants, archived,
+ noNotification, noVibrate, soundUri);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ return conversationId;
+ }
+
+ /**
+ * Get a conversation from the local DB based on the message's thread id.
+ *
+ * @param dbWrapper The database
+ * @param threadId The message's thread in the SMS database
+ * @param senderBlocked Flag whether sender of message is in blocked people list
+ * @return The existing conversation id or null
+ */
+ @VisibleForTesting
+ @DoesNotRunOnMainThread
+ public static String getExistingConversation(final DatabaseWrapper dbWrapper,
+ final long threadId, final boolean senderBlocked) {
+ Assert.isNotMainThread();
+ String conversationId = null;
+
+ Cursor cursor = null;
+ try {
+ // Look for an existing conversation in the db with this thread id
+ cursor = dbWrapper.rawQuery("SELECT " + ConversationColumns._ID
+ + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
+ + " WHERE " + ConversationColumns.SMS_THREAD_ID + "=" + threadId,
+ null);
+
+ if (cursor.moveToFirst()) {
+ Assert.isTrue(cursor.getCount() == 1);
+ conversationId = cursor.getString(0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return conversationId;
+ }
+
+ /**
+ * Get the thread id for an existing conversation from the local DB.
+ *
+ * @param dbWrapper The database
+ * @param conversationId The conversation to look up thread for
+ * @return The thread id. Returns -1 if the conversation was not found or if it was found
+ * but the thread column was NULL.
+ */
+ @DoesNotRunOnMainThread
+ public static long getThreadId(final DatabaseWrapper dbWrapper, final String conversationId) {
+ Assert.isNotMainThread();
+ long threadId = -1;
+
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns.SMS_THREAD_ID },
+ ConversationColumns._ID + " =?",
+ new String[] { conversationId },
+ null, null, null);
+
+ if (cursor.moveToFirst()) {
+ Assert.isTrue(cursor.getCount() == 1);
+ if (!cursor.isNull(0)) {
+ threadId = cursor.getLong(0);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return threadId;
+ }
+
+ @DoesNotRunOnMainThread
+ public static boolean isBlockedDestination(final DatabaseWrapper db, final String destination) {
+ Assert.isNotMainThread();
+ return isBlockedParticipant(db, destination, ParticipantColumns.NORMALIZED_DESTINATION);
+ }
+
+ static boolean isBlockedParticipant(final DatabaseWrapper db, final String participantId) {
+ return isBlockedParticipant(db, participantId, ParticipantColumns._ID);
+ }
+
+ static boolean isBlockedParticipant(final DatabaseWrapper db, final String value,
+ final String column) {
+ Cursor cursor = null;
+ try {
+ cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ new String[] { ParticipantColumns.BLOCKED },
+ column + "=? AND " + ParticipantColumns.SUB_ID + "=?",
+ new String[] { value,
+ Integer.toString(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
+ null, null, null);
+
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ return cursor.getInt(0) == 1;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return false; // if there's no row, it's not blocked :-)
+ }
+
+ /**
+ * Create a conversation in the local DB based on the message's thread id.
+ *
+ * It's up to the caller to make sure that this is all inside a transaction. It will return
+ * null if it's not in the local DB.
+ *
+ * @param dbWrapper The database
+ * @param threadId The message's thread
+ * @param selfId The selfId to make default for this conversation
+ * @param archived Flag whether the conversation should be created archived
+ * @param noNotification If notification should be disabled
+ * @param noVibrate If vibrate on notification should be disabled
+ * @param soundUri The customized sound
+ * @return The existing conversation id or new conversation id
+ */
+ static String createConversationInTransaction(final DatabaseWrapper dbWrapper,
+ final long threadId, final String conversationName, final String selfId,
+ final List<ParticipantData> participants, final boolean archived,
+ boolean noNotification, boolean noVibrate, String soundUri) {
+ // We want conversation and participant creation to be atomic
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ boolean hasEmailAddress = false;
+ for (final ParticipantData participant : participants) {
+ Assert.isTrue(!participant.isSelf());
+ if (participant.isEmail()) {
+ hasEmailAddress = true;
+ }
+ }
+
+ // TODO : Conversations state - normal vs. archived
+
+ // Insert a new local conversation for this thread id
+ final ContentValues values = new ContentValues();
+ values.put(ConversationColumns.SMS_THREAD_ID, threadId);
+ // Start with conversation hidden - sending a message or saving a draft will change that
+ values.put(ConversationColumns.SORT_TIMESTAMP, 0L);
+ values.put(ConversationColumns.CURRENT_SELF_ID, selfId);
+ values.put(ConversationColumns.PARTICIPANT_COUNT, participants.size());
+ values.put(ConversationColumns.INCLUDE_EMAIL_ADDRESS, (hasEmailAddress ? 1 : 0));
+ if (archived) {
+ values.put(ConversationColumns.ARCHIVE_STATUS, 1);
+ }
+ if (noNotification) {
+ values.put(ConversationColumns.NOTIFICATION_ENABLED, 0);
+ }
+ if (noVibrate) {
+ values.put(ConversationColumns.NOTIFICATION_VIBRATION, 0);
+ }
+ if (!TextUtils.isEmpty(soundUri)) {
+ values.put(ConversationColumns.NOTIFICATION_SOUND_URI, soundUri);
+ }
+
+ fillParticipantData(values, participants);
+
+ final long conversationRowId = dbWrapper.insert(DatabaseHelper.CONVERSATIONS_TABLE, null,
+ values);
+
+ Assert.isTrue(conversationRowId != -1);
+ if (conversationRowId == -1) {
+ LogUtil.e(TAG, "BugleDatabaseOperations : failed to insert conversation into table");
+ return null;
+ }
+
+ final String conversationId = Long.toString(conversationRowId);
+
+ // Make sure that participants are added for this conversation
+ for (final ParticipantData participant : participants) {
+ // TODO: Use blocking information
+ addParticipantToConversation(dbWrapper, participant, conversationId);
+ }
+
+ // Now fully resolved participants available can update conversation name / avatar.
+ // b/16437575: We cannot use the participants directly, but instead have to call
+ // getParticipantsForConversation() to retrieve the actual participants. This is needed
+ // because the call to addParticipantToConversation() won't fill up the ParticipantData
+ // if the participant already exists in the participant table. For example, say you have
+ // an existing conversation with John. Now if you create a new group conversation with
+ // Jeff & John with only their phone numbers, then when we try to add John's number to the
+ // group conversation, we see that he's already in the participant table, therefore we
+ // short-circuit any steps to actually fill out the ParticipantData for John other than
+ // just returning his participant id. Eventually, the ParticipantData we have is still the
+ // raw data with just the phone number. getParticipantsForConversation(), on the other
+ // hand, will fill out all the info for each participant from the participants table.
+ updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId,
+ getParticipantsForConversation(dbWrapper, conversationId));
+
+ return conversationId;
+ }
+
+ private static void fillParticipantData(final ContentValues values,
+ final List<ParticipantData> participants) {
+ if (participants != null && !participants.isEmpty()) {
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(participants);
+ values.put(ConversationColumns.ICON, avatarUri.toString());
+
+ long contactId;
+ String lookupKey;
+ String destination;
+ if (participants.size() == 1) {
+ final ParticipantData firstParticipant = participants.get(0);
+ contactId = firstParticipant.getContactId();
+ lookupKey = firstParticipant.getLookupKey();
+ destination = firstParticipant.getNormalizedDestination();
+ } else {
+ contactId = 0;
+ lookupKey = null;
+ destination = null;
+ }
+
+ values.put(ConversationColumns.PARTICIPANT_CONTACT_ID, contactId);
+ values.put(ConversationColumns.PARTICIPANT_LOOKUP_KEY, lookupKey);
+ values.put(ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION, destination);
+ }
+ }
+
+ /**
+ * Delete conversation and associated messages/parts
+ */
+ @DoesNotRunOnMainThread
+ public static boolean deleteConversation(final DatabaseWrapper dbWrapper,
+ final String conversationId, final long cutoffTimestamp) {
+ Assert.isNotMainThread();
+ dbWrapper.beginTransaction();
+ boolean conversationDeleted = false;
+ boolean conversationMessagesDeleted = false;
+ try {
+ // Delete existing messages
+ if (cutoffTimestamp == Long.MAX_VALUE) {
+ // Delete parts and messages
+ dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId });
+ conversationMessagesDeleted = true;
+ } else {
+ // Delete all messages prior to the cutoff
+ dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.CONVERSATION_ID + "=? AND "
+ + MessageColumns.RECEIVED_TIMESTAMP + "<=?",
+ new String[] { conversationId, Long.toString(cutoffTimestamp) });
+
+ // Delete any draft message. The delete above may not always include the draft,
+ // because under certain scenarios (e.g. sending messages in progress), the draft
+ // timestamp can be larger than the cutoff time, which is generally the conversation
+ // sort timestamp. Because of how the sms/mms provider works on some newer
+ // devices, it's important that we never delete all the messages in a conversation
+ // without also deleting the conversation itself (see b/20262204 for details).
+ dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
+ new String[] {
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
+ conversationId
+ });
+
+ // Check to see if there are any messages left in the conversation
+ final long count = dbWrapper.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.CONVERSATION_ID + "=?", new String[] { conversationId });
+ conversationMessagesDeleted = (count == 0);
+
+ // Log detail information if there are still messages left in the conversation
+ if (!conversationMessagesDeleted) {
+ final long maxTimestamp =
+ getConversationMaxTimestamp(dbWrapper, conversationId);
+ LogUtil.w(TAG, "BugleDatabaseOperations:"
+ + " cannot delete all messages in a conversation"
+ + ", after deletion: count=" + count
+ + ", max timestamp=" + maxTimestamp
+ + ", cutoff timestamp=" + cutoffTimestamp);
+ }
+ }
+
+ if (conversationMessagesDeleted) {
+ // Delete conversation row
+ final int count = dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE,
+ ConversationColumns._ID + "=?", new String[] { conversationId });
+ conversationDeleted = (count > 0);
+ }
+ dbWrapper.setTransactionSuccessful();
+ } finally {
+ dbWrapper.endTransaction();
+ }
+ return conversationDeleted;
+ }
+
+ private static final String MAX_RECEIVED_TIMESTAMP =
+ "MAX(" + MessageColumns.RECEIVED_TIMESTAMP + ")";
+ /**
+ * Get the max received timestamp of a conversation's messages
+ */
+ private static long getConversationMaxTimestamp(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ final Cursor cursor = dbWrapper.query(
+ DatabaseHelper.MESSAGES_TABLE,
+ new String[]{ MAX_RECEIVED_TIMESTAMP },
+ MessageColumns.CONVERSATION_ID + "=?",
+ new String[]{ conversationId },
+ null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return 0;
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateConversationMetadataInTransaction(final DatabaseWrapper dbWrapper,
+ final String conversationId, final String messageId, final long latestTimestamp,
+ final boolean keepArchived, final String smsServiceCenter,
+ final boolean shouldAutoSwitchSelfId) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+
+ final ContentValues values = new ContentValues();
+ values.put(ConversationColumns.LATEST_MESSAGE_ID, messageId);
+ values.put(ConversationColumns.SORT_TIMESTAMP, latestTimestamp);
+ if (!TextUtils.isEmpty(smsServiceCenter)) {
+ values.put(ConversationColumns.SMS_SERVICE_CENTER, smsServiceCenter);
+ }
+
+ // When the conversation gets updated with new messages, unarchive the conversation unless
+ // the sender is blocked, or we have been told to keep it archived.
+ if (!keepArchived) {
+ values.put(ConversationColumns.ARCHIVE_STATUS, 0);
+ }
+
+ final MessageData message = readMessage(dbWrapper, messageId);
+ addSnippetTextAndPreviewToContentValues(message, false /* showDraft */, values);
+
+ if (shouldAutoSwitchSelfId) {
+ addSelfIdAutoSwitchInfoToContentValues(dbWrapper, message, conversationId, values);
+ }
+
+ // Conversation always exists as this method is called from ActionService only after
+ // reading and if necessary creating the conversation.
+ updateConversationRow(dbWrapper, conversationId, values);
+
+ if (shouldAutoSwitchSelfId && OsUtil.isAtLeastL_MR1()) {
+ // Normally, the draft message compose UI trusts its UI state for providing up-to-date
+ // conversation self id. Therefore, notify UI through local broadcast receiver about
+ // this external change so the change can be properly reflected.
+ UIIntents.get().broadcastConversationSelfIdChange(dbWrapper.getContext(),
+ conversationId, getConversationSelfId(dbWrapper, conversationId));
+ }
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateConversationMetadataInTransaction(final DatabaseWrapper db,
+ final String conversationId, final String messageId, final long latestTimestamp,
+ final boolean keepArchived, final boolean shouldAutoSwitchSelfId) {
+ Assert.isNotMainThread();
+ updateConversationMetadataInTransaction(
+ db, conversationId, messageId, latestTimestamp, keepArchived, null,
+ shouldAutoSwitchSelfId);
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateConversationArchiveStatusInTransaction(final DatabaseWrapper dbWrapper,
+ final String conversationId, final boolean isArchived) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ final ContentValues values = new ContentValues();
+ values.put(ConversationColumns.ARCHIVE_STATUS, isArchived ? 1 : 0);
+ updateConversationRowIfExists(dbWrapper, conversationId, values);
+ }
+
+ static void addSnippetTextAndPreviewToContentValues(final MessageData message,
+ final boolean showDraft, final ContentValues values) {
+ values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
+ values.put(ConversationColumns.SNIPPET_TEXT, message.getMessageText());
+ values.put(ConversationColumns.SUBJECT_TEXT, message.getMmsSubject());
+
+ String type = null;
+ String uriString = null;
+ for (final MessagePartData part : message.getParts()) {
+ if (part.isAttachment() &&
+ ContentType.isConversationListPreviewableType(part.getContentType())) {
+ uriString = part.getContentUri().toString();
+ type = part.getContentType();
+ break;
+ }
+ }
+ values.put(ConversationColumns.PREVIEW_CONTENT_TYPE, type);
+ values.put(ConversationColumns.PREVIEW_URI, uriString);
+ }
+
+ /**
+ * Adds self-id auto switch info for a conversation if the last message has a different
+ * subscription than the conversation's.
+ * @return true if self id will need to be changed, false otherwise.
+ */
+ static boolean addSelfIdAutoSwitchInfoToContentValues(final DatabaseWrapper dbWrapper,
+ final MessageData message, final String conversationId, final ContentValues values) {
+ // Only auto switch conversation self for incoming messages.
+ if (!OsUtil.isAtLeastL_MR1() || !message.getIsIncoming()) {
+ return false;
+ }
+
+ final String conversationSelfId = getConversationSelfId(dbWrapper, conversationId);
+ final String messageSelfId = message.getSelfId();
+
+ if (conversationSelfId == null || messageSelfId == null) {
+ return false;
+ }
+
+ // Get the sub IDs in effect for both the message and the conversation and compare them:
+ // 1. If message is unbound (using default sub id), then the message was sent with
+ // pre-MSIM support. Don't auto-switch because we don't know the subscription for the
+ // message.
+ // 2. If message is bound,
+ // i. If conversation is unbound, use the system default sub id as its effective sub.
+ // ii. If conversation is bound, use its subscription directly.
+ // Compare the message sub id with the conversation's effective sub id. If they are
+ // different, auto-switch the conversation to the message's sub.
+ final ParticipantData conversationSelf = getExistingParticipant(dbWrapper,
+ conversationSelfId);
+ final ParticipantData messageSelf = getExistingParticipant(dbWrapper, messageSelfId);
+ if (!messageSelf.isActiveSubscription()) {
+ // Don't switch if the message subscription is no longer active.
+ return false;
+ }
+ final int messageSubId = messageSelf.getSubId();
+ if (messageSubId == ParticipantData.DEFAULT_SELF_SUB_ID) {
+ return false;
+ }
+
+ final int conversationEffectiveSubId =
+ PhoneUtils.getDefault().getEffectiveSubId(conversationSelf.getSubId());
+
+ if (conversationEffectiveSubId != messageSubId) {
+ return addConversationSelfIdToContentValues(dbWrapper, messageSelf.getId(), values);
+ }
+ return false;
+ }
+
+ /**
+ * Adds conversation self id updates to ContentValues given. This performs check on the selfId
+ * to ensure it's valid and active.
+ * @return true if self id will need to be changed, false otherwise.
+ */
+ static boolean addConversationSelfIdToContentValues(final DatabaseWrapper dbWrapper,
+ final String selfId, final ContentValues values) {
+ // Make sure the selfId passed in is valid and active.
+ final String selection = ParticipantColumns._ID + "=? AND " +
+ ParticipantColumns.SIM_SLOT_ID + "<>?";
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ new String[] { ParticipantColumns._ID }, selection,
+ new String[] { selfId, String.valueOf(ParticipantData.INVALID_SLOT_ID) },
+ null, null, null);
+
+ if (cursor != null && cursor.getCount() > 0) {
+ values.put(ConversationColumns.CURRENT_SELF_ID, selfId);
+ return true;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return false;
+ }
+
+ private static void updateConversationDraftSnippetAndPreviewInTransaction(
+ final DatabaseWrapper dbWrapper, final String conversationId,
+ final MessageData draftMessage) {
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+
+ long sortTimestamp = 0L;
+ Cursor cursor = null;
+ try {
+ // Check to find the latest message in the conversation
+ cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
+ REFRESH_CONVERSATION_MESSAGE_PROJECTION,
+ MessageColumns.CONVERSATION_ID + "=?",
+ new String[]{conversationId}, null, null,
+ MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
+
+ if (cursor.moveToFirst()) {
+ sortTimestamp = cursor.getLong(1);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+
+ final ContentValues values = new ContentValues();
+ if (draftMessage == null || !draftMessage.hasContent()) {
+ values.put(ConversationColumns.SHOW_DRAFT, 0);
+ values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, "");
+ values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, "");
+ values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, "");
+ values.put(ConversationColumns.DRAFT_PREVIEW_URI, "");
+ } else {
+ sortTimestamp = Math.max(sortTimestamp, draftMessage.getReceivedTimeStamp());
+ values.put(ConversationColumns.SHOW_DRAFT, 1);
+ values.put(ConversationColumns.DRAFT_SNIPPET_TEXT, draftMessage.getMessageText());
+ values.put(ConversationColumns.DRAFT_SUBJECT_TEXT, draftMessage.getMmsSubject());
+ String type = null;
+ String uriString = null;
+ for (final MessagePartData part : draftMessage.getParts()) {
+ if (part.isAttachment() &&
+ ContentType.isConversationListPreviewableType(part.getContentType())) {
+ uriString = part.getContentUri().toString();
+ type = part.getContentType();
+ break;
+ }
+ }
+ values.put(ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE, type);
+ values.put(ConversationColumns.DRAFT_PREVIEW_URI, uriString);
+ }
+ values.put(ConversationColumns.SORT_TIMESTAMP, sortTimestamp);
+ // Called in transaction after reading conversation row
+ updateConversationRow(dbWrapper, conversationId, values);
+ }
+
+ @DoesNotRunOnMainThread
+ public static boolean updateConversationRowIfExists(final DatabaseWrapper dbWrapper,
+ final String conversationId, final ContentValues values) {
+ Assert.isNotMainThread();
+ return updateRowIfExists(dbWrapper, DatabaseHelper.CONVERSATIONS_TABLE,
+ ConversationColumns._ID, conversationId, values);
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateConversationRow(final DatabaseWrapper dbWrapper,
+ final String conversationId, final ContentValues values) {
+ Assert.isNotMainThread();
+ final boolean exists = updateConversationRowIfExists(dbWrapper, conversationId, values);
+ Assert.isTrue(exists);
+ }
+
+ @DoesNotRunOnMainThread
+ public static boolean updateMessageRowIfExists(final DatabaseWrapper dbWrapper,
+ final String messageId, final ContentValues values) {
+ Assert.isNotMainThread();
+ return updateRowIfExists(dbWrapper, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID,
+ messageId, values);
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateMessageRow(final DatabaseWrapper dbWrapper,
+ final String messageId, final ContentValues values) {
+ Assert.isNotMainThread();
+ final boolean exists = updateMessageRowIfExists(dbWrapper, messageId, values);
+ Assert.isTrue(exists);
+ }
+
+ @DoesNotRunOnMainThread
+ public static boolean updatePartRowIfExists(final DatabaseWrapper dbWrapper,
+ final String partId, final ContentValues values) {
+ Assert.isNotMainThread();
+ return updateRowIfExists(dbWrapper, DatabaseHelper.PARTS_TABLE, PartColumns._ID,
+ partId, values);
+ }
+
+ /**
+ * Returns the default conversation name based on its participants.
+ */
+ private static String getDefaultConversationName(final List<ParticipantData> participants) {
+ return ConversationListItemData.generateConversationName(participants);
+ }
+
+ /**
+ * Updates a given conversation's name based on its participants.
+ */
+ @DoesNotRunOnMainThread
+ public static void updateConversationNameAndAvatarInTransaction(
+ final DatabaseWrapper dbWrapper, final String conversationId) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+
+ final ArrayList<ParticipantData> participants =
+ getParticipantsForConversation(dbWrapper, conversationId);
+ updateConversationNameAndAvatarInTransaction(dbWrapper, conversationId, participants);
+ }
+
+ /**
+ * Updates a given conversation's name based on its participants.
+ */
+ private static void updateConversationNameAndAvatarInTransaction(
+ final DatabaseWrapper dbWrapper, final String conversationId,
+ final List<ParticipantData> participants) {
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+
+ final ContentValues values = new ContentValues();
+ values.put(ConversationColumns.NAME,
+ getDefaultConversationName(participants));
+
+ fillParticipantData(values, participants);
+
+ // Used by background thread when refreshing conversation so conversation could be deleted.
+ updateConversationRowIfExists(dbWrapper, conversationId, values);
+
+ WidgetConversationProvider.notifyConversationRenamed(Factory.get().getApplicationContext(),
+ conversationId);
+ }
+
+ /**
+ * Updates a given conversation's self id.
+ */
+ @DoesNotRunOnMainThread
+ public static void updateConversationSelfIdInTransaction(
+ final DatabaseWrapper dbWrapper, final String conversationId, final String selfId) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ final ContentValues values = new ContentValues();
+ if (addConversationSelfIdToContentValues(dbWrapper, selfId, values)) {
+ updateConversationRowIfExists(dbWrapper, conversationId, values);
+ }
+ }
+
+ @DoesNotRunOnMainThread
+ public static String getConversationSelfId(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ Assert.isNotMainThread();
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns.CURRENT_SELF_ID },
+ ConversationColumns._ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ return cursor.getString(0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Frees up memory associated with phone number to participant id matching.
+ */
+ @DoesNotRunOnMainThread
+ public static void clearParticipantIdCache() {
+ Assert.isNotMainThread();
+ synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
+ sNormalizedPhoneNumberToParticipantIdCache.clear();
+ }
+ }
+
+ @DoesNotRunOnMainThread
+ public static ArrayList<String> getRecipientsForConversation(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ Assert.isNotMainThread();
+ final ArrayList<ParticipantData> participants =
+ getParticipantsForConversation(dbWrapper, conversationId);
+
+ final ArrayList<String> recipients = new ArrayList<String>();
+ for (final ParticipantData participant : participants) {
+ recipients.add(participant.getSendDestination());
+ }
+
+ return recipients;
+ }
+
+ @DoesNotRunOnMainThread
+ public static String getSmsServiceCenterForConversation(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ Assert.isNotMainThread();
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns.SMS_SERVICE_CENTER },
+ ConversationColumns._ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ return cursor.getString(0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ @DoesNotRunOnMainThread
+ public static ParticipantData getExistingParticipant(final DatabaseWrapper dbWrapper,
+ final String participantId) {
+ Assert.isNotMainThread();
+ ParticipantData participant = null;
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantData.ParticipantsQuery.PROJECTION,
+ ParticipantColumns._ID + " =?",
+ new String[] { participantId }, null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ participant = ParticipantData.getFromCursor(cursor);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return participant;
+ }
+
+ static int getSelfSubscriptionId(final DatabaseWrapper dbWrapper,
+ final String selfParticipantId) {
+ final ParticipantData selfParticipant = BugleDatabaseOperations.getExistingParticipant(
+ dbWrapper, selfParticipantId);
+ if (selfParticipant != null) {
+ Assert.isTrue(selfParticipant.isSelf());
+ return selfParticipant.getSubId();
+ }
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ @VisibleForTesting
+ @DoesNotRunOnMainThread
+ public static ArrayList<ParticipantData> getParticipantsForConversation(
+ final DatabaseWrapper dbWrapper, final String conversationId) {
+ Assert.isNotMainThread();
+ final ArrayList<ParticipantData> participants =
+ new ArrayList<ParticipantData>();
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantData.ParticipantsQuery.PROJECTION,
+ ParticipantColumns._ID + " IN ( " + "SELECT "
+ + ConversationParticipantsColumns.PARTICIPANT_ID + " AS "
+ + ParticipantColumns._ID
+ + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE
+ + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID + " =? )",
+ new String[] { conversationId }, null, null, null);
+
+ while (cursor.moveToNext()) {
+ participants.add(ParticipantData.getFromCursor(cursor));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return participants;
+ }
+
+ @DoesNotRunOnMainThread
+ public static MessageData readMessage(final DatabaseWrapper dbWrapper, final String messageId) {
+ Assert.isNotMainThread();
+ final MessageData message = readMessageData(dbWrapper, messageId);
+ if (message != null) {
+ readMessagePartsData(dbWrapper, message, false);
+ }
+ return message;
+ }
+
+ @VisibleForTesting
+ static MessagePartData readMessagePartData(final DatabaseWrapper dbWrapper,
+ final String partId) {
+ MessagePartData messagePartData = null;
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
+ MessagePartData.getProjection(), PartColumns._ID + "=?",
+ new String[] { partId }, null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ messagePartData = MessagePartData.createFromCursor(cursor);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return messagePartData;
+ }
+
+ @DoesNotRunOnMainThread
+ public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
+ final Uri smsMessageUri) {
+ Assert.isNotMainThread();
+ MessageData message = null;
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(), MessageColumns.SMS_MESSAGE_URI + "=?",
+ new String[] { smsMessageUri.toString() }, null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ message = new MessageData();
+ message.bind(cursor);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return message;
+ }
+
+ @DoesNotRunOnMainThread
+ public static MessageData readMessageData(final DatabaseWrapper dbWrapper,
+ final String messageId) {
+ Assert.isNotMainThread();
+ MessageData message = null;
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(), MessageColumns._ID + "=?",
+ new String[] { messageId }, null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ message = new MessageData();
+ message.bind(cursor);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return message;
+ }
+
+ /**
+ * Read all the parts for a message
+ * @param dbWrapper database
+ * @param message read parts for this message
+ * @param checkAttachmentFilesExist check each attachment file and only include if file exists
+ */
+ private static void readMessagePartsData(final DatabaseWrapper dbWrapper,
+ final MessageData message, final boolean checkAttachmentFilesExist) {
+ final ContentResolver contentResolver =
+ Factory.get().getApplicationContext().getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.PARTS_TABLE,
+ MessagePartData.getProjection(), PartColumns.MESSAGE_ID + "=?",
+ new String[] { message.getMessageId() }, null, null, null);
+ while (cursor.moveToNext()) {
+ final MessagePartData messagePartData = MessagePartData.createFromCursor(cursor);
+ if (checkAttachmentFilesExist && messagePartData.isAttachment() &&
+ !UriUtil.isBugleAppResource(messagePartData.getContentUri())) {
+ try {
+ // Test that the file exists before adding the attachment to the draft
+ final ParcelFileDescriptor fileDescriptor =
+ contentResolver.openFileDescriptor(
+ messagePartData.getContentUri(), "r");
+ if (fileDescriptor != null) {
+ fileDescriptor.close();
+ message.addPart(messagePartData);
+ }
+ } catch (final IOException e) {
+ // The attachment's temp storage no longer exists, just ignore the file
+ } catch (final SecurityException e) {
+ // Likely thrown by openFileDescriptor due to an expired access grant.
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "uri: " + messagePartData.getContentUri());
+ }
+ }
+ } else {
+ message.addPart(messagePartData);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * Write a message part to our local database
+ *
+ * @param dbWrapper The database
+ * @param messagePart The message part to insert
+ * @return The row id of the newly inserted part
+ */
+ static String insertNewMessagePartInTransaction(final DatabaseWrapper dbWrapper,
+ final MessagePartData messagePart, final String conversationId) {
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ Assert.isTrue(!TextUtils.isEmpty(messagePart.getMessageId()));
+
+ // Insert a new part row
+ final SQLiteStatement insert = messagePart.getInsertStatement(dbWrapper, conversationId);
+ final long rowNumber = insert.executeInsert();
+
+ Assert.inRange(rowNumber, 0, Long.MAX_VALUE);
+ final String partId = Long.toString(rowNumber);
+
+ // Update the part id
+ messagePart.updatePartId(partId);
+
+ return partId;
+ }
+
+ /**
+ * Insert a message and its parts into the table
+ */
+ @DoesNotRunOnMainThread
+ public static void insertNewMessageInTransaction(final DatabaseWrapper dbWrapper,
+ final MessageData message) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+
+ // Insert message row
+ final SQLiteStatement insert = message.getInsertStatement(dbWrapper);
+ final long rowNumber = insert.executeInsert();
+
+ Assert.inRange(rowNumber, 0, Long.MAX_VALUE);
+ final String messageId = Long.toString(rowNumber);
+ message.updateMessageId(messageId);
+ // Insert new parts
+ for (final MessagePartData messagePart : message.getParts()) {
+ messagePart.updateMessageId(messageId);
+ insertNewMessagePartInTransaction(dbWrapper, messagePart, message.getConversationId());
+ }
+ }
+
+ /**
+ * Update a message and add its parts into the table
+ */
+ @DoesNotRunOnMainThread
+ public static void updateMessageInTransaction(final DatabaseWrapper dbWrapper,
+ final MessageData message) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ final String messageId = message.getMessageId();
+ // Check message still exists (sms sync or delete might have purged it)
+ final MessageData current = BugleDatabaseOperations.readMessage(dbWrapper, messageId);
+ if (current != null) {
+ // Delete existing message parts)
+ deletePartsForMessage(dbWrapper, message.getMessageId());
+ // Insert new parts
+ for (final MessagePartData messagePart : message.getParts()) {
+ messagePart.updatePartId(null);
+ messagePart.updateMessageId(message.getMessageId());
+ insertNewMessagePartInTransaction(dbWrapper, messagePart,
+ message.getConversationId());
+ }
+ // Update message row
+ final ContentValues values = new ContentValues();
+ message.populate(values);
+ updateMessageRowIfExists(dbWrapper, message.getMessageId(), values);
+ }
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateMessageAndPartsInTransaction(final DatabaseWrapper dbWrapper,
+ final MessageData message, final List<MessagePartData> partsToUpdate) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ final ContentValues values = new ContentValues();
+ for (final MessagePartData messagePart : partsToUpdate) {
+ values.clear();
+ messagePart.populate(values);
+ updatePartRowIfExists(dbWrapper, messagePart.getPartId(), values);
+ }
+ values.clear();
+ message.populate(values);
+ updateMessageRowIfExists(dbWrapper, message.getMessageId(), values);
+ }
+
+ /**
+ * Delete all parts for a message
+ */
+ static void deletePartsForMessage(final DatabaseWrapper dbWrapper,
+ final String messageId) {
+ final int cnt = dbWrapper.delete(DatabaseHelper.PARTS_TABLE,
+ PartColumns.MESSAGE_ID + " =?",
+ new String[] { messageId });
+ Assert.inRange(cnt, 0, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Delete one message and update the conversation (if necessary).
+ *
+ * @return number of rows deleted (should be 1 or 0).
+ */
+ @DoesNotRunOnMainThread
+ public static int deleteMessage(final DatabaseWrapper dbWrapper, final String messageId) {
+ Assert.isNotMainThread();
+ dbWrapper.beginTransaction();
+ try {
+ // Read message to find out which conversation it is in
+ final MessageData message = BugleDatabaseOperations.readMessage(dbWrapper, messageId);
+
+ int count = 0;
+ if (message != null) {
+ final String conversationId = message.getConversationId();
+ // Delete message
+ count = dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns._ID + "=?", new String[] { messageId });
+
+ if (!deleteConversationIfEmptyInTransaction(dbWrapper, conversationId)) {
+ // TODO: Should we leave the conversation sort timestamp alone?
+ refreshConversationMetadataInTransaction(dbWrapper, conversationId,
+ false/* shouldAutoSwitchSelfId */, false/*archived*/);
+ }
+ }
+ dbWrapper.setTransactionSuccessful();
+ return count;
+ } finally {
+ dbWrapper.endTransaction();
+ }
+ }
+
+ /**
+ * Deletes the conversation if there are zero non-draft messages left.
+ * <p>
+ * This is necessary because the telephony database has a trigger that deletes threads after
+ * their last message is deleted. We need to ensure that if a thread goes away, we also delete
+ * the conversation in Bugle. We don't store draft messages in telephony, so we ignore those
+ * when querying for the # of messages in the conversation.
+ *
+ * @return true if the conversation was deleted
+ */
+ @DoesNotRunOnMainThread
+ public static boolean deleteConversationIfEmptyInTransaction(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ Cursor cursor = null;
+ try {
+ // TODO: The refreshConversationMetadataInTransaction method below uses this
+ // same query; maybe they should share this logic?
+
+ // Check to see if there are any (non-draft) messages in the conversation
+ cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
+ REFRESH_CONVERSATION_MESSAGE_PROJECTION,
+ MessageColumns.CONVERSATION_ID + "=? AND " +
+ MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
+ new String[] { conversationId }, null, null,
+ MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
+ if (cursor.getCount() == 0) {
+ dbWrapper.delete(DatabaseHelper.CONVERSATIONS_TABLE,
+ ConversationColumns._ID + "=?", new String[] { conversationId });
+ LogUtil.i(TAG,
+ "BugleDatabaseOperations: Deleted empty conversation " + conversationId);
+ return true;
+ } else {
+ return false;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private static final String[] REFRESH_CONVERSATION_MESSAGE_PROJECTION = new String[] {
+ MessageColumns._ID,
+ MessageColumns.RECEIVED_TIMESTAMP,
+ MessageColumns.SENDER_PARTICIPANT_ID
+ };
+
+ /**
+ * Update conversation snippet, timestamp and optionally self id to match latest message in
+ * conversation.
+ */
+ @DoesNotRunOnMainThread
+ public static void refreshConversationMetadataInTransaction(final DatabaseWrapper dbWrapper,
+ final String conversationId, final boolean shouldAutoSwitchSelfId,
+ boolean keepArchived) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ Cursor cursor = null;
+ try {
+ // Check to see if there are any (non-draft) messages in the conversation
+ cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
+ REFRESH_CONVERSATION_MESSAGE_PROJECTION,
+ MessageColumns.CONVERSATION_ID + "=? AND " +
+ MessageColumns.STATUS + "!=" + MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
+ new String[] { conversationId }, null, null,
+ MessageColumns.RECEIVED_TIMESTAMP + " DESC", "1" /* limit */);
+
+ if (cursor.moveToFirst()) {
+ // Refresh latest message in conversation
+ final String latestMessageId = cursor.getString(0);
+ final long latestMessageTimestamp = cursor.getLong(1);
+ final String senderParticipantId = cursor.getString(2);
+ final boolean senderBlocked = isBlockedParticipant(dbWrapper, senderParticipantId);
+ updateConversationMetadataInTransaction(dbWrapper, conversationId,
+ latestMessageId, latestMessageTimestamp, senderBlocked || keepArchived,
+ shouldAutoSwitchSelfId);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * When moving/removing an existing message update conversation metadata if necessary
+ * @param dbWrapper db wrapper
+ * @param conversationId conversation to modify
+ * @param messageId message that is leaving the conversation
+ * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a
+ * result of this call when we see a new latest message?
+ * @param keepArchived should we keep the conversation archived despite refresh
+ */
+ @DoesNotRunOnMainThread
+ public static void maybeRefreshConversationMetadataInTransaction(
+ final DatabaseWrapper dbWrapper, final String conversationId, final String messageId,
+ final boolean shouldAutoSwitchSelfId, final boolean keepArchived) {
+ Assert.isNotMainThread();
+ boolean refresh = true;
+ if (!TextUtils.isEmpty(messageId)) {
+ refresh = false;
+ // Look for an existing conversation in the db with this conversation id
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns.LATEST_MESSAGE_ID },
+ ConversationColumns._ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ refresh = TextUtils.equals(cursor.getString(0), messageId);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ if (refresh) {
+ // TODO: I think it is okay to delete the conversation if it is empty...
+ refreshConversationMetadataInTransaction(dbWrapper, conversationId,
+ shouldAutoSwitchSelfId, keepArchived);
+ }
+ }
+
+
+
+ // SQL statement to query latest message if for particular conversation
+ private static final String QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL = "SELECT "
+ + ConversationColumns.LATEST_MESSAGE_ID + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
+ + " WHERE " + ConversationColumns._ID + "=? LIMIT 1";
+
+ /**
+ * Note this is not thread safe so callers need to make sure they own the wrapper + statements
+ * while they call this and use the returned value.
+ */
+ @DoesNotRunOnMainThread
+ public static SQLiteStatement getQueryConversationsLatestMessageStatement(
+ final DatabaseWrapper db, final String conversationId) {
+ Assert.isNotMainThread();
+ final SQLiteStatement query = db.getStatementInTransaction(
+ DatabaseWrapper.INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE,
+ QUERY_CONVERSATIONS_LATEST_MESSAGE_SQL);
+ query.clearBindings();
+ query.bindString(1, conversationId);
+ return query;
+ }
+
+ // SQL statement to query latest message if for particular conversation
+ private static final String QUERY_MESSAGES_LATEST_MESSAGE_SQL = "SELECT "
+ + MessageColumns._ID + " FROM " + DatabaseHelper.MESSAGES_TABLE
+ + " WHERE " + MessageColumns.CONVERSATION_ID + "=? ORDER BY "
+ + MessageColumns.RECEIVED_TIMESTAMP + " DESC LIMIT 1";
+
+ /**
+ * Note this is not thread safe so callers need to make sure they own the wrapper + statements
+ * while they call this and use the returned value.
+ */
+ @DoesNotRunOnMainThread
+ public static SQLiteStatement getQueryMessagesLatestMessageStatement(
+ final DatabaseWrapper db, final String conversationId) {
+ Assert.isNotMainThread();
+ final SQLiteStatement query = db.getStatementInTransaction(
+ DatabaseWrapper.INDEX_QUERY_MESSAGES_LATEST_MESSAGE,
+ QUERY_MESSAGES_LATEST_MESSAGE_SQL);
+ query.clearBindings();
+ query.bindString(1, conversationId);
+ return query;
+ }
+
+ /**
+ * Update conversation metadata if necessary
+ * @param dbWrapper db wrapper
+ * @param conversationId conversation to modify
+ * @param shouldAutoSwitchSelfId should we try to auto-switch the conversation's self-id as a
+ * result of this call when we see a new latest message?
+ * @param keepArchived if the conversation should be kept archived
+ */
+ @DoesNotRunOnMainThread
+ public static void maybeRefreshConversationMetadataInTransaction(
+ final DatabaseWrapper dbWrapper, final String conversationId,
+ final boolean shouldAutoSwitchSelfId, boolean keepArchived) {
+ Assert.isNotMainThread();
+ String currentLatestMessageId = null;
+ String latestMessageId = null;
+ try {
+ final SQLiteStatement currentLatestMessageIdSql =
+ getQueryConversationsLatestMessageStatement(dbWrapper, conversationId);
+ currentLatestMessageId = currentLatestMessageIdSql.simpleQueryForString();
+
+ final SQLiteStatement latestMessageIdSql =
+ getQueryMessagesLatestMessageStatement(dbWrapper, conversationId);
+ latestMessageId = latestMessageIdSql.simpleQueryForString();
+ } catch (final SQLiteDoneException e) {
+ LogUtil.e(TAG, "BugleDatabaseOperations: Query for latest message failed", e);
+ }
+
+ if (TextUtils.isEmpty(currentLatestMessageId) ||
+ !TextUtils.equals(currentLatestMessageId, latestMessageId)) {
+ refreshConversationMetadataInTransaction(dbWrapper, conversationId,
+ shouldAutoSwitchSelfId, keepArchived);
+ }
+ }
+
+ static boolean getConversationExists(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ // Look for an existing conversation in the db with this conversation id
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { /* No projection */},
+ ConversationColumns._ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ return cursor.getCount() == 1;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ /** Preserve parts in message but clear the stored draft */
+ public static final int UPDATE_MODE_CLEAR_DRAFT = 1;
+ /** Add the message as a draft */
+ public static final int UPDATE_MODE_ADD_DRAFT = 2;
+
+ /**
+ * Update draft message for specified conversation
+ * @param dbWrapper local database (wrapped)
+ * @param conversationId conversation to update
+ * @param message Optional message to preserve attachments for (either as draft or for
+ * sending)
+ * @param updateMode either {@link #UPDATE_MODE_CLEAR_DRAFT} or
+ * {@link #UPDATE_MODE_ADD_DRAFT}
+ * @return message id of newly written draft (else null)
+ */
+ @DoesNotRunOnMainThread
+ public static String updateDraftMessageData(final DatabaseWrapper dbWrapper,
+ final String conversationId, @Nullable final MessageData message,
+ final int updateMode) {
+ Assert.isNotMainThread();
+ Assert.notNull(conversationId);
+ Assert.inRange(updateMode, UPDATE_MODE_CLEAR_DRAFT, UPDATE_MODE_ADD_DRAFT);
+ String messageId = null;
+ Cursor cursor = null;
+ dbWrapper.beginTransaction();
+ try {
+ // Find all draft parts for the current conversation
+ final SimpleArrayMap<Uri, MessagePartData> currentDraftParts = new SimpleArrayMap<>();
+ cursor = dbWrapper.query(DatabaseHelper.DRAFT_PARTS_VIEW,
+ MessagePartData.getProjection(),
+ MessageColumns.CONVERSATION_ID + " =?",
+ new String[] { conversationId }, null, null, null);
+ while (cursor.moveToNext()) {
+ final MessagePartData part = MessagePartData.createFromCursor(cursor);
+ if (part.isAttachment()) {
+ currentDraftParts.put(part.getContentUri(), part);
+ }
+ }
+ // Optionally, preserve attachments for "message"
+ final boolean conversationExists = getConversationExists(dbWrapper, conversationId);
+ if (message != null && conversationExists) {
+ for (final MessagePartData part : message.getParts()) {
+ if (part.isAttachment()) {
+ currentDraftParts.remove(part.getContentUri());
+ }
+ }
+ }
+
+ // Delete orphan content
+ for (int index = 0; index < currentDraftParts.size(); index++) {
+ final MessagePartData part = currentDraftParts.valueAt(index);
+ part.destroySync();
+ }
+
+ // Delete existing draft (cascade deletes parts)
+ dbWrapper.delete(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
+ new String[] {
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
+ conversationId
+ });
+
+ // Write new draft
+ if (updateMode == UPDATE_MODE_ADD_DRAFT && message != null
+ && message.hasContent() && conversationExists) {
+ Assert.equals(MessageData.BUGLE_STATUS_OUTGOING_DRAFT,
+ message.getStatus());
+
+ // Now add draft to message table
+ insertNewMessageInTransaction(dbWrapper, message);
+ messageId = message.getMessageId();
+ }
+
+ if (conversationExists) {
+ updateConversationDraftSnippetAndPreviewInTransaction(
+ dbWrapper, conversationId, message);
+
+ if (message != null && message.getSelfId() != null) {
+ updateConversationSelfIdInTransaction(dbWrapper, conversationId,
+ message.getSelfId());
+ }
+ }
+
+ dbWrapper.setTransactionSuccessful();
+ } finally {
+ dbWrapper.endTransaction();
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG,
+ "Updated draft message " + messageId + " for conversation " + conversationId);
+ }
+ return messageId;
+ }
+
+ /**
+ * Read the first draft message associated with this conversation.
+ * If none present create an empty (sms) draft message.
+ */
+ @DoesNotRunOnMainThread
+ public static MessageData readDraftMessageData(final DatabaseWrapper dbWrapper,
+ final String conversationId, final String conversationSelfId) {
+ Assert.isNotMainThread();
+ MessageData message = null;
+ Cursor cursor = null;
+ dbWrapper.beginTransaction();
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(),
+ MessageColumns.STATUS + "=? AND " + MessageColumns.CONVERSATION_ID + "=?",
+ new String[] {
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_DRAFT),
+ conversationId
+ }, null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ message = new MessageData();
+ message.bindDraft(cursor, conversationSelfId);
+ readMessagePartsData(dbWrapper, message, true);
+ // Disconnect draft parts from DB
+ for (final MessagePartData part : message.getParts()) {
+ part.updatePartId(null);
+ part.updateMessageId(null);
+ }
+ message.updateMessageId(null);
+ }
+ dbWrapper.setTransactionSuccessful();
+ } finally {
+ dbWrapper.endTransaction();
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return message;
+ }
+
+ // Internal
+ private static void addParticipantToConversation(final DatabaseWrapper dbWrapper,
+ final ParticipantData participant, final String conversationId) {
+ final String participantId = getOrCreateParticipantInTransaction(dbWrapper, participant);
+ Assert.notNull(participantId);
+
+ // Add the participant to the conversation participants table
+ final ContentValues values = new ContentValues();
+ values.put(ConversationParticipantsColumns.CONVERSATION_ID, conversationId);
+ values.put(ConversationParticipantsColumns.PARTICIPANT_ID, participantId);
+ dbWrapper.insert(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE, null, values);
+ }
+
+ /**
+ * Get string used as canonical recipient for participant cache for sub id
+ */
+ private static String getCanonicalRecipientFromSubId(final int subId) {
+ return "SELF(" + subId + ")";
+ }
+
+ /**
+ * Maps from a sub id or phone number to a participant id if there is one.
+ *
+ * @return If the participant is available in our cache, or the DB, this returns the
+ * participant id for the given subid/phone number. Otherwise it returns null.
+ */
+ @VisibleForTesting
+ private static String getParticipantId(final DatabaseWrapper dbWrapper,
+ final int subId, final String canonicalRecipient) {
+ // First check our memory cache for the participant Id
+ String participantId;
+ synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
+ participantId = sNormalizedPhoneNumberToParticipantIdCache.get(canonicalRecipient);
+ }
+
+ if (participantId != null) {
+ return participantId;
+ }
+
+ // This code will only be executed for incremental additions.
+ Cursor cursor = null;
+ try {
+ if (subId != ParticipantData.OTHER_THAN_SELF_SUB_ID) {
+ // Now look for an existing participant in the db with this sub id.
+ cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ new String[] {ParticipantColumns._ID},
+ ParticipantColumns.SUB_ID + "=?",
+ new String[] { Integer.toString(subId) }, null, null, null);
+ } else {
+ // Look for existing participant with this normalized phone number and no subId.
+ cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ new String[] {ParticipantColumns._ID},
+ ParticipantColumns.NORMALIZED_DESTINATION + "=? AND "
+ + ParticipantColumns.SUB_ID + "=?",
+ new String[] {canonicalRecipient, Integer.toString(subId)},
+ null, null, null);
+ }
+
+ if (cursor.moveToFirst()) {
+ // TODO Is this assert correct for multi-sim where a new sim was put in?
+ Assert.isTrue(cursor.getCount() == 1);
+
+ // We found an existing participant in the database
+ participantId = cursor.getString(0);
+
+ synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
+ // Add it to the cache for next time
+ sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient,
+ participantId);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return participantId;
+ }
+
+ @DoesNotRunOnMainThread
+ public static ParticipantData getOrCreateSelf(final DatabaseWrapper dbWrapper,
+ final int subId) {
+ Assert.isNotMainThread();
+ ParticipantData participant = null;
+ dbWrapper.beginTransaction();
+ try {
+ final ParticipantData shell = ParticipantData.getSelfParticipant(subId);
+ final String participantId = getOrCreateParticipantInTransaction(dbWrapper, shell);
+ participant = getExistingParticipant(dbWrapper, participantId);
+ dbWrapper.setTransactionSuccessful();
+ } finally {
+ dbWrapper.endTransaction();
+ }
+ return participant;
+ }
+
+ /**
+ * Lookup and if necessary create a new participant
+ * @param dbWrapper Database wrapper
+ * @param participant Participant to find/create
+ * @return participantId ParticipantId for existing or newly created participant
+ */
+ @DoesNotRunOnMainThread
+ public static String getOrCreateParticipantInTransaction(final DatabaseWrapper dbWrapper,
+ final ParticipantData participant) {
+ Assert.isNotMainThread();
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ int subId = ParticipantData.OTHER_THAN_SELF_SUB_ID;
+ String participantId = null;
+ String canonicalRecipient = null;
+ if (participant.isSelf()) {
+ subId = participant.getSubId();
+ canonicalRecipient = getCanonicalRecipientFromSubId(subId);
+ } else {
+ canonicalRecipient = participant.getNormalizedDestination();
+ }
+ Assert.notNull(canonicalRecipient);
+ participantId = getParticipantId(dbWrapper, subId, canonicalRecipient);
+
+ if (participantId != null) {
+ return participantId;
+ }
+
+ if (!participant.isContactIdResolved()) {
+ // Refresh participant's name and avatar with matching contact in CP2.
+ ParticipantRefresh.refreshParticipant(dbWrapper, participant);
+ }
+
+ // Insert the participant into the participants table
+ final ContentValues values = participant.toContentValues();
+ final long participantRow = dbWrapper.insert(DatabaseHelper.PARTICIPANTS_TABLE, null,
+ values);
+ participantId = Long.toString(participantRow);
+ Assert.notNull(canonicalRecipient);
+
+ synchronized (sNormalizedPhoneNumberToParticipantIdCache) {
+ // Now that we've inserted it, add it to our cache
+ sNormalizedPhoneNumberToParticipantIdCache.put(canonicalRecipient, participantId);
+ }
+
+ return participantId;
+ }
+
+ @DoesNotRunOnMainThread
+ public static void updateDestination(final DatabaseWrapper dbWrapper,
+ final String destination, final boolean blocked) {
+ Assert.isNotMainThread();
+ final ContentValues values = new ContentValues();
+ values.put(ParticipantColumns.BLOCKED, blocked ? 1 : 0);
+ dbWrapper.update(DatabaseHelper.PARTICIPANTS_TABLE, values,
+ ParticipantColumns.NORMALIZED_DESTINATION + "=? AND " +
+ ParticipantColumns.SUB_ID + "=?",
+ new String[] { destination, Integer.toString(
+ ParticipantData.OTHER_THAN_SELF_SUB_ID) });
+ }
+
+ @DoesNotRunOnMainThread
+ public static String getConversationFromOtherParticipantDestination(
+ final DatabaseWrapper db, final String otherDestination) {
+ Assert.isNotMainThread();
+ Cursor cursor = null;
+ try {
+ cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns._ID },
+ ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + "=?",
+ new String[] { otherDestination }, null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ return cursor.getString(0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Get a list of conversations that contain any of participants specified.
+ */
+ private static HashSet<String> getConversationsForParticipants(
+ final ArrayList<String> participantIds) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final HashSet<String> conversationIds = new HashSet<String>();
+
+ final String selection = ConversationParticipantsColumns.PARTICIPANT_ID + "=?";
+ for (final String participantId : participantIds) {
+ final String[] selectionArgs = new String[] { participantId };
+ final Cursor cursor = db.query(DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE,
+ ConversationParticipantsQuery.PROJECTION,
+ selection, selectionArgs, null, null, null);
+
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ final String conversationId = cursor.getString(
+ ConversationParticipantsQuery.INDEX_CONVERSATION_ID);
+ conversationIds.add(conversationId);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ return conversationIds;
+ }
+
+ /**
+ * Refresh conversation names/avatars based on a list of participants that are changed.
+ */
+ @DoesNotRunOnMainThread
+ public static void refreshConversationsForParticipants(final ArrayList<String> participants) {
+ Assert.isNotMainThread();
+ final HashSet<String> conversationIds = getConversationsForParticipants(participants);
+ if (conversationIds.size() > 0) {
+ for (final String conversationId : conversationIds) {
+ refreshConversation(conversationId);
+ }
+
+ MessagingContentProvider.notifyConversationListChanged();
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Number of conversations refreshed:" + conversationIds.size());
+ }
+ }
+ }
+
+ /**
+ * Refresh conversation names/avatars based on a changed participant.
+ */
+ @DoesNotRunOnMainThread
+ public static void refreshConversationsForParticipant(final String participantId) {
+ Assert.isNotMainThread();
+ final ArrayList<String> participantList = new ArrayList<String>(1);
+ participantList.add(participantId);
+ refreshConversationsForParticipants(participantList);
+ }
+
+ /**
+ * Refresh one conversation.
+ */
+ private static void refreshConversation(final String conversationId) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ db.beginTransaction();
+ try {
+ BugleDatabaseOperations.updateConversationNameAndAvatarInTransaction(db,
+ conversationId);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ MessagingContentProvider.notifyParticipantsChanged(conversationId);
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
+ }
+
+ @DoesNotRunOnMainThread
+ public static boolean updateRowIfExists(final DatabaseWrapper db, final String table,
+ final String rowKey, final String rowId, final ContentValues values) {
+ Assert.isNotMainThread();
+ final StringBuilder sb = new StringBuilder();
+ final ArrayList<String> whereValues = new ArrayList<String>(values.size() + 1);
+ whereValues.add(rowId);
+
+ for (final String key : values.keySet()) {
+ if (sb.length() > 0) {
+ sb.append(" OR ");
+ }
+ final Object value = values.get(key);
+ sb.append(key);
+ if (value != null) {
+ sb.append(" IS NOT ?");
+ whereValues.add(value.toString());
+ } else {
+ sb.append(" IS NOT NULL");
+ }
+ }
+
+ final String whereClause = rowKey + "=?" + " AND (" + sb.toString() + ")";
+ final String [] whereValuesArray = whereValues.toArray(new String[whereValues.size()]);
+ final int count = db.update(table, values, whereClause, whereValuesArray);
+ if (count > 1) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Updated more than 1 row " + count + "; " + table +
+ " for " + rowKey + " = " + rowId + " (deleted?)");
+ }
+ Assert.inRange(count, 0, 1);
+ return (count >= 0);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/BugleNotifications.java b/src/com/android/messaging/datamodel/BugleNotifications.java
new file mode 100644
index 0000000..b796e73
--- /dev/null
+++ b/src/com/android/messaging/datamodel/BugleNotifications.java
@@ -0,0 +1,1221 @@
+/*
+ * 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.datamodel;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Typeface;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.RemoteInput;
+import android.support.v4.util.SimpleArrayMap;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.text.style.TextAppearanceSpan;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MessageNotificationState.BundledMessageNotificationState;
+import com.android.messaging.datamodel.MessageNotificationState.ConversationLineInfo;
+import com.android.messaging.datamodel.MessageNotificationState.MultiConversationNotificationState;
+import com.android.messaging.datamodel.MessageNotificationState.MultiMessageNotificationState;
+import com.android.messaging.datamodel.action.MarkAsReadAction;
+import com.android.messaging.datamodel.action.MarkAsSeenAction;
+import com.android.messaging.datamodel.action.RedownloadMmsAction;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageResource;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.datamodel.media.VideoThumbnailRequest;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ConversationIdSet;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.NotificationPlayer;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PendingIntentConstants;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.RingtoneUtil;
+import com.android.messaging.util.ThreadUtil;
+import com.android.messaging.util.UriUtil;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Handle posting, updating and removing all conversation notifications.
+ *
+ * There are currently two main classes of notification and their rules: <p>
+ * 1) Messages - {@link MessageNotificationState}. Only one message notification.
+ * Unread messages across senders and conversations are coalesced.<p>
+ * 2) Failed Messages - {@link MessageNotificationState#checkFailedMesages } Only one failed
+ * message. Multiple failures are coalesced.<p>
+ *
+ * To add a new class of notifications, subclass the NotificationState and add commands which
+ * create one and pass into general creation function.
+ *
+ */
+public class BugleNotifications {
+ // Logging
+ public static final String TAG = LogUtil.BUGLE_NOTIFICATIONS_TAG;
+
+ // Constants to use for update.
+ public static final int UPDATE_NONE = 0;
+ public static final int UPDATE_MESSAGES = 1;
+ public static final int UPDATE_ERRORS = 2;
+ public static final int UPDATE_ALL = UPDATE_MESSAGES + UPDATE_ERRORS;
+
+ // Constants for notification type used for audio and vibration settings.
+ public static final int LOCAL_SMS_NOTIFICATION = 0;
+
+ private static final String SMS_NOTIFICATION_TAG = ":sms:";
+ private static final String SMS_ERROR_NOTIFICATION_TAG = ":error:";
+
+ private static final String WEARABLE_COMPANION_APP_PACKAGE = "com.google.android.wearable.app";
+
+ private static final Set<NotificationState> sPendingNotifications =
+ new HashSet<NotificationState>();
+
+ private static int sWearableImageWidth;
+ private static int sWearableImageHeight;
+ private static int sIconWidth;
+ private static int sIconHeight;
+
+ private static boolean sInitialized = false;
+
+ private static final Object mLock = new Object();
+
+ // sLastMessageDingTime is a map between a conversation id and a time. It's used to keep track
+ // of the time we last dinged a message for this conversation. When messages are coming in
+ // at flurry, we don't want to over-ding the user.
+ private static final SimpleArrayMap<String, Long> sLastMessageDingTime =
+ new SimpleArrayMap<String, Long>();
+ private static int sTimeBetweenDingsMs;
+
+ /**
+ * This is the volume at which to play the observable-conversation notification sound,
+ * expressed as a fraction of the system notification volume.
+ */
+ private static final float OBSERVABLE_CONVERSATION_NOTIFICATION_VOLUME = 0.25f;
+
+ /**
+ * Entry point for posting notifications.
+ * Don't call this on the UI thread.
+ * @param silent If true, no ring will be played. If false, checks global settings before
+ * playing a ringtone
+ * @param coverage Indicates which notification types should be checked. Valid values are
+ * UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, or UPDATE_ALL
+ */
+ public static void update(final boolean silent, final int coverage) {
+ update(silent, null /* conversationId */, coverage);
+ }
+
+ /**
+ * Entry point for posting notifications.
+ * Don't call this on the UI thread.
+ * @param silent If true, no ring will be played. If false, checks global settings before
+ * playing a ringtone
+ * @param conversationId Conversation ID where a new message was received
+ * @param coverage Indicates which notification types should be checked. Valid values are
+ * UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, or UPDATE_ALL
+ */
+ public static void update(final boolean silent, final String conversationId,
+ final int coverage) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Update: silent = " + silent
+ + " conversationId = " + conversationId
+ + " coverage = " + coverage);
+ }
+ Assert.isNotMainThread();
+ checkInitialized();
+
+ if (!shouldNotify()) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Notifications disabled");
+ }
+ cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
+ return;
+ } else {
+ if ((coverage & UPDATE_MESSAGES) != 0) {
+ createMessageNotification(silent, conversationId);
+ }
+ }
+ if ((coverage & UPDATE_ERRORS) != 0) {
+ MessageNotificationState.checkFailedMessages();
+ }
+ }
+
+ /**
+ * Cancel all notifications of a certain type.
+ *
+ * @param type Message or error notifications from Constants.
+ */
+ private static synchronized void cancel(final int type) {
+ cancel(type, null, false);
+ }
+
+ /**
+ * Cancel all notifications of a certain type.
+ *
+ * @param type Message or error notifications from Constants.
+ * @param conversationId If set, cancel the notification for this
+ * conversation only. For message notifications, this only works
+ * if the notifications are bundled (group children).
+ * @param isBundledNotification True if this notification is part of a
+ * notification bundle. This only applies to message notifications,
+ * which are bundled together with other message notifications.
+ */
+ private static synchronized void cancel(final int type, final String conversationId,
+ final boolean isBundledNotification) {
+ final String notificationTag = buildNotificationTag(type, conversationId,
+ isBundledNotification);
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(Factory.get().getApplicationContext());
+
+ // Find all pending notifications and cancel them.
+ synchronized (sPendingNotifications) {
+ final Iterator<NotificationState> iter = sPendingNotifications.iterator();
+ while (iter.hasNext()) {
+ final NotificationState notifState = iter.next();
+ if (notifState.mType == type) {
+ notifState.mCanceled = true;
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Canceling pending notification");
+ }
+ iter.remove();
+ }
+ }
+ }
+ notificationManager.cancel(notificationTag, type);
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "Canceled notifications of type " + type);
+ }
+
+ // Message notifications for multiple conversations can be grouped together (see comment in
+ // createMessageNotification). We need to do bookkeeping to track the current set of
+ // notification group children, including removing them when we cancel notifications).
+ if (type == PendingIntentConstants.SMS_NOTIFICATION_ID) {
+ final Context context = Factory.get().getApplicationContext();
+ final ConversationIdSet groupChildIds = getGroupChildIds(context);
+
+ if (groupChildIds != null && groupChildIds.size() > 0) {
+ // If a conversation is specified, remove just that notification. Otherwise,
+ // we're removing the group summary so clear all children.
+ if (conversationId != null) {
+ groupChildIds.remove(conversationId);
+ writeGroupChildIds(context, groupChildIds);
+ } else {
+ cancelStaleGroupChildren(groupChildIds, null);
+ // We'll update the group children preference as we cancel each child,
+ // so we don't need to do it here.
+ }
+ }
+ }
+ }
+
+ /**
+ * Cancels stale notifications from the currently active group of
+ * notifications. If the {@code state} parameter is an instance of
+ * {@link MultiConversationNotificationState} it represents a new
+ * notification group. This method will cancel any notifications that were
+ * in the old group, but not the new one. If the new notification is not a
+ * group, then all existing grouped notifications are cancelled.
+ *
+ * @param previousGroupChildren Conversation ids for the active notification
+ * group
+ * @param state New notification state
+ */
+ private static void cancelStaleGroupChildren(final ConversationIdSet previousGroupChildren,
+ final NotificationState state) {
+ final ConversationIdSet newChildren = new ConversationIdSet();
+ if (state instanceof MultiConversationNotificationState) {
+ for (final NotificationState child :
+ ((MultiConversationNotificationState) state).mChildren) {
+ if (child.mConversationIds != null) {
+ newChildren.add(child.mConversationIds.first());
+ }
+ }
+ }
+ for (final String childConversationId : previousGroupChildren) {
+ if (!newChildren.contains(childConversationId)) {
+ cancel(PendingIntentConstants.SMS_NOTIFICATION_ID, childConversationId, true);
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if incoming notifications should display a
+ * notification, {@code false} otherwise.
+ *
+ * @return true if the notification should occur
+ */
+ private static boolean shouldNotify() {
+ // If we're not the default sms app, don't put up any notifications.
+ if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
+ return false;
+ }
+
+ // Now check prefs (i.e. settings) to see if the user turned off notifications.
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final String prefKey = context.getString(R.string.notifications_enabled_pref_key);
+ final boolean defaultValue = context.getResources().getBoolean(
+ R.bool.notifications_enabled_pref_default);
+ return prefs.getBoolean(prefKey, defaultValue);
+ }
+
+ /**
+ * Returns {@code true} if incoming notifications for the given {@link NotificationState}
+ * should vibrate the device, {@code false} otherwise.
+ *
+ * @return true if vibration should be used
+ */
+ public static boolean shouldVibrate(final NotificationState state) {
+ // The notification should vibrate if the global setting is turned on AND
+ // the per-conversation setting is turned on (default).
+ if (!state.getNotificationVibrate()) {
+ return false;
+ } else {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final String prefKey = context.getString(R.string.notification_vibration_pref_key);
+ final boolean defaultValue = context.getResources().getBoolean(
+ R.bool.notification_vibration_pref_default);
+ return prefs.getBoolean(prefKey, defaultValue);
+ }
+ }
+
+ private static Uri getNotificationRingtoneUriForConversationId(final String conversationId) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final ConversationListItemData convData =
+ ConversationListItemData.getExistingConversation(db, conversationId);
+ return RingtoneUtil.getNotificationRingtoneUri(
+ convData != null ? convData.getNotificationSoundUri() : null);
+ }
+
+ /**
+ * Returns a unique tag to identify a notification.
+ *
+ * @param name The tag name (in practice, the type)
+ * @param conversationId The conversation id (optional)
+ */
+ private static String buildNotificationTag(final String name,
+ final String conversationId) {
+ final Context context = Factory.get().getApplicationContext();
+ if (conversationId != null) {
+ return context.getPackageName() + name + ":" + conversationId;
+ } else {
+ return context.getPackageName() + name;
+ }
+ }
+
+ /**
+ * Returns a unique tag to identify a notification.
+ * <p>
+ * This delegates to
+ * {@link #buildNotificationTag(int, String, boolean)} and can be
+ * used when the notification is never bundled (e.g. error notifications).
+ */
+ static String buildNotificationTag(final int type, final String conversationId) {
+ return buildNotificationTag(type, conversationId, false /* bundledNotification */);
+ }
+
+ /**
+ * Returns a unique tag to identify a notification.
+ *
+ * @param type One of the constants in {@link PendingIntentConstants}
+ * @param conversationId The conversation id (where applicable)
+ * @param bundledNotification Set to true if this notification will be
+ * bundled together with other notifications (e.g. on a wearable
+ * device).
+ */
+ static String buildNotificationTag(final int type, final String conversationId,
+ final boolean bundledNotification) {
+ String tag = null;
+ switch(type) {
+ case PendingIntentConstants.SMS_NOTIFICATION_ID:
+ if (bundledNotification) {
+ tag = buildNotificationTag(SMS_NOTIFICATION_TAG, conversationId);
+ } else {
+ tag = buildNotificationTag(SMS_NOTIFICATION_TAG, null);
+ }
+ break;
+ case PendingIntentConstants.MSG_SEND_ERROR:
+ tag = buildNotificationTag(SMS_ERROR_NOTIFICATION_TAG, null);
+ break;
+ }
+ return tag;
+ }
+
+ private static void checkInitialized() {
+ if (!sInitialized) {
+ final Resources resources = Factory.get().getApplicationContext().getResources();
+ sWearableImageWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_wearable_image_width);
+ sWearableImageHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_wearable_image_height);
+ sIconHeight = (int) resources.getDimension(
+ android.R.dimen.notification_large_icon_height);
+ sIconWidth =
+ (int) resources.getDimension(android.R.dimen.notification_large_icon_width);
+
+ sInitialized = true;
+ }
+ }
+
+ private static void processAndSend(final NotificationState state, final boolean silent,
+ final boolean softSound) {
+ final Context context = Factory.get().getApplicationContext();
+ final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context);
+ notifBuilder.setCategory(Notification.CATEGORY_MESSAGE);
+ // TODO: Need to fix this for multi conversation notifications to rate limit dings.
+ final String conversationId = state.mConversationIds.first();
+
+
+ final Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(state.getRingtoneUri());
+ // If the notification's conversation is currently observable (focused or in the
+ // conversation list), then play a notification beep at a low volume and don't display an
+ // actual notification.
+ if (softSound) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "processAndSend: fromConversationId == " +
+ "sCurrentlyDisplayedConversationId so NOT showing notification," +
+ " but playing soft sound. conversationId: " + conversationId);
+ }
+ playObservableConversationNotificationSound(ringtoneUri);
+ return;
+ }
+ state.mBaseRequestCode = state.mType;
+
+ // Set the delete intent (except for bundled wearable notifications, which are dismissed
+ // as a group, either from the wearable or when the summary notification is dismissed from
+ // the host device).
+ if (!(state instanceof BundledMessageNotificationState)) {
+ final PendingIntent clearIntent = state.getClearIntent();
+ notifBuilder.setDeleteIntent(clearIntent);
+ }
+
+ updateBuilderAudioVibrate(state, notifBuilder, silent, ringtoneUri, conversationId);
+
+ // Set the content intent
+ PendingIntent destinationIntent;
+ if (state.mConversationIds.size() > 1) {
+ // We have notifications for multiple conversation, go to the conversation list.
+ destinationIntent = UIIntents.get()
+ .getPendingIntentForConversationListActivity(context);
+ } else {
+ // We have a single conversation, go directly to that conversation.
+ destinationIntent = UIIntents.get()
+ .getPendingIntentForConversationActivity(context,
+ state.mConversationIds.first(),
+ null /*draft*/);
+ }
+ notifBuilder.setContentIntent(destinationIntent);
+
+ // TODO: set based on contact coming from a favorite.
+ notifBuilder.setPriority(state.getPriority());
+
+ // Save the state of the notification in-progress so when the avatar is loaded,
+ // we can continue building the notification.
+ final NotificationCompat.Style notifStyle = state.build(notifBuilder);
+ state.mNotificationBuilder = notifBuilder;
+ state.mNotificationStyle = notifStyle;
+ if (!state.mPeople.isEmpty()) {
+ final Bundle people = new Bundle();
+ people.putStringArray(NotificationCompat.EXTRA_PEOPLE,
+ state.mPeople.toArray(new String[state.mPeople.size()]));
+ notifBuilder.addExtras(people);
+ }
+
+ if (state.mParticipantAvatarsUris != null) {
+ final Uri avatarUri = state.mParticipantAvatarsUris.get(0);
+ final AvatarRequestDescriptor descriptor = new AvatarRequestDescriptor(avatarUri,
+ sIconWidth, sIconHeight, OsUtil.isAtLeastL());
+ final MediaRequest<ImageResource> imageRequest = descriptor.buildSyncMediaRequest(
+ context);
+
+ synchronized (sPendingNotifications) {
+ sPendingNotifications.add(state);
+ }
+
+ // Synchronously load the avatar.
+ final ImageResource avatarImage =
+ MediaResourceManager.get().requestMediaResourceSync(imageRequest);
+ if (avatarImage != null) {
+ ImageResource avatarHiRes = null;
+ try {
+ if (isWearCompanionAppInstalled()) {
+ // For Wear users, we need to request a high-res avatar image to use as the
+ // notification card background. If the sender has a contact photo, we'll
+ // request the display photo from the Contacts provider. Otherwise, we ask
+ // the local content provider for a hi-res version of the generic avatar
+ // (e.g. letter with colored background).
+ avatarHiRes = requestContactDisplayPhoto(context,
+ getDisplayPhotoUri(avatarUri));
+ if (avatarHiRes == null) {
+ final AvatarRequestDescriptor hiResDesc =
+ new AvatarRequestDescriptor(avatarUri,
+ sWearableImageWidth,
+ sWearableImageHeight,
+ false /* cropToCircle */,
+ true /* isWearBackground */);
+ avatarHiRes = MediaResourceManager.get().requestMediaResourceSync(
+ hiResDesc.buildSyncMediaRequest(context));
+ }
+ }
+
+ // We have to make copies of the bitmaps to hand to the NotificationManager
+ // because the bitmap in the ImageResource is managed and will automatically
+ // get released.
+ Bitmap avatarBitmap = Bitmap.createBitmap(avatarImage.getBitmap());
+ Bitmap avatarHiResBitmap = (avatarHiRes != null) ?
+ Bitmap.createBitmap(avatarHiRes.getBitmap()) : null;
+ sendNotification(state, avatarBitmap, avatarHiResBitmap);
+ return;
+ } finally {
+ avatarImage.release();
+ if (avatarHiRes != null) {
+ avatarHiRes.release();
+ }
+ }
+ }
+ }
+ // We have no avatar. Post the notification anyway.
+ sendNotification(state, null, null);
+ }
+
+ /**
+ * Returns the thumbnailUri from the avatar URI, or null if avatar URI does not have thumbnail.
+ */
+ private static Uri getThumbnailUri(final Uri avatarUri) {
+ Uri localUri = null;
+ final String avatarType = AvatarUriUtil.getAvatarType(avatarUri);
+ if (TextUtils.equals(avatarType, AvatarUriUtil.TYPE_LOCAL_RESOURCE_URI)) {
+ localUri = AvatarUriUtil.getPrimaryUri(avatarUri);
+ } else if (UriUtil.isLocalResourceUri(avatarUri)) {
+ localUri = avatarUri;
+ }
+ if (localUri != null && localUri.getAuthority().equals(ContactsContract.AUTHORITY)) {
+ // Contact photos are of the form: content://com.android.contacts/contacts/123/photo
+ final List<String> pathParts = localUri.getPathSegments();
+ if (pathParts.size() == 3 &&
+ pathParts.get(2).equals(Contacts.Photo.CONTENT_DIRECTORY)) {
+ return localUri;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the displayPhotoUri from the avatar URI, or null if avatar URI
+ * does not have a displayPhotoUri.
+ */
+ private static Uri getDisplayPhotoUri(final Uri avatarUri) {
+ final Uri thumbnailUri = getThumbnailUri(avatarUri);
+ if (thumbnailUri == null) {
+ return null;
+ }
+ final List<String> originalPaths = thumbnailUri.getPathSegments();
+ final int originalPathsSize = originalPaths.size();
+ final StringBuilder newPathBuilder = new StringBuilder();
+ // Change content://com.android.contacts/contacts("_corp")/123/photo to
+ // content://com.android.contacts/contacts("_corp")/123/display_photo
+ for (int i = 0; i < originalPathsSize; i++) {
+ newPathBuilder.append('/');
+ if (i == 2) {
+ newPathBuilder.append(ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
+ } else {
+ newPathBuilder.append(originalPaths.get(i));
+ }
+ }
+ return thumbnailUri.buildUpon().path(newPathBuilder.toString()).build();
+ }
+
+ private static ImageResource requestContactDisplayPhoto(final Context context,
+ final Uri displayPhotoUri) {
+ final UriImageRequestDescriptor bgDescriptor =
+ new UriImageRequestDescriptor(displayPhotoUri,
+ sWearableImageWidth,
+ sWearableImageHeight,
+ false, /* allowCompression */
+ true, /* isStatic */
+ false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ return MediaResourceManager.get().requestMediaResourceSync(
+ bgDescriptor.buildSyncMediaRequest(context));
+ }
+
+ private static void createMessageNotification(final boolean silent,
+ final String conversationId) {
+ final NotificationState state = MessageNotificationState.getNotificationState();
+ final boolean softSound = DataModel.get().isNewMessageObservable(conversationId);
+ if (state == null) {
+ cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
+ if (softSound && !TextUtils.isEmpty(conversationId)) {
+ final Uri ringtoneUri = getNotificationRingtoneUriForConversationId(conversationId);
+ playObservableConversationNotificationSound(ringtoneUri);
+ }
+ return;
+ }
+ processAndSend(state, silent, softSound);
+
+ // The rest of the logic here is for supporting Android Wear devices, specifically for when
+ // we are notifying about multiple conversations. In that case, the Inbox-style summary
+ // notification (which we already processed above) appears on the phone (as it always has),
+ // but wearables show per-conversation notifications, bundled together in a group.
+
+ // It is valid to replace a notification group with another group with fewer conversations,
+ // or even with one notification for a single conversation. In either case, we need to
+ // explicitly cancel any children from the old group which are not being notified about now.
+ final Context context = Factory.get().getApplicationContext();
+ final ConversationIdSet oldGroupChildIds = getGroupChildIds(context);
+ if (oldGroupChildIds != null && oldGroupChildIds.size() > 0) {
+ cancelStaleGroupChildren(oldGroupChildIds, state);
+ }
+
+ // Send per-conversation notifications (if there are multiple conversations).
+ final ConversationIdSet groupChildIds = new ConversationIdSet();
+ if (state instanceof MultiConversationNotificationState) {
+ for (final NotificationState child :
+ ((MultiConversationNotificationState) state).mChildren) {
+ processAndSend(child, true /* silent */, softSound);
+ if (child.mConversationIds != null) {
+ groupChildIds.add(child.mConversationIds.first());
+ }
+ }
+ }
+
+ // Record the new set of group children.
+ writeGroupChildIds(context, groupChildIds);
+ }
+
+ private static void updateBuilderAudioVibrate(final NotificationState state,
+ final NotificationCompat.Builder notifBuilder, final boolean silent,
+ final Uri ringtoneUri, final String conversationId) {
+ int defaults = Notification.DEFAULT_LIGHTS;
+ if (!silent) {
+ final BuglePrefs prefs = Factory.get().getApplicationPrefs();
+ final long latestNotificationTimestamp = prefs.getLong(
+ BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, Long.MIN_VALUE);
+ final long latestReceivedTimestamp = state.getLatestReceivedTimestamp();
+ prefs.putLong(
+ BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP,
+ Math.max(latestNotificationTimestamp, latestReceivedTimestamp));
+ if (latestReceivedTimestamp > latestNotificationTimestamp) {
+ synchronized (mLock) {
+ // Find out the last time we dinged for this conversation
+ Long lastTime = sLastMessageDingTime.get(conversationId);
+ if (sTimeBetweenDingsMs == 0) {
+ sTimeBetweenDingsMs = BugleGservices.get().getInt(
+ BugleGservicesKeys.NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS,
+ BugleGservicesKeys.NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS_DEFAULT) *
+ 1000;
+ }
+ if (lastTime == null
+ || SystemClock.elapsedRealtime() - lastTime > sTimeBetweenDingsMs) {
+ sLastMessageDingTime.put(conversationId, SystemClock.elapsedRealtime());
+ notifBuilder.setSound(ringtoneUri);
+ if (shouldVibrate(state)) {
+ defaults |= Notification.DEFAULT_VIBRATE;
+ }
+ }
+ }
+ }
+ }
+ notifBuilder.setDefaults(defaults);
+ }
+
+ // TODO: this doesn't seem to be defined in NotificationCompat yet. Temporarily
+ // define it here until it makes its way from Notification -> NotificationCompat.
+ /**
+ * Notification category: incoming direct message (SMS, instant message, etc.).
+ */
+ private static final String CATEGORY_MESSAGE = "msg";
+
+ private static void sendNotification(final NotificationState notificationState,
+ final Bitmap avatarIcon, final Bitmap avatarHiRes) {
+ final Context context = Factory.get().getApplicationContext();
+ if (notificationState.mCanceled) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "sendNotification: Notification already cancelled; dropping it");
+ }
+ return;
+ }
+
+ synchronized (sPendingNotifications) {
+ if (sPendingNotifications.contains(notificationState)) {
+ sPendingNotifications.remove(notificationState);
+ }
+ }
+
+ notificationState.mNotificationBuilder
+ .setSmallIcon(notificationState.getIcon())
+ .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
+ .setColor(context.getResources().getColor(R.color.notification_accent_color))
+// .setPublicVersion(null) // TODO: when/if we ever support different
+ // text on the lockscreen, instead of "contents hidden"
+ .setCategory(CATEGORY_MESSAGE);
+
+ if (avatarIcon != null) {
+ notificationState.mNotificationBuilder.setLargeIcon(avatarIcon);
+ }
+
+ if (notificationState.mParticipantContactUris != null &&
+ notificationState.mParticipantContactUris.size() > 0) {
+ for (final Uri contactUri : notificationState.mParticipantContactUris) {
+ notificationState.mNotificationBuilder.addPerson(contactUri.toString());
+ }
+ }
+
+ final Uri attachmentUri = notificationState.getAttachmentUri();
+ final String attachmentType = notificationState.getAttachmentType();
+ Bitmap attachmentBitmap = null;
+
+ // For messages with photo/video attachment, request an image to show in the notification.
+ if (attachmentUri != null && notificationState.mNotificationStyle != null &&
+ (notificationState.mNotificationStyle instanceof
+ NotificationCompat.BigPictureStyle) &&
+ (ContentType.isImageType(attachmentType) ||
+ ContentType.isVideoType(attachmentType))) {
+ final boolean isVideo = ContentType.isVideoType(attachmentType);
+
+ MediaRequest<ImageResource> imageRequest;
+ if (isVideo) {
+ Assert.isTrue(VideoThumbnailRequest.shouldShowIncomingVideoThumbnails());
+ final MessagePartVideoThumbnailRequestDescriptor videoDescriptor =
+ new MessagePartVideoThumbnailRequestDescriptor(attachmentUri);
+ imageRequest = videoDescriptor.buildSyncMediaRequest(context);
+ } else {
+ final UriImageRequestDescriptor imageDescriptor =
+ new UriImageRequestDescriptor(attachmentUri,
+ sWearableImageWidth,
+ sWearableImageHeight,
+ false /* allowCompression */,
+ true /* isStatic */,
+ false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ imageRequest = imageDescriptor.buildSyncMediaRequest(context);
+ }
+ final ImageResource imageResource =
+ MediaResourceManager.get().requestMediaResourceSync(imageRequest);
+ if (imageResource != null) {
+ try {
+ // Copy the bitmap, because the one in the ImageResource is managed by
+ // MediaResourceManager.
+ Bitmap imageResourceBitmap = imageResource.getBitmap();
+ Config config = imageResourceBitmap.getConfig();
+
+ // Make sure our bitmap has a valid format.
+ if (config == null) {
+ config = Bitmap.Config.ARGB_8888;
+ }
+ attachmentBitmap = imageResourceBitmap.copy(config, true);
+ } finally {
+ imageResource.release();
+ }
+ }
+ }
+
+ fireOffNotification(notificationState, attachmentBitmap, avatarIcon, avatarHiRes);
+ }
+
+ private static void fireOffNotification(final NotificationState notificationState,
+ final Bitmap attachmentBitmap, final Bitmap avatarBitmap, Bitmap avatarHiResBitmap) {
+ if (notificationState.mCanceled) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Firing off notification, but notification already canceled");
+ }
+ return;
+ }
+
+ final Context context = Factory.get().getApplicationContext();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "MMS picture loaded, bitmap: " + attachmentBitmap);
+ }
+
+ final NotificationCompat.Builder notifBuilder = notificationState.mNotificationBuilder;
+ notifBuilder.setStyle(notificationState.mNotificationStyle);
+ notifBuilder.setColor(context.getResources().getColor(R.color.notification_accent_color));
+
+ final WearableExtender wearableExtender = new WearableExtender();
+ setWearableGroupOptions(notifBuilder, notificationState);
+
+ if (avatarHiResBitmap != null) {
+ wearableExtender.setBackground(avatarHiResBitmap);
+ } else if (avatarBitmap != null) {
+ // Nothing to do here; we already set avatarBitmap as the notification icon
+ } else {
+ final Bitmap defaultBackground = BitmapFactory.decodeResource(
+ context.getResources(), R.drawable.bg_sms);
+ wearableExtender.setBackground(defaultBackground);
+ }
+
+ if (notificationState instanceof MultiMessageNotificationState) {
+ if (attachmentBitmap != null) {
+ // When we've got a picture attachment, we do some switcheroo trickery. When
+ // the notification is expanded, we show the picture as a bigPicture. The small
+ // icon shows the sender's avatar. When that same notification is collapsed, the
+ // picture is shown in the location where the avatar is normally shown. The lines
+ // below make all that happen.
+
+ // Here we're taking the picture attachment and making a small, scaled, center
+ // cropped version of the picture we can stuff into the place where the avatar
+ // goes when the notification is collapsed.
+ final Bitmap smallBitmap = ImageUtils.scaleCenterCrop(attachmentBitmap, sIconWidth,
+ sIconHeight);
+ ((NotificationCompat.BigPictureStyle) notificationState.mNotificationStyle)
+ .bigPicture(attachmentBitmap)
+ .bigLargeIcon(avatarBitmap);
+ notificationState.mNotificationBuilder.setLargeIcon(smallBitmap);
+
+ // Add a wearable page with no visible card so you can more easily see the photo.
+ final NotificationCompat.Builder photoPageNotifBuilder =
+ new NotificationCompat.Builder(Factory.get().getApplicationContext());
+ final WearableExtender photoPageWearableExtender = new WearableExtender();
+ photoPageWearableExtender.setHintShowBackgroundOnly(true);
+ if (attachmentBitmap != null) {
+ final Bitmap wearBitmap = ImageUtils.scaleCenterCrop(attachmentBitmap,
+ sWearableImageWidth, sWearableImageHeight);
+ photoPageWearableExtender.setBackground(wearBitmap);
+ }
+ photoPageNotifBuilder.extend(photoPageWearableExtender);
+ wearableExtender.addPage(photoPageNotifBuilder.build());
+ }
+
+ maybeAddWearableConversationLog(wearableExtender,
+ (MultiMessageNotificationState) notificationState);
+ addDownloadMmsAction(notifBuilder, wearableExtender, notificationState);
+ addWearableVoiceReplyAction(wearableExtender, notificationState);
+ }
+
+ // Apply the wearable options and build & post the notification
+ notifBuilder.extend(wearableExtender);
+ doNotify(notifBuilder.build(), notificationState);
+ }
+
+ private static void setWearableGroupOptions(final NotificationCompat.Builder notifBuilder,
+ final NotificationState notificationState) {
+ final String groupKey = "groupkey";
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Group key (for wearables)=" + groupKey);
+ }
+ if (notificationState instanceof MultiConversationNotificationState) {
+ notifBuilder.setGroup(groupKey).setGroupSummary(true);
+ } else if (notificationState instanceof BundledMessageNotificationState) {
+ final int order = ((BundledMessageNotificationState) notificationState).mGroupOrder;
+ // Convert the order to a zero-padded string ("00", "01", "02", etc).
+ // The Wear library orders notifications within a bundle lexicographically
+ // by the sort key, hence the need for zeroes to preserve the ordering.
+ final String sortKey = String.format(Locale.US, "%02d", order);
+ notifBuilder.setGroup(groupKey).setSortKey(sortKey);
+ }
+ }
+
+ private static void maybeAddWearableConversationLog(
+ final WearableExtender wearableExtender,
+ final MultiMessageNotificationState notificationState) {
+ if (!isWearCompanionAppInstalled()) {
+ return;
+ }
+ final String convId = notificationState.mConversationIds.first();
+ ConversationLineInfo convInfo = notificationState.mConvList.mConvInfos.get(0);
+ final Notification page = MessageNotificationState.buildConversationPageForWearable(
+ convId,
+ convInfo.mParticipantCount);
+ if (page != null) {
+ wearableExtender.addPage(page);
+ }
+ }
+
+ private static void addWearableVoiceReplyAction(
+ final WearableExtender wearableExtender, final NotificationState notificationState) {
+ if (!(notificationState instanceof MultiMessageNotificationState)) {
+ return;
+ }
+ final MultiMessageNotificationState multiMessageNotificationState =
+ (MultiMessageNotificationState) notificationState;
+ final Context context = Factory.get().getApplicationContext();
+
+ final String conversationId = notificationState.mConversationIds.first();
+ final ConversationLineInfo convInfo =
+ multiMessageNotificationState.mConvList.mConvInfos.get(0);
+ final String selfId = convInfo.mSelfParticipantId;
+
+ final boolean requiresMms =
+ MmsSmsUtils.getRequireMmsForEmailAddress(
+ convInfo.mIncludeEmailAddress, convInfo.mSubId) ||
+ (convInfo.mIsGroup && MmsUtils.groupMmsEnabled(convInfo.mSubId));
+
+ final int requestCode = multiMessageNotificationState.getReplyIntentRequestCode();
+ final PendingIntent replyPendingIntent = UIIntents.get()
+ .getPendingIntentForSendingMessageToConversation(context,
+ conversationId, selfId, requiresMms, requestCode);
+
+ final int replyLabelRes = requiresMms ? R.string.notification_reply_via_mms :
+ R.string.notification_reply_via_sms;
+
+ final NotificationCompat.Action.Builder actionBuilder =
+ new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
+ context.getString(replyLabelRes), replyPendingIntent);
+ final String[] choices = context.getResources().getStringArray(
+ R.array.notification_reply_choices);
+ final RemoteInput remoteInput = new RemoteInput.Builder(Intent.EXTRA_TEXT).setLabel(
+ context.getString(R.string.notification_reply_prompt)).
+ setChoices(choices)
+ .build();
+ actionBuilder.addRemoteInput(remoteInput);
+ wearableExtender.addAction(actionBuilder.build());
+ }
+
+ private static void addDownloadMmsAction(final NotificationCompat.Builder notifBuilder,
+ final WearableExtender wearableExtender, final NotificationState notificationState) {
+ if (!(notificationState instanceof MultiMessageNotificationState)) {
+ return;
+ }
+ final MultiMessageNotificationState multiMessageNotificationState =
+ (MultiMessageNotificationState) notificationState;
+ final ConversationLineInfo convInfo =
+ multiMessageNotificationState.mConvList.mConvInfos.get(0);
+ if (!convInfo.getDoesLatestMessageNeedDownload()) {
+ return;
+ }
+ final String messageId = convInfo.getLatestMessageId();
+ if (messageId == null) {
+ // No message Id, no download for you
+ return;
+ }
+ final Context context = Factory.get().getApplicationContext();
+ final PendingIntent downloadPendingIntent =
+ RedownloadMmsAction.getPendingIntentForRedownloadMms(context, messageId);
+
+ final NotificationCompat.Action.Builder actionBuilder =
+ new NotificationCompat.Action.Builder(R.drawable.ic_file_download_light,
+ context.getString(R.string.notification_download_mms),
+ downloadPendingIntent);
+ final NotificationCompat.Action downloadAction = actionBuilder.build();
+ notifBuilder.addAction(downloadAction);
+
+ // Support the action on a wearable device as well
+ wearableExtender.addAction(downloadAction);
+ }
+
+ private static synchronized void doNotify(final Notification notification,
+ final NotificationState notificationState) {
+ if (notification == null) {
+ return;
+ }
+ final int type = notificationState.mType;
+ final ConversationIdSet conversationIds = notificationState.mConversationIds;
+ final boolean isBundledNotification =
+ (notificationState instanceof BundledMessageNotificationState);
+
+ // Mark the notification as finished
+ notificationState.mCanceled = true;
+
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(Factory.get().getApplicationContext());
+ // Only need conversationId for tags with a single conversation.
+ String conversationId = null;
+ if (conversationIds != null && conversationIds.size() == 1) {
+ conversationId = conversationIds.first();
+ }
+ final String notificationTag = buildNotificationTag(type,
+ conversationId, isBundledNotification);
+
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.defaults |= Notification.DEFAULT_LIGHTS;
+
+ notificationManager.notify(notificationTag, type, notification);
+
+ LogUtil.i(TAG, "Notifying for conversation " + conversationId + "; "
+ + "tag = " + notificationTag + ", type = " + type);
+ }
+
+ // This is the message string used in each line of an inboxStyle notification.
+ // TODO: add attachment type
+ static CharSequence formatInboxMessage(final String sender,
+ final CharSequence message, final Uri attachmentUri, final String attachmentType) {
+ final Context context = Factory.get().getApplicationContext();
+ final TextAppearanceSpan notificationSenderSpan = new TextAppearanceSpan(
+ context, R.style.NotificationSenderText);
+
+ final TextAppearanceSpan notificationTertiaryText = new TextAppearanceSpan(
+ context, R.style.NotificationTertiaryText);
+
+ final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
+ if (!TextUtils.isEmpty(sender)) {
+ spannableStringBuilder.append(sender);
+ spannableStringBuilder.setSpan(notificationSenderSpan, 0, sender.length(), 0);
+ }
+ final String separator = context.getString(R.string.notification_separator);
+
+ if (!TextUtils.isEmpty(message)) {
+ if (spannableStringBuilder.length() > 0) {
+ spannableStringBuilder.append(separator);
+ }
+ final int start = spannableStringBuilder.length();
+ spannableStringBuilder.append(message);
+ spannableStringBuilder.setSpan(notificationTertiaryText, start,
+ start + message.length(), 0);
+ }
+ if (attachmentUri != null) {
+ if (spannableStringBuilder.length() > 0) {
+ spannableStringBuilder.append(separator);
+ }
+ spannableStringBuilder.append(formatAttachmentTag(null, attachmentType));
+ }
+ return spannableStringBuilder;
+ }
+
+ protected static CharSequence buildColonSeparatedMessage(
+ final String title, final CharSequence content, final Uri attachmentUri,
+ final String attachmentType) {
+ return buildBoldedMessage(title, content, attachmentUri, attachmentType,
+ R.string.notification_ticker_separator);
+ }
+
+ protected static CharSequence buildSpaceSeparatedMessage(
+ final String title, final CharSequence content, final Uri attachmentUri,
+ final String attachmentType) {
+ return buildBoldedMessage(title, content, attachmentUri, attachmentType,
+ R.string.notification_space_separator);
+ }
+
+ /**
+ * buildBoldedMessage - build a formatted message where the title is bold, there's a
+ * separator, then the message.
+ */
+ private static CharSequence buildBoldedMessage(
+ final String title, final CharSequence message, final Uri attachmentUri,
+ final String attachmentType,
+ final int separatorId) {
+ final Context context = Factory.get().getApplicationContext();
+ final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
+
+ // Boldify the title (which is the sender's name)
+ if (!TextUtils.isEmpty(title)) {
+ spanBuilder.append(title);
+ spanBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, title.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ if (!TextUtils.isEmpty(message)) {
+ if (spanBuilder.length() > 0) {
+ spanBuilder.append(context.getString(separatorId));
+ }
+ spanBuilder.append(message);
+ }
+ if (attachmentUri != null) {
+ if (spanBuilder.length() > 0) {
+ final String separator = context.getString(R.string.notification_separator);
+ spanBuilder.append(separator);
+ }
+ spanBuilder.append(formatAttachmentTag(null, attachmentType));
+ }
+ return spanBuilder;
+ }
+
+ static CharSequence formatAttachmentTag(final String author, final String attachmentType) {
+ final Context context = Factory.get().getApplicationContext();
+ final TextAppearanceSpan notificationSecondaryText = new TextAppearanceSpan(
+ context, R.style.NotificationSecondaryText);
+ final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
+ if (!TextUtils.isEmpty(author)) {
+ final TextAppearanceSpan notificationSenderSpan = new TextAppearanceSpan(
+ context, R.style.NotificationSenderText);
+ spannableStringBuilder.append(author);
+ spannableStringBuilder.setSpan(notificationSenderSpan, 0, author.length(), 0);
+ final String separator = context.getString(R.string.notification_separator);
+ spannableStringBuilder.append(separator);
+ }
+ final int start = spannableStringBuilder.length();
+ // The default attachment type is an image, since that's what was originally
+ // supported. When there's no content type, assume it's an image.
+ int message = R.string.notification_picture;
+ if (ContentType.isAudioType(attachmentType)) {
+ message = R.string.notification_audio;
+ } else if (ContentType.isVideoType(attachmentType)) {
+ message = R.string.notification_video;
+ } else if (ContentType.isVCardType(attachmentType)) {
+ message = R.string.notification_vcard;
+ }
+ spannableStringBuilder.append(context.getText(message));
+ spannableStringBuilder.setSpan(notificationSecondaryText, start,
+ spannableStringBuilder.length(), 0);
+ return spannableStringBuilder;
+ }
+
+ /**
+ * Play the observable conversation notification sound (it's the regular notification sound, but
+ * played at half-volume)
+ */
+ private static void playObservableConversationNotificationSound(final Uri ringtoneUri) {
+ final Context context = Factory.get().getApplicationContext();
+ final AudioManager audioManager = (AudioManager) context
+ .getSystemService(Context.AUDIO_SERVICE);
+ final boolean silenced =
+ audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ if (silenced) {
+ return;
+ }
+
+ final NotificationPlayer player = new NotificationPlayer(LogUtil.BUGLE_TAG);
+ player.play(ringtoneUri, false,
+ AudioManager.STREAM_NOTIFICATION,
+ OBSERVABLE_CONVERSATION_NOTIFICATION_VOLUME);
+
+ // Stop the sound after five seconds to handle continuous ringtones
+ ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ player.stop();
+ }
+ }, 5000);
+ }
+
+ public static boolean isWearCompanionAppInstalled() {
+ boolean found = false;
+ try {
+ Factory.get().getApplicationContext().getPackageManager()
+ .getPackageInfo(WEARABLE_COMPANION_APP_PACKAGE, 0);
+ found = true;
+ } catch (final NameNotFoundException e) {
+ // Ignore; found is already false
+ }
+ return found;
+ }
+
+ /**
+ * When we go to the conversation list, call this to mark all messages as seen. That means
+ * we won't show a notification again for the same message.
+ */
+ public static void markAllMessagesAsSeen() {
+ MarkAsSeenAction.markAllAsSeen();
+ resetLastMessageDing(null); // reset the ding timeout for all conversations
+ }
+
+ /**
+ * When we open a particular conversation, call this to mark all messages as read.
+ */
+ public static void markMessagesAsRead(final String conversationId) {
+ MarkAsReadAction.markAsRead(conversationId);
+ resetLastMessageDing(conversationId);
+ }
+
+ /**
+ * Returns the conversation ids of all active, grouped notifications, or
+ * {code null} if no notifications are currently active and grouped.
+ */
+ private static ConversationIdSet getGroupChildIds(final Context context) {
+ final String prefKey = context.getString(R.string.notifications_group_children_key);
+ final String groupChildIdsText = BuglePrefs.getApplicationPrefs().getString(prefKey, "");
+ if (!TextUtils.isEmpty(groupChildIdsText)) {
+ return ConversationIdSet.createSet(groupChildIdsText);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Records the conversation ids of the currently active grouped notifications.
+ */
+ private static void writeGroupChildIds(final Context context,
+ final ConversationIdSet childIds) {
+ final ConversationIdSet oldChildIds = getGroupChildIds(context);
+ if (childIds.equals(oldChildIds)) {
+ return;
+ }
+ final String prefKey = context.getString(R.string.notifications_group_children_key);
+ BuglePrefs.getApplicationPrefs().putString(prefKey, childIds.getDelimitedString());
+ }
+
+ /**
+ * Reset the timer for a notification ding on a particular conversation or all conversations.
+ */
+ public static void resetLastMessageDing(final String conversationId) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(conversationId)) {
+ // reset all conversation dings
+ sLastMessageDingTime.clear();
+ } else {
+ sLastMessageDingTime.remove(conversationId);
+ }
+ }
+ }
+
+ public static void notifyEmergencySmsFailed(final String emergencyNumber,
+ final String conversationId) {
+ final Context context = Factory.get().getApplicationContext();
+
+ final CharSequence line1 = MessageNotificationState.applyWarningTextColor(context,
+ context.getString(R.string.notification_emergency_send_failure_line1,
+ emergencyNumber));
+ final String line2 = context.getString(R.string.notification_emergency_send_failure_line2,
+ emergencyNumber);
+ final PendingIntent destinationIntent = UIIntents.get()
+ .getPendingIntentForConversationActivity(context, conversationId, null /* draft */);
+
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
+ builder.setTicker(line1)
+ .setContentTitle(line1)
+ .setContentText(line2)
+ .setStyle(new NotificationCompat.BigTextStyle(builder).bigText(line2))
+ .setSmallIcon(R.drawable.ic_failed_light)
+ .setContentIntent(destinationIntent)
+ .setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure));
+
+ final String tag = context.getPackageName() + ":emergency_sms_error";
+ NotificationManagerCompat.from(context).notify(
+ tag,
+ PendingIntentConstants.MSG_SEND_ERROR,
+ builder.build());
+ }
+}
+
diff --git a/src/com/android/messaging/datamodel/BugleRecipientEntry.java b/src/com/android/messaging/datamodel/BugleRecipientEntry.java
new file mode 100644
index 0000000..2a9e5ff
--- /dev/null
+++ b/src/com/android/messaging/datamodel/BugleRecipientEntry.java
@@ -0,0 +1,64 @@
+/*
+ * 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.datamodel;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.ex.chips.RecipientEntry;
+
+/**
+ * An extension of RecipientEntry for Bugle's use since Bugle uses phone numbers to identify
+ * participants / recipients instead of contact ids. This allows the user to send to multiple
+ * phone numbers of the same contact.
+ */
+public class BugleRecipientEntry extends RecipientEntry {
+
+ protected BugleRecipientEntry(final int entryType, final String displayName,
+ final String destination, final int destinationType, final String destinationLabel,
+ final long contactId, final Long directoryId, final long dataId,
+ final Uri photoThumbnailUri, final boolean isFirstLevel, final boolean isValid,
+ final String lookupKey) {
+ super(entryType, displayName, destination, destinationType, destinationLabel, contactId,
+ directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid, lookupKey);
+ }
+
+ public static BugleRecipientEntry constructTopLevelEntry(final String displayName,
+ final int displayNameSource, final String destination, final int destinationType,
+ final String destinationLabel, final long contactId, final Long directoryId,
+ final long dataId, final String thumbnailUriAsString, final boolean isValid,
+ final String lookupKey) {
+ return new BugleRecipientEntry(ENTRY_TYPE_PERSON, displayName, destination, destinationType,
+ destinationLabel, contactId, directoryId, dataId, (thumbnailUriAsString != null
+ ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey);
+ }
+
+ public static BugleRecipientEntry constructSecondLevelEntry(final String displayName,
+ final int displayNameSource, final String destination, final int destinationType,
+ final String destinationLabel, final long contactId, final Long directoryId,
+ final long dataId, final String thumbnailUriAsString, final boolean isValid,
+ final String lookupKey) {
+ return new BugleRecipientEntry(ENTRY_TYPE_PERSON, displayName, destination, destinationType,
+ destinationLabel, contactId, directoryId, dataId, (thumbnailUriAsString != null
+ ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
+ }
+
+ @Override
+ public boolean isSamePerson(final RecipientEntry entry) {
+ return getDestination() != null && entry.getDestination() != null &&
+ TextUtils.equals(getDestination(), entry.getDestination());
+ }
+}
diff --git a/src/com/android/messaging/datamodel/ConversationImagePartsView.java b/src/com/android/messaging/datamodel/ConversationImagePartsView.java
new file mode 100644
index 0000000..70ba381
--- /dev/null
+++ b/src/com/android/messaging/datamodel/ConversationImagePartsView.java
@@ -0,0 +1,120 @@
+/*
+ * 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.datamodel;
+
+import android.provider.BaseColumns;
+
+import com.android.ex.photo.provider.PhotoContract.PhotoViewColumns;
+
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.util.ContentType;
+
+/**
+ * View for the image parts for the conversation. It is used to provide the photoviewer with a
+ * a data source for all the photos in a conversation, so that the photoviewer can support paging
+ * through all the photos of the conversation. The columns of the view are a superset of
+ * {@link com.android.ex.photo.provider.PhotoContract.PhotoViewColumns}.
+ */
+public class ConversationImagePartsView {
+ private static final String VIEW_NAME = "conversation_image_parts_view";
+
+ private static final String CREATE_SQL = "CREATE VIEW " +
+ VIEW_NAME + " AS SELECT "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
+ + " as " + Columns.CONVERSATION_ID + ", "
+ + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_URI
+ + " as " + Columns.URI + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME
+ + " as " + Columns.SENDER_FULL_NAME + ", "
+ + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_URI
+ + " as " + Columns.CONTENT_URI + ", "
+ // Use NULL as the thumbnail uri
+ + " NULL as " + Columns.THUMBNAIL_URI + ", "
+ + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.CONTENT_TYPE
+ + " as " + Columns.CONTENT_TYPE + ", "
+ //
+ // Columns in addition to those specified by PhotoContract
+ //
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
+ + " as " + Columns.DISPLAY_DESTINATION + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP
+ + " as " + Columns.RECEIVED_TIMESTAMP + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
+ + " as " + Columns.STATUS + " "
+
+ + " FROM " + DatabaseHelper.MESSAGES_TABLE + " LEFT JOIN " + DatabaseHelper.PARTS_TABLE
+ + " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID
+ + "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") "
+ + " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON ("
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
+ + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")"
+
+ // "content_type like 'image/%'"
+ + " WHERE " + DatabaseHelper.PARTS_TABLE + "." + PartColumns.CONTENT_TYPE
+ + " like '" + ContentType.IMAGE_PREFIX + "%'"
+
+ + " ORDER BY "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " ASC, "
+ + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + " ASC";
+
+ static class Columns implements BaseColumns {
+ static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID;
+ static final String URI = PhotoViewColumns.URI;
+ static final String SENDER_FULL_NAME = PhotoViewColumns.NAME;
+ static final String CONTENT_URI = PhotoViewColumns.CONTENT_URI;
+ static final String THUMBNAIL_URI = PhotoViewColumns.THUMBNAIL_URI;
+ static final String CONTENT_TYPE = PhotoViewColumns.CONTENT_TYPE;
+ // Columns in addition to those specified by PhotoContract
+ static final String DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION;
+ static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP;
+ static final String STATUS = MessageColumns.STATUS;
+ }
+
+ public interface PhotoViewQuery {
+ public final String[] PROJECTION = {
+ PhotoViewColumns.URI,
+ PhotoViewColumns.NAME,
+ PhotoViewColumns.CONTENT_URI,
+ PhotoViewColumns.THUMBNAIL_URI,
+ PhotoViewColumns.CONTENT_TYPE,
+ // Columns in addition to those specified by PhotoContract
+ Columns.DISPLAY_DESTINATION,
+ Columns.RECEIVED_TIMESTAMP,
+ Columns.STATUS,
+ };
+
+ public final int INDEX_URI = 0;
+ public final int INDEX_SENDER_FULL_NAME = 1;
+ public final int INDEX_CONTENT_URI = 2;
+ public final int INDEX_THUMBNAIL_URI = 3;
+ public final int INDEX_CONTENT_TYPE = 4;
+ // Columns in addition to those specified by PhotoContract
+ public final int INDEX_DISPLAY_DESTINATION = 5;
+ public final int INDEX_RECEIVED_TIMESTAMP = 6;
+ public final int INDEX_STATUS = 7;
+ }
+
+ static final String getViewName() {
+ return VIEW_NAME;
+ }
+
+ static final String getCreateSql() {
+ return CREATE_SQL;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/CursorQueryData.java b/src/com/android/messaging/datamodel/CursorQueryData.java
new file mode 100644
index 0000000..3e6a656
--- /dev/null
+++ b/src/com/android/messaging/datamodel/CursorQueryData.java
@@ -0,0 +1,82 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import com.android.messaging.util.Assert;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Holds parameters and data (such as content URI) for performing queries on the content provider.
+ * This class could then be used to perform a query using either a BoundCursorLoader or querying
+ * on the content resolver directly.
+ *
+ * This class is used for cases where the way to load a cursor is not fixed. For example,
+ * when using ContactUtil to query for phone numbers, the ContactPickerFragment wants to use
+ * a CursorLoader to asynchronously load the data and tie in nicely with its data binding
+ * paradigm, whereas ContactRecipientAdapter wants to synchronously perform the query on the
+ * worker thread.
+ */
+public class CursorQueryData {
+ protected final Uri mUri;
+ protected final String[] mProjection;
+ protected final String mSelection;
+ protected final String[] mSelectionArgs;
+ protected final String mSortOrder;
+ protected final Context mContext;
+
+ public CursorQueryData(final Context context, final Uri uri, final String[] projection,
+ final String selection, final String[] selectionArgs, final String sortOrder) {
+ mContext = context;
+ mUri = uri;
+ mProjection = projection;
+ mSelection = selection;
+ mSelectionArgs = selectionArgs;
+ mSortOrder = sortOrder;
+ }
+
+ public BoundCursorLoader createBoundCursorLoader(final String bindingId) {
+ return new BoundCursorLoader(bindingId, mContext, mUri, mProjection, mSelection,
+ mSelectionArgs, mSortOrder);
+ }
+
+ public Cursor performSynchronousQuery() {
+ Assert.isNotMainThread();
+ if (mUri == null) {
+ // See {@link #getEmptyQueryData}
+ return null;
+ } else {
+ return mContext.getContentResolver().query(mUri, mProjection, mSelection,
+ mSelectionArgs, mSortOrder);
+ }
+ }
+
+ @VisibleForTesting
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Representation of an invalid query. {@link #performSynchronousQuery} will return
+ * a null Cursor.
+ */
+ public static CursorQueryData getEmptyQueryData() {
+ return new CursorQueryData(null, null, null, null, null, null);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/DataModel.java b/src/com/android/messaging/datamodel/DataModel.java
new file mode 100644
index 0000000..936b51c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/DataModel.java
@@ -0,0 +1,158 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.text.TextUtils;
+
+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.BlockedParticipantsData;
+import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.ContactPickerData;
+import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.datamodel.data.LaunchConversationData;
+import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.ParticipantListItemData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
+import com.android.messaging.datamodel.data.PeopleOptionsItemData;
+import com.android.messaging.datamodel.data.SettingsData;
+import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
+import com.android.messaging.datamodel.data.SubscriptionListData;
+import com.android.messaging.datamodel.data.VCardContactItemData;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.ConnectivityUtil;
+
+public abstract class DataModel {
+ private String mFocusedConversation;
+ private boolean mConversationListScrolledToNewestConversation;
+
+ public static DataModel get() {
+ return Factory.get().getDataModel();
+ }
+
+ public static final void startActionService(final Action action) {
+ get().getActionService().startAction(action);
+ }
+
+ public static final void scheduleAction(final Action action,
+ final int code, final long delayMs) {
+ get().getActionService().scheduleAction(action, code, delayMs);
+ }
+
+ public abstract ConversationListData createConversationListData(final Context context,
+ final ConversationListDataListener listener, final boolean archivedMode);
+
+ public abstract ConversationData createConversationData(final Context context,
+ final ConversationDataListener listener, final String conversationId);
+
+ public abstract ContactListItemData createContactListItemData();
+
+ public abstract ContactPickerData createContactPickerData(final Context context,
+ final ContactPickerDataListener listener);
+
+ public abstract MediaPickerData createMediaPickerData(final Context context);
+
+ public abstract GalleryGridItemData createGalleryGridItemData();
+
+ public abstract LaunchConversationData createLaunchConversationData(
+ LaunchConversationDataListener listener);
+
+ public abstract PeopleOptionsItemData createPeopleOptionsItemData(final Context context);
+
+ public abstract PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
+ final Context context, final PeopleAndOptionsDataListener listener);
+
+ public abstract VCardContactItemData createVCardContactItemData(final Context context,
+ final MessagePartData data);
+
+ public abstract VCardContactItemData createVCardContactItemData(final Context context,
+ final Uri vCardUri);
+
+ public abstract ParticipantListItemData createParticipantListItemData(
+ final ParticipantData participant);
+
+ public abstract BlockedParticipantsData createBlockedParticipantsData(Context context,
+ BlockedParticipantsDataListener listener);
+
+ public abstract SubscriptionListData createSubscriptonListData(Context context);
+
+ public abstract SettingsData createSettingsData(Context context, SettingsDataListener listener);
+
+ public abstract DraftMessageData createDraftMessageData(String conversationId);
+
+ public abstract ActionService getActionService();
+
+ public abstract BackgroundWorker getBackgroundWorkerForActionService();
+
+ @DoesNotRunOnMainThread
+ public abstract DatabaseWrapper getDatabase();
+
+ // Allow DataModel to coordinate with activity lifetime events.
+ public abstract void onActivityResume();
+
+ abstract void onCreateTables(final SQLiteDatabase db);
+
+ public void setFocusedConversation(final String conversationId) {
+ mFocusedConversation = conversationId;
+ }
+
+ public boolean isFocusedConversation(final String conversationId) {
+ return !TextUtils.isEmpty(mFocusedConversation)
+ && TextUtils.equals(mFocusedConversation, conversationId);
+ }
+
+ public void setConversationListScrolledToNewestConversation(
+ final boolean scrolledToNewestConversation) {
+ mConversationListScrolledToNewestConversation = scrolledToNewestConversation;
+ }
+
+ public boolean isConversationListScrolledToNewestConversation() {
+ return mConversationListScrolledToNewestConversation;
+ }
+
+ /**
+ * If a new message is received in the specified conversation, will the user be able to
+ * observe it in some UI within the app?
+ * @param conversationId conversation with the new incoming message
+ */
+ public boolean isNewMessageObservable(final String conversationId) {
+ return isConversationListScrolledToNewestConversation()
+ || isFocusedConversation(conversationId);
+ }
+
+ public abstract void onApplicationCreated();
+
+ public abstract ConnectivityUtil getConnectivityUtil();
+
+ public abstract SyncManager getSyncManager();
+}
diff --git a/src/com/android/messaging/datamodel/DataModelException.java b/src/com/android/messaging/datamodel/DataModelException.java
new file mode 100644
index 0000000..7084438
--- /dev/null
+++ b/src/com/android/messaging/datamodel/DataModelException.java
@@ -0,0 +1,101 @@
+/*
+ * 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.datamodel;
+
+public class DataModelException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private static final int FIRST = 100;
+
+ // ERRORS GENERATED INTERNALLY BY DATA MODEL.
+
+ // ERRORS RELATED WITH SMS.
+ public static final int ERROR_SMS_TEMPORARY_FAILURE = 116;
+ public static final int ERROR_SMS_PERMANENT_FAILURE = 117;
+ public static final int ERROR_MMS_TEMPORARY_FAILURE = 118;
+ public static final int ERROR_MMS_PERMANENT_UNKNOWN_FAILURE = 119;
+
+ // Request expired.
+ public static final int ERROR_EXPIRED = 120;
+ // Request canceled by user.
+ public static final int ERROR_CANCELED = 121;
+
+ public static final int ERROR_MOBILE_DATA_DISABLED = 123;
+ public static final int ERROR_MMS_SERVICE_BLOCKED = 124;
+ public static final int ERROR_MMS_INVALID_ADDRESS = 125;
+ public static final int ERROR_MMS_NETWORK_PROBLEM = 126;
+ public static final int ERROR_MMS_MESSAGE_NOT_FOUND = 127;
+ public static final int ERROR_MMS_MESSAGE_FORMAT_CORRUPT = 128;
+ public static final int ERROR_MMS_CONTENT_NOT_ACCEPTED = 129;
+ public static final int ERROR_MMS_MESSAGE_NOT_SUPPORTED = 130;
+ public static final int ERROR_MMS_REPLY_CHARGING_ERROR = 131;
+ public static final int ERROR_MMS_ADDRESS_HIDING_NOT_SUPPORTED = 132;
+ public static final int ERROR_MMS_LACK_OF_PREPAID = 133;
+ public static final int ERROR_MMS_CAN_NOT_PERSIST = 134;
+ public static final int ERROR_MMS_NO_AVAILABLE_APN = 135;
+ public static final int ERROR_MMS_INVALID_MESSAGE_TO_SEND = 136;
+ public static final int ERROR_MMS_INVALID_MESSAGE_RECEIVED = 137;
+ public static final int ERROR_MMS_NO_CONFIGURATION = 138;
+
+ private static final int LAST = 138;
+
+ private final boolean mIsInjection;
+ private final int mErrorCode;
+ private final String mMessage;
+ private final long mBackoff;
+
+ public DataModelException(final int errorCode, final Exception innerException,
+ final long backoff, final boolean injection, final String message) {
+ // Since some of the exceptions passed in may not be serializable, only record message
+ // instead of setting inner exception for Exception class. Otherwise, we will get
+ // serialization issues when we pass ServerRequestException as intent extra later.
+ if (errorCode < FIRST || errorCode > LAST) {
+ throw new IllegalArgumentException("error code out of range: " + errorCode);
+ }
+ mIsInjection = injection;
+ mErrorCode = errorCode;
+ if (innerException != null) {
+ mMessage = innerException.getMessage() + " -- " +
+ (mIsInjection ? "[INJECTED] -- " : "") + message;
+ } else {
+ mMessage = (mIsInjection ? "[INJECTED] -- " : "") + message;
+ }
+
+ mBackoff = backoff;
+ }
+
+ public DataModelException(final int errorCode) {
+ this(errorCode, null, 0, false, null);
+ }
+
+ public DataModelException(final int errorCode, final Exception innerException) {
+ this(errorCode, innerException, 0, false, null);
+ }
+
+ public DataModelException(final int errorCode, final String message) {
+ this(errorCode, null, 0, false, message);
+ }
+
+ @Override
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/DataModelImpl.java b/src/com/android/messaging/datamodel/DataModelImpl.java
new file mode 100644
index 0000000..6ab3f00
--- /dev/null
+++ b/src/com/android/messaging/datamodel/DataModelImpl.java
@@ -0,0 +1,236 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.telephony.SubscriptionManager;
+
+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.BlockedParticipantsData;
+import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.ContactPickerData;
+import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.datamodel.data.LaunchConversationData;
+import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.ParticipantListItemData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
+import com.android.messaging.datamodel.data.PeopleOptionsItemData;
+import com.android.messaging.datamodel.data.SettingsData;
+import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
+import com.android.messaging.datamodel.data.SubscriptionListData;
+import com.android.messaging.datamodel.data.VCardContactItemData;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.ConnectivityUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+public class DataModelImpl extends DataModel {
+ private final Context mContext;
+ private final ActionService mActionService;
+ private final BackgroundWorker mDataModelWorker;
+ private final DatabaseHelper mDatabaseHelper;
+ private final ConnectivityUtil mConnectivityUtil;
+ private final SyncManager mSyncManager;
+
+ public DataModelImpl(final Context context) {
+ super();
+ mContext = context;
+ mActionService = new ActionService();
+ mDataModelWorker = new BackgroundWorker();
+ mDatabaseHelper = DatabaseHelper.getInstance(context);
+ mConnectivityUtil = new ConnectivityUtil(context);
+ mSyncManager = new SyncManager();
+ }
+
+ @Override
+ public ConversationListData createConversationListData(final Context context,
+ final ConversationListDataListener listener, final boolean archivedMode) {
+ return new ConversationListData(context, listener, archivedMode);
+ }
+
+ @Override
+ public ConversationData createConversationData(final Context context,
+ final ConversationDataListener listener, final String conversationId) {
+ return new ConversationData(context, listener, conversationId);
+ }
+
+ @Override
+ public ContactListItemData createContactListItemData() {
+ return new ContactListItemData();
+ }
+
+ @Override
+ public ContactPickerData createContactPickerData(final Context context,
+ final ContactPickerDataListener listener) {
+ return new ContactPickerData(context, listener);
+ }
+
+ @Override
+ public BlockedParticipantsData createBlockedParticipantsData(
+ final Context context, final BlockedParticipantsDataListener listener) {
+ return new BlockedParticipantsData(context, listener);
+ }
+
+ @Override
+ public MediaPickerData createMediaPickerData(final Context context) {
+ return new MediaPickerData(context);
+ }
+
+ @Override
+ public GalleryGridItemData createGalleryGridItemData() {
+ return new GalleryGridItemData();
+ }
+
+ @Override
+ public LaunchConversationData createLaunchConversationData(
+ final LaunchConversationDataListener listener) {
+ return new LaunchConversationData(listener);
+ }
+
+ @Override
+ public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) {
+ return new PeopleOptionsItemData(context);
+ }
+
+ @Override
+ public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
+ final Context context, final PeopleAndOptionsDataListener listener) {
+ return new PeopleAndOptionsData(conversationId, context, listener);
+ }
+
+ @Override
+ public VCardContactItemData createVCardContactItemData(final Context context,
+ final MessagePartData data) {
+ return new VCardContactItemData(context, data);
+ }
+
+ @Override
+ public VCardContactItemData createVCardContactItemData(final Context context,
+ final Uri vCardUri) {
+ return new VCardContactItemData(context, vCardUri);
+ }
+
+ @Override
+ public ParticipantListItemData createParticipantListItemData(
+ final ParticipantData participant) {
+ return new ParticipantListItemData(participant);
+ }
+
+ @Override
+ public SubscriptionListData createSubscriptonListData(Context context) {
+ return new SubscriptionListData(context);
+ }
+
+ @Override
+ public SettingsData createSettingsData(Context context, SettingsDataListener listener) {
+ return new SettingsData(context, listener);
+ }
+
+ @Override
+ public DraftMessageData createDraftMessageData(String conversationId) {
+ return new DraftMessageData(conversationId);
+ }
+
+ @Override
+ public ActionService getActionService() {
+ // We need to allow access to this on the UI thread since it's used to start actions.
+ return mActionService;
+ }
+
+ @Override
+ public BackgroundWorker getBackgroundWorkerForActionService() {
+ return mDataModelWorker;
+ }
+
+ @Override
+ @DoesNotRunOnMainThread
+ public DatabaseWrapper getDatabase() {
+ // We prevent the main UI thread from accessing the database since we have to allow
+ // public access to this class to enable sub-packages to access data.
+ Assert.isNotMainThread();
+ return mDatabaseHelper.getDatabase();
+ }
+
+ @Override
+ public ConnectivityUtil getConnectivityUtil() {
+ return mConnectivityUtil;
+ }
+
+ @Override
+ public SyncManager getSyncManager() {
+ return mSyncManager;
+ }
+
+ @Override
+ void onCreateTables(final SQLiteDatabase db) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Rebuilt databases: reseting related state");
+ // Clear other things that implicitly reference the DB
+ SyncManager.resetLastSyncTimestamps();
+ }
+
+ @Override
+ public void onActivityResume() {
+ // Perform an incremental sync and register for changes if necessary
+ mSyncManager.updateSyncObserver(mContext);
+
+ // Trigger a participant refresh if needed, we should only need to refresh if there is
+ // contact change while the activity was paused.
+ ParticipantRefresh.refreshParticipantsIfNeeded();
+ }
+
+ @Override
+ public void onApplicationCreated() {
+ FixupMessageStatusOnStartupAction.fixupMessageStatus();
+ ProcessPendingMessagesAction.processFirstPendingMessage();
+ SyncManager.immediateSync();
+
+ if (OsUtil.isAtLeastL_MR1()) {
+ // Start listening for subscription change events for refreshing self participants.
+ PhoneUtils.getDefault().toLMr1().registerOnSubscriptionsChangedListener(
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ // TODO: This dynamically changes the mms config that app is
+ // currently using. It may cause inconsistency in some cases. We need
+ // to check the usage of mms config and handle the dynamic change
+ // gracefully
+ MmsConfig.loadAsync();
+ ParticipantRefresh.refreshSelfParticipants();
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/DatabaseHelper.java b/src/com/android/messaging/datamodel/DatabaseHelper.java
new file mode 100644
index 0000000..f16bb3c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/DatabaseHelper.java
@@ -0,0 +1,813 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+
+import com.android.messaging.BugleApplication;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * TODO: Open Issues:
+ * - Should we be storing the draft messages in the regular messages table or should we have a
+ * separate table for drafts to keep the normal messages query as simple as possible?
+ */
+
+/**
+ * Allows access to the SQL database. This is package private.
+ */
+public class DatabaseHelper extends SQLiteOpenHelper {
+ public static final String DATABASE_NAME = "bugle_db";
+
+ private static final int getDatabaseVersion(final Context context) {
+ return Integer.parseInt(context.getResources().getString(R.string.database_version));
+ }
+
+ /** Table containing names of all other tables and views */
+ private static final String MASTER_TABLE = "sqlite_master";
+ /** Column containing the name of the tables and views */
+ private static final String[] MASTER_COLUMNS = new String[] { "name", };
+
+ // Table names
+ public static final String CONVERSATIONS_TABLE = "conversations";
+ public static final String MESSAGES_TABLE = "messages";
+ public static final String PARTS_TABLE = "parts";
+ public static final String PARTICIPANTS_TABLE = "participants";
+ public static final String CONVERSATION_PARTICIPANTS_TABLE = "conversation_participants";
+
+ // Views
+ static final String DRAFT_PARTS_VIEW = "draft_parts_view";
+
+ // Conversations table schema
+ public static class ConversationColumns implements BaseColumns {
+ /* SMS/MMS Thread ID from the system provider */
+ public static final String SMS_THREAD_ID = "sms_thread_id";
+
+ /* Display name for the conversation */
+ public static final String NAME = "name";
+
+ /* Latest Message ID for the read status to display in conversation list */
+ public static final String LATEST_MESSAGE_ID = "latest_message_id";
+
+ /* Latest text snippet for display in conversation list */
+ public static final String SNIPPET_TEXT = "snippet_text";
+
+ /* Latest text subject for display in conversation list, empty string if none exists */
+ public static final String SUBJECT_TEXT = "subject_text";
+
+ /* Preview Uri */
+ public static final String PREVIEW_URI = "preview_uri";
+
+ /* The preview uri's content type */
+ public static final String PREVIEW_CONTENT_TYPE = "preview_content_type";
+
+ /* If we should display the current draft snippet/preview pair or snippet/preview pair */
+ public static final String SHOW_DRAFT = "show_draft";
+
+ /* Latest draft text subject for display in conversation list, empty string if none exists*/
+ public static final String DRAFT_SUBJECT_TEXT = "draft_subject_text";
+
+ /* Latest draft text snippet for display, empty string if none exists */
+ public static final String DRAFT_SNIPPET_TEXT = "draft_snippet_text";
+
+ /* Draft Preview Uri, empty string if none exists */
+ public static final String DRAFT_PREVIEW_URI = "draft_preview_uri";
+
+ /* The preview uri's content type */
+ public static final String DRAFT_PREVIEW_CONTENT_TYPE = "draft_preview_content_type";
+
+ /* If this conversation is archived */
+ public static final String ARCHIVE_STATUS = "archive_status";
+
+ /* Timestamp for sorting purposes */
+ public static final String SORT_TIMESTAMP = "sort_timestamp";
+
+ /* Last read message timestamp */
+ public static final String LAST_READ_TIMESTAMP = "last_read_timestamp";
+
+ /* Avatar for the conversation. Could be for group of individual */
+ public static final String ICON = "icon";
+
+ /* Participant contact ID if this conversation has a single participant. -1 otherwise */
+ public static final String PARTICIPANT_CONTACT_ID = "participant_contact_id";
+
+ /* Participant lookup key if this conversation has a single participant. null otherwise */
+ public static final String PARTICIPANT_LOOKUP_KEY = "participant_lookup_key";
+
+ /*
+ * Participant's normalized destination if this conversation has a single participant.
+ * null otherwise.
+ */
+ public static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION =
+ "participant_normalized_destination";
+
+ /* Default self participant for the conversation */
+ public static final String CURRENT_SELF_ID = "current_self_id";
+
+ /* Participant count not including self (so will be 1 for 1:1 or bigger for group) */
+ public static final String PARTICIPANT_COUNT = "participant_count";
+
+ /* Should notifications be enabled for this conversation? */
+ public static final String NOTIFICATION_ENABLED = "notification_enabled";
+
+ /* Notification sound used for the conversation */
+ public static final String NOTIFICATION_SOUND_URI = "notification_sound_uri";
+
+ /* Should vibrations be enabled for the conversation's notification? */
+ public static final String NOTIFICATION_VIBRATION = "notification_vibration";
+
+ /* Conversation recipients include email address */
+ public static final String INCLUDE_EMAIL_ADDRESS = "include_email_addr";
+
+ // Record the last received sms's service center info if it indicates that the reply path
+ // is present (TP-Reply-Path), so that we could use it for the subsequent message to send.
+ // Refer to TS 23.040 D.6 and SmsMessageSender.java in Android Messaging app.
+ public static final String SMS_SERVICE_CENTER = "sms_service_center";
+ }
+
+ // Conversation table SQL
+ private static final String CREATE_CONVERSATIONS_TABLE_SQL =
+ "CREATE TABLE " + CONVERSATIONS_TABLE + "("
+ + ConversationColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ // TODO : Int? Required not default?
+ + ConversationColumns.SMS_THREAD_ID + " INT DEFAULT(0), "
+ + ConversationColumns.NAME + " TEXT, "
+ + ConversationColumns.LATEST_MESSAGE_ID + " INT, "
+ + ConversationColumns.SNIPPET_TEXT + " TEXT, "
+ + ConversationColumns.SUBJECT_TEXT + " TEXT, "
+ + ConversationColumns.PREVIEW_URI + " TEXT, "
+ + ConversationColumns.PREVIEW_CONTENT_TYPE + " TEXT, "
+ + ConversationColumns.SHOW_DRAFT + " INT DEFAULT(0), "
+ + ConversationColumns.DRAFT_SNIPPET_TEXT + " TEXT, "
+ + ConversationColumns.DRAFT_SUBJECT_TEXT + " TEXT, "
+ + ConversationColumns.DRAFT_PREVIEW_URI + " TEXT, "
+ + ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, "
+ + ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), "
+ + ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), "
+ + ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), "
+ + ConversationColumns.ICON + " TEXT, "
+ + ConversationColumns.PARTICIPANT_CONTACT_ID + " INT DEFAULT ( "
+ + ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED + "), "
+ + ConversationColumns.PARTICIPANT_LOOKUP_KEY + " TEXT, "
+ + 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_SOUND_URI + " TEXT, "
+ + ConversationColumns.NOTIFICATION_VIBRATION + " INT DEFAULT(1), "
+ + ConversationColumns.INCLUDE_EMAIL_ADDRESS + " INT DEFAULT(0), "
+ + ConversationColumns.SMS_SERVICE_CENTER + " TEXT "
+ + ");";
+
+ private static final String CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL =
+ "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SMS_THREAD_ID
+ + " ON " + CONVERSATIONS_TABLE
+ + "(" + ConversationColumns.SMS_THREAD_ID + ")";
+
+ private static final String CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL =
+ "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.ARCHIVE_STATUS
+ + " ON " + CONVERSATIONS_TABLE
+ + "(" + ConversationColumns.ARCHIVE_STATUS + ")";
+
+ private static final String CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL =
+ "CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP
+ + " ON " + CONVERSATIONS_TABLE
+ + "(" + ConversationColumns.SORT_TIMESTAMP + ")";
+
+ // Messages table schema
+ public static class MessageColumns implements BaseColumns {
+ /* conversation id that this message belongs to */
+ public static final String CONVERSATION_ID = "conversation_id";
+
+ /* participant which send this message */
+ public static final String SENDER_PARTICIPANT_ID = "sender_id";
+
+ /* This is bugle's internal status for the message */
+ public static final String STATUS = "message_status";
+
+ /* Type of message: SMS, MMS or MMS notification */
+ public static final String PROTOCOL = "message_protocol";
+
+ /* This is the time that the sender sent the message */
+ public static final String SENT_TIMESTAMP = "sent_timestamp";
+
+ /* Time that we received the message on this device */
+ public static final String RECEIVED_TIMESTAMP = "received_timestamp";
+
+ /* When the message has been seen by a user in a notification */
+ public static final String SEEN = "seen";
+
+ /* When the message has been read by a user */
+ public static final String READ = "read";
+
+ /* participant representing the sim which processed this message */
+ public static final String SELF_PARTICIPANT_ID = "self_id";
+
+ /*
+ * Time when a retry is initiated. This is used to compute the retry window
+ * when we retry sending/downloading a message.
+ */
+ public static final String RETRY_START_TIMESTAMP = "retry_start_timestamp";
+
+ // Columns which map to the SMS provider
+
+ /* Message ID from the platform provider */
+ public static final String SMS_MESSAGE_URI = "sms_message_uri";
+
+ /* The message priority for MMS message */
+ public static final String SMS_PRIORITY = "sms_priority";
+
+ /* The message size for MMS message */
+ public static final String SMS_MESSAGE_SIZE = "sms_message_size";
+
+ /* The subject for MMS message */
+ public static final String MMS_SUBJECT = "mms_subject";
+
+ /* Transaction id for MMS notificaiton */
+ public static final String MMS_TRANSACTION_ID = "mms_transaction_id";
+
+ /* Content location for MMS notificaiton */
+ public static final String MMS_CONTENT_LOCATION = "mms_content_location";
+
+ /* The expiry time (ms) for MMS message */
+ public static final String MMS_EXPIRY = "mms_expiry";
+
+ /* The detailed status (RESPONSE_STATUS or RETRIEVE_STATUS) for MMS message */
+ public static final String RAW_TELEPHONY_STATUS = "raw_status";
+ }
+
+ // Messages table SQL
+ private static final String CREATE_MESSAGES_TABLE_SQL =
+ "CREATE TABLE " + MESSAGES_TABLE + " ("
+ + MessageColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + MessageColumns.CONVERSATION_ID + " INT, "
+ + MessageColumns.SENDER_PARTICIPANT_ID + " INT, "
+ + MessageColumns.SENT_TIMESTAMP + " INT DEFAULT(0), "
+ + MessageColumns.RECEIVED_TIMESTAMP + " INT DEFAULT(0), "
+ + MessageColumns.PROTOCOL + " INT DEFAULT(0), "
+ + MessageColumns.STATUS + " INT DEFAULT(0), "
+ + MessageColumns.SEEN + " INT DEFAULT(0), "
+ + MessageColumns.READ + " INT DEFAULT(0), "
+ + MessageColumns.SMS_MESSAGE_URI + " TEXT, "
+ + MessageColumns.SMS_PRIORITY + " INT DEFAULT(0), "
+ + MessageColumns.SMS_MESSAGE_SIZE + " INT DEFAULT(0), "
+ + MessageColumns.MMS_SUBJECT + " TEXT, "
+ + MessageColumns.MMS_TRANSACTION_ID + " TEXT, "
+ + MessageColumns.MMS_CONTENT_LOCATION + " TEXT, "
+ + MessageColumns.MMS_EXPIRY + " INT DEFAULT(0), "
+ + MessageColumns.RAW_TELEPHONY_STATUS + " INT DEFAULT(0), "
+ + MessageColumns.SELF_PARTICIPANT_ID + " INT, "
+ + MessageColumns.RETRY_START_TIMESTAMP + " INT DEFAULT(0), "
+ + "FOREIGN KEY (" + MessageColumns.CONVERSATION_ID + ") REFERENCES "
+ + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE "
+ + "FOREIGN KEY (" + MessageColumns.SENDER_PARTICIPANT_ID + ") REFERENCES "
+ + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + ") ON DELETE SET NULL "
+ + "FOREIGN KEY (" + MessageColumns.SELF_PARTICIPANT_ID + ") REFERENCES "
+ + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + ") ON DELETE SET NULL "
+ + ");";
+
+ // Primary sort index for messages table : by conversation id, status, received timestamp.
+ private static final String MESSAGES_TABLE_SORT_INDEX_SQL =
+ "CREATE INDEX index_" + MESSAGES_TABLE + "_sort ON " + MESSAGES_TABLE + "("
+ + MessageColumns.CONVERSATION_ID + ", "
+ + MessageColumns.STATUS + ", "
+ + MessageColumns.RECEIVED_TIMESTAMP + ")";
+
+ private static final String MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL =
+ "CREATE INDEX index_" + MESSAGES_TABLE + "_status_seen ON " + MESSAGES_TABLE + "("
+ + MessageColumns.STATUS + ", "
+ + MessageColumns.SEEN + ")";
+
+ // Parts table schema
+ // A part may contain text or a media url, but not both.
+ public static class PartColumns implements BaseColumns {
+ /* message id that this part belongs to */
+ public static final String MESSAGE_ID = "message_id";
+
+ /* conversation id that this part belongs to */
+ public static final String CONVERSATION_ID = "conversation_id";
+
+ /* text for this part */
+ public static final String TEXT = "text";
+
+ /* content uri for this part */
+ public static final String CONTENT_URI = "uri";
+
+ /* content type for this part */
+ public static final String CONTENT_TYPE = "content_type";
+
+ /* cached width for this part (for layout while loading) */
+ public static final String WIDTH = "width";
+
+ /* cached height for this part (for layout while loading) */
+ public static final String HEIGHT = "height";
+
+ /* de-normalized copy of timestamp from the messages table. This is populated
+ * via an insert trigger on the parts table.
+ */
+ public static final String TIMESTAMP = "timestamp";
+ }
+
+ // Message part table SQL
+ private static final String CREATE_PARTS_TABLE_SQL =
+ "CREATE TABLE " + PARTS_TABLE + "("
+ + PartColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + PartColumns.MESSAGE_ID + " INT,"
+ + PartColumns.TEXT + " TEXT,"
+ + PartColumns.CONTENT_URI + " TEXT,"
+ + PartColumns.CONTENT_TYPE + " TEXT,"
+ + PartColumns.WIDTH + " INT DEFAULT("
+ + MessagingContentProvider.UNSPECIFIED_SIZE + "),"
+ + PartColumns.HEIGHT + " INT DEFAULT("
+ + MessagingContentProvider.UNSPECIFIED_SIZE + "),"
+ + PartColumns.TIMESTAMP + " INT, "
+ + PartColumns.CONVERSATION_ID + " INT NOT NULL,"
+ + "FOREIGN KEY (" + PartColumns.MESSAGE_ID + ") REFERENCES "
+ + MESSAGES_TABLE + "(" + MessageColumns._ID + ") ON DELETE CASCADE "
+ + "FOREIGN KEY (" + PartColumns.CONVERSATION_ID + ") REFERENCES "
+ + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE "
+ + ");";
+
+ public static final String CREATE_PARTS_TRIGGER_SQL =
+ "CREATE TRIGGER " + PARTS_TABLE + "_TRIGGER" + " AFTER INSERT ON " + PARTS_TABLE
+ + " FOR EACH ROW "
+ + " BEGIN UPDATE " + PARTS_TABLE
+ + " SET " + PartColumns.TIMESTAMP + "="
+ + " (SELECT received_timestamp FROM " + MESSAGES_TABLE + " WHERE " + MESSAGES_TABLE
+ + "." + MessageColumns._ID + "=" + "NEW." + PartColumns.MESSAGE_ID + ")"
+ + " WHERE " + PARTS_TABLE + "." + PartColumns._ID + "=" + "NEW." + PartColumns._ID
+ + "; END";
+
+ public static final String CREATE_MESSAGES_TRIGGER_SQL =
+ "CREATE TRIGGER " + MESSAGES_TABLE + "_TRIGGER" + " AFTER UPDATE OF "
+ + MessageColumns.RECEIVED_TIMESTAMP + " ON " + MESSAGES_TABLE
+ + " FOR EACH ROW BEGIN UPDATE " + PARTS_TABLE + " SET " + PartColumns.TIMESTAMP
+ + " = NEW." + MessageColumns.RECEIVED_TIMESTAMP + " WHERE " + PARTS_TABLE + "."
+ + PartColumns.MESSAGE_ID + " = NEW." + MessageColumns._ID
+ + "; END;";
+
+ // Primary sort index for parts table : by message_id
+ private static final String PARTS_TABLE_MESSAGE_INDEX_SQL =
+ "CREATE INDEX index_" + PARTS_TABLE + "_message_id ON " + PARTS_TABLE + "("
+ + PartColumns.MESSAGE_ID + ")";
+
+ // Participants table schema
+ public static class ParticipantColumns implements BaseColumns {
+ /* The subscription id for the sim associated with this self participant.
+ * Introduced in L. For earlier versions will always be default_sub_id (-1).
+ * For multi sim devices (or cases where the sim was changed) single device
+ * may have several different sub_id values */
+ public static final String SUB_ID = "sub_id";
+
+ /* The slot of the active SIM (inserted in the device) for this self-participant. If the
+ * self-participant doesn't correspond to any active SIM, this will be
+ * {@link android.telephony.SubscriptionManager#INVALID_SLOT_ID}.
+ * The column is ignored for all non-self participants.
+ */
+ public static final String SIM_SLOT_ID = "sim_slot_id";
+
+ /* The phone number stored in a standard E164 format if possible. This is unique for a
+ * given participant. We can't handle multiple participants with the same phone number
+ * since we don't know which of them a message comes from. This can also be an email
+ * address, in which case this is the same as the displayed address */
+ public static final String NORMALIZED_DESTINATION = "normalized_destination";
+
+ /* The phone number as originally supplied and used for dialing. Not necessarily in E164
+ * format or unique */
+ public static final String SEND_DESTINATION = "send_destination";
+
+ /* The user-friendly formatting of the phone number according to the region setting of
+ * the device when the row was added. */
+ public static final String DISPLAY_DESTINATION = "display_destination";
+
+ /* A string with this participant's full name or a pretty printed phone number */
+ public static final String FULL_NAME = "full_name";
+
+ /* A string with just this participant's first name */
+ public static final String FIRST_NAME = "first_name";
+
+ /* A local URI to an asset for the icon for this participant */
+ public static final String PROFILE_PHOTO_URI = "profile_photo_uri";
+
+ /* Contact id for matching local contact for this participant */
+ public static final String CONTACT_ID = "contact_id";
+
+ /* String that contains hints on how to find contact information in a contact lookup */
+ public static final String LOOKUP_KEY = "lookup_key";
+
+ /* If this participant is blocked */
+ public static final String BLOCKED = "blocked";
+
+ /* The color of the subscription (FOR SELF PARTICIPANTS ONLY) */
+ public static final String SUBSCRIPTION_COLOR = "subscription_color";
+
+ /* The name of the subscription (FOR SELF PARTICIPANTS ONLY) */
+ public static final String SUBSCRIPTION_NAME = "subscription_name";
+
+ /* The exact destination stored in Contacts for this participant */
+ public static final String CONTACT_DESTINATION = "contact_destination";
+ }
+
+ // Participants table SQL
+ private static final String CREATE_PARTICIPANTS_TABLE_SQL =
+ "CREATE TABLE " + PARTICIPANTS_TABLE + "("
+ + ParticipantColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + ParticipantColumns.SUB_ID + " INT DEFAULT("
+ + ParticipantData.OTHER_THAN_SELF_SUB_ID + "),"
+ + ParticipantColumns.SIM_SLOT_ID + " INT DEFAULT("
+ + ParticipantData.INVALID_SLOT_ID + "),"
+ + ParticipantColumns.NORMALIZED_DESTINATION + " TEXT,"
+ + ParticipantColumns.SEND_DESTINATION + " TEXT,"
+ + ParticipantColumns.DISPLAY_DESTINATION + " TEXT,"
+ + ParticipantColumns.FULL_NAME + " TEXT,"
+ + ParticipantColumns.FIRST_NAME + " TEXT,"
+ + ParticipantColumns.PROFILE_PHOTO_URI + " TEXT, "
+ + ParticipantColumns.CONTACT_ID + " INT DEFAULT( "
+ + ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED + "), "
+ + ParticipantColumns.LOOKUP_KEY + " STRING, "
+ + ParticipantColumns.BLOCKED + " INT DEFAULT(0), "
+ + ParticipantColumns.SUBSCRIPTION_NAME + " TEXT, "
+ + ParticipantColumns.SUBSCRIPTION_COLOR + " INT DEFAULT(0), "
+ + ParticipantColumns.CONTACT_DESTINATION + " TEXT, "
+ + "UNIQUE (" + ParticipantColumns.NORMALIZED_DESTINATION + ", "
+ + ParticipantColumns.SUB_ID + ") ON CONFLICT FAIL" + ");";
+
+ private static final String CREATE_SELF_PARTICIPANT_SQL =
+ "INSERT INTO " + PARTICIPANTS_TABLE
+ + " ( " + ParticipantColumns.SUB_ID + " ) VALUES ( %s )";
+
+ static String getCreateSelfParticipantSql(int subId) {
+ return String.format(CREATE_SELF_PARTICIPANT_SQL, subId);
+ }
+
+ // Conversation Participants table schema - contains a list of participants excluding the user
+ // in a given conversation.
+ public static class ConversationParticipantsColumns implements BaseColumns {
+ /* participant id of someone in this conversation */
+ public static final String PARTICIPANT_ID = "participant_id";
+
+ /* conversation id that this participant belongs to */
+ public static final String CONVERSATION_ID = "conversation_id";
+ }
+
+ // Conversation Participants table SQL
+ private static final String CREATE_CONVERSATION_PARTICIPANTS_TABLE_SQL =
+ "CREATE TABLE " + CONVERSATION_PARTICIPANTS_TABLE + "("
+ + ConversationParticipantsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + ConversationParticipantsColumns.CONVERSATION_ID + " INT,"
+ + ConversationParticipantsColumns.PARTICIPANT_ID + " INT,"
+ + "UNIQUE (" + ConversationParticipantsColumns.CONVERSATION_ID + ","
+ + ConversationParticipantsColumns.PARTICIPANT_ID + ") ON CONFLICT FAIL, "
+ + "FOREIGN KEY (" + ConversationParticipantsColumns.CONVERSATION_ID + ") "
+ + "REFERENCES " + CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ")"
+ + " ON DELETE CASCADE "
+ + "FOREIGN KEY (" + ConversationParticipantsColumns.PARTICIPANT_ID + ")"
+ + " REFERENCES " + PARTICIPANTS_TABLE + "(" + ParticipantColumns._ID + "));";
+
+ // Primary access pattern for conversation participants is to look them up for a specific
+ // conversation.
+ private static final String CONVERSATION_PARTICIPANTS_TABLE_CONVERSATION_ID_INDEX_SQL =
+ "CREATE INDEX index_" + CONVERSATION_PARTICIPANTS_TABLE + "_"
+ + ConversationParticipantsColumns.CONVERSATION_ID
+ + " ON " + CONVERSATION_PARTICIPANTS_TABLE
+ + "(" + ConversationParticipantsColumns.CONVERSATION_ID + ")";
+
+ // View for getting parts which are for draft messages.
+ static final String DRAFT_PARTS_VIEW_SQL = "CREATE VIEW " +
+ DRAFT_PARTS_VIEW + " AS SELECT "
+ + PARTS_TABLE + '.' + PartColumns._ID
+ + " as " + PartColumns._ID + ", "
+ + PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
+ + " as " + PartColumns.MESSAGE_ID + ", "
+ + PARTS_TABLE + '.' + PartColumns.TEXT
+ + " as " + PartColumns.TEXT + ", "
+ + PARTS_TABLE + '.' + PartColumns.CONTENT_URI
+ + " as " + PartColumns.CONTENT_URI + ", "
+ + PARTS_TABLE + '.' + PartColumns.CONTENT_TYPE
+ + " as " + PartColumns.CONTENT_TYPE + ", "
+ + PARTS_TABLE + '.' + PartColumns.WIDTH
+ + " as " + PartColumns.WIDTH + ", "
+ + PARTS_TABLE + '.' + PartColumns.HEIGHT
+ + " as " + PartColumns.HEIGHT + ", "
+ + MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
+ + " as " + MessageColumns.CONVERSATION_ID + " "
+ + " FROM " + MESSAGES_TABLE + " LEFT JOIN " + PARTS_TABLE + " ON ("
+ + MESSAGES_TABLE + "." + MessageColumns._ID
+ + "=" + PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ")"
+ // Exclude draft messages from main view
+ + " WHERE " + MESSAGES_TABLE + "." + MessageColumns.STATUS
+ + " = " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT;
+
+ // List of all our SQL tables
+ private static final String[] CREATE_TABLE_SQLS = new String[] {
+ CREATE_CONVERSATIONS_TABLE_SQL,
+ CREATE_MESSAGES_TABLE_SQL,
+ CREATE_PARTS_TABLE_SQL,
+ CREATE_PARTICIPANTS_TABLE_SQL,
+ CREATE_CONVERSATION_PARTICIPANTS_TABLE_SQL,
+ };
+
+ // List of all our indices
+ private static final String[] CREATE_INDEX_SQLS = new String[] {
+ CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL,
+ CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL,
+ CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL,
+ MESSAGES_TABLE_SORT_INDEX_SQL,
+ MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL,
+ PARTS_TABLE_MESSAGE_INDEX_SQL,
+ CONVERSATION_PARTICIPANTS_TABLE_CONVERSATION_ID_INDEX_SQL,
+ };
+
+ // List of all our SQL triggers
+ private static final String[] CREATE_TRIGGER_SQLS = new String[] {
+ CREATE_PARTS_TRIGGER_SQL,
+ CREATE_MESSAGES_TRIGGER_SQL,
+ };
+
+ // List of all our views
+ private static final String[] CREATE_VIEW_SQLS = new String[] {
+ ConversationListItemData.getConversationListViewSql(),
+ ConversationImagePartsView.getCreateSql(),
+ DRAFT_PARTS_VIEW_SQL,
+ };
+
+ private static final Object sLock = new Object();
+ private final Context mApplicationContext;
+ private static DatabaseHelper sHelperInstance; // Protected by sLock.
+
+ private final Object mDatabaseWrapperLock = new Object();
+ private DatabaseWrapper mDatabaseWrapper; // Protected by mDatabaseWrapperLock.
+ private final DatabaseUpgradeHelper mUpgradeHelper = new DatabaseUpgradeHelper();
+
+ /**
+ * Get a (singleton) instance of {@link DatabaseHelper}, creating one if there isn't one yet.
+ * This is the only public method for getting a new instance of the class.
+ * @param context Should be the application context (or something that will live for the
+ * lifetime of the application).
+ * @return The current (or a new) DatabaseHelper instance.
+ */
+ public static DatabaseHelper getInstance(final Context context) {
+ synchronized (sLock) {
+ if (sHelperInstance == null) {
+ sHelperInstance = new DatabaseHelper(context);
+ }
+ return sHelperInstance;
+ }
+ }
+
+ /**
+ * Private constructor, used from {@link #getInstance()}.
+ * @param context Should be the application context (or something that will live for the
+ * lifetime of the application).
+ */
+ private DatabaseHelper(final Context context) {
+ super(context, DATABASE_NAME, null, getDatabaseVersion(context), null);
+ mApplicationContext = context;
+ }
+
+ /**
+ * Test method that always instantiates a new DatabaseHelper instance. This should
+ * be used ONLY by the tests and never by the real application.
+ * @param context Test context.
+ * @return Brand new DatabaseHelper instance.
+ */
+ @VisibleForTesting
+ static DatabaseHelper getNewInstanceForTest(final Context context) {
+ Assert.isEngBuild();
+ Assert.isTrue(BugleApplication.isRunningTests());
+ return new DatabaseHelper(context);
+ }
+
+ /**
+ * Get the (singleton) instance of @{link DatabaseWrapper}.
+ * <p>The database is always opened as a writeable database.
+ * @return The current (or a new) DatabaseWrapper instance.
+ */
+ @DoesNotRunOnMainThread
+ DatabaseWrapper getDatabase() {
+ // We prevent the main UI thread from accessing the database here since we have to allow
+ // public access to this class to enable sub-packages to access data.
+ Assert.isNotMainThread();
+
+ synchronized (mDatabaseWrapperLock) {
+ if (mDatabaseWrapper == null) {
+ mDatabaseWrapper = new DatabaseWrapper(mApplicationContext, getWritableDatabase());
+ }
+ return mDatabaseWrapper;
+ }
+ }
+
+ @Override
+ public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ mUpgradeHelper.onDowngrade(db, oldVersion, newVersion);
+ }
+
+ /**
+ * Drops and recreates all tables.
+ */
+ public static void rebuildTables(final SQLiteDatabase db) {
+ // Drop tables first, then views, and indices.
+ dropAllTables(db);
+ dropAllViews(db);
+ dropAllIndexes(db);
+ dropAllTriggers(db);
+
+ // Recreate the whole database.
+ createDatabase(db);
+ }
+
+ /**
+ * Drop and rebuild a given view.
+ */
+ static void rebuildView(final SQLiteDatabase db, final String viewName,
+ final String createViewSql) {
+ dropView(db, viewName, true /* throwOnFailure */);
+ db.execSQL(createViewSql);
+ }
+
+ private static void dropView(final SQLiteDatabase db, final String viewName,
+ final boolean throwOnFailure) {
+ final String dropPrefix = "DROP VIEW IF EXISTS ";
+ try {
+ db.execSQL(dropPrefix + viewName);
+ } catch (final SQLException ex) {
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop view " + viewName + " "
+ + ex);
+ }
+
+ if (throwOnFailure) {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Drops all user-defined tables from the given database.
+ */
+ private static void dropAllTables(final SQLiteDatabase db) {
+ final Cursor tableCursor =
+ db.query(MASTER_TABLE, MASTER_COLUMNS, "type='table'", null, null, null, null);
+ if (tableCursor != null) {
+ try {
+ final String dropPrefix = "DROP TABLE IF EXISTS ";
+ while (tableCursor.moveToNext()) {
+ final String tableName = tableCursor.getString(0);
+
+ // Skip special tables
+ if (tableName.startsWith("android_") || tableName.startsWith("sqlite_")) {
+ continue;
+ }
+ try {
+ db.execSQL(dropPrefix + tableName);
+ } catch (final SQLException ex) {
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop table " + tableName + " "
+ + ex);
+ }
+ }
+ }
+ } finally {
+ tableCursor.close();
+ }
+ }
+ }
+
+ /**
+ * Drops all user-defined triggers from the given database.
+ */
+ private static void dropAllTriggers(final SQLiteDatabase db) {
+ final Cursor triggerCursor =
+ db.query(MASTER_TABLE, MASTER_COLUMNS, "type='trigger'", null, null, null, null);
+ if (triggerCursor != null) {
+ try {
+ final String dropPrefix = "DROP TRIGGER IF EXISTS ";
+ while (triggerCursor.moveToNext()) {
+ final String triggerName = triggerCursor.getString(0);
+
+ // Skip special tables
+ if (triggerName.startsWith("android_") || triggerName.startsWith("sqlite_")) {
+ continue;
+ }
+ try {
+ db.execSQL(dropPrefix + triggerName);
+ } catch (final SQLException ex) {
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop trigger " + triggerName +
+ " " + ex);
+ }
+ }
+ }
+ } finally {
+ triggerCursor.close();
+ }
+ }
+ }
+
+ /**
+ * Drops all user-defined views from the given database.
+ */
+ private static void dropAllViews(final SQLiteDatabase db) {
+ final Cursor viewCursor =
+ db.query(MASTER_TABLE, MASTER_COLUMNS, "type='view'", null, null, null, null);
+ if (viewCursor != null) {
+ try {
+ while (viewCursor.moveToNext()) {
+ final String viewName = viewCursor.getString(0);
+ dropView(db, viewName, false /* throwOnFailure */);
+ }
+ } finally {
+ viewCursor.close();
+ }
+ }
+ }
+
+ /**
+ * Drops all user-defined views from the given database.
+ */
+ private static void dropAllIndexes(final SQLiteDatabase db) {
+ final Cursor indexCursor =
+ db.query(MASTER_TABLE, MASTER_COLUMNS, "type='index'", null, null, null, null);
+ if (indexCursor != null) {
+ try {
+ final String dropPrefix = "DROP INDEX IF EXISTS ";
+ while (indexCursor.moveToNext()) {
+ final String indexName = indexCursor.getString(0);
+ try {
+ db.execSQL(dropPrefix + indexName);
+ } catch (final SQLException ex) {
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "unable to drop index " + indexName + " "
+ + ex);
+ }
+ }
+ }
+ } finally {
+ indexCursor.close();
+ }
+ }
+ }
+
+ private static void createDatabase(final SQLiteDatabase db) {
+ for (final String sql : CREATE_TABLE_SQLS) {
+ db.execSQL(sql);
+ }
+
+ for (final String sql : CREATE_INDEX_SQLS) {
+ db.execSQL(sql);
+ }
+
+ for (final String sql : CREATE_VIEW_SQLS) {
+ db.execSQL(sql);
+ }
+
+ for (final String sql : CREATE_TRIGGER_SQLS) {
+ db.execSQL(sql);
+ }
+
+ // Enable foreign key constraints
+ db.execSQL("PRAGMA foreign_keys=ON;");
+
+ // Add the default self participant. The default self will be assigned a proper slot id
+ // during participant refresh.
+ db.execSQL(getCreateSelfParticipantSql(ParticipantData.DEFAULT_SELF_SUB_ID));
+
+ DataModel.get().onCreateTables(db);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createDatabase(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ mUpgradeHelper.doOnUpgrade(db, oldVersion, newVersion);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java b/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
new file mode 100644
index 0000000..d112533
--- /dev/null
+++ b/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
@@ -0,0 +1,42 @@
+/*
+ * 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.datamodel;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+public class DatabaseUpgradeHelper {
+ private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
+
+ public void doOnUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ Assert.isTrue(newVersion >= oldVersion);
+ if (oldVersion == newVersion) {
+ return;
+ }
+
+ LogUtil.i(TAG, "Database upgrade started from version " + oldVersion + " to " + newVersion);
+
+ // Add future upgrade code here
+ }
+
+ public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ DatabaseHelper.rebuildTables(db);
+ LogUtil.e(TAG, "Database downgrade requested for version " +
+ oldVersion + " version " + newVersion + ", forcing db rebuild!");
+ }
+}
diff --git a/src/com/android/messaging/datamodel/DatabaseWrapper.java b/src/com/android/messaging/datamodel/DatabaseWrapper.java
new file mode 100644
index 0000000..ca7a331
--- /dev/null
+++ b/src/com/android/messaging/datamodel/DatabaseWrapper.java
@@ -0,0 +1,482 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteFullException;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
+import android.util.SparseArray;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+
+import java.util.Locale;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+public class DatabaseWrapper {
+ private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
+
+ private final SQLiteDatabase mDatabase;
+ private final Context mContext;
+ private final boolean mLog;
+ /**
+ * Set mExplainQueryPlanRegexp (via {@link BugleGservicesKeys#EXPLAIN_QUERY_PLAN_REGEXP}
+ * to regex matching queries to see query plans. For example, ".*" to show all query plans.
+ */
+ // See
+ private final String mExplainQueryPlanRegexp;
+ private static final int sTimingThreshold = 50; // in milliseconds
+
+ public static final int INDEX_INSERT_MESSAGE_PART = 0;
+ public static final int INDEX_INSERT_MESSAGE = 1;
+ public static final int INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE = 2;
+ public static final int INDEX_QUERY_MESSAGES_LATEST_MESSAGE = 3;
+
+ private final SparseArray<SQLiteStatement> mCompiledStatements;
+
+ static class TransactionData {
+ long time;
+ boolean transactionSuccessful;
+ }
+
+ // track transaction on a per thread basis
+ private static ThreadLocal<Stack<TransactionData>> sTransactionDepth =
+ new ThreadLocal<Stack<TransactionData>>() {
+ @Override
+ public Stack<TransactionData> initialValue() {
+ return new Stack<TransactionData>();
+ }
+ };
+
+ private static String[] sFormatStrings = new String[] {
+ "took %d ms to %s",
+ " took %d ms to %s",
+ " took %d ms to %s",
+ };
+
+ DatabaseWrapper(final Context context, final SQLiteDatabase db) {
+ mLog = LogUtil.isLoggable(LogUtil.BUGLE_DATABASE_PERF_TAG, LogUtil.VERBOSE);
+ mExplainQueryPlanRegexp = Factory.get().getBugleGservices().getString(
+ BugleGservicesKeys.EXPLAIN_QUERY_PLAN_REGEXP, null);
+ mDatabase = db;
+ mContext = context;
+ mCompiledStatements = new SparseArray<SQLiteStatement>();
+ }
+
+ public SQLiteStatement getStatementInTransaction(final int index, final String statement) {
+ // Use transaction to serialize access to statements
+ Assert.isTrue(mDatabase.inTransaction());
+ SQLiteStatement compiled = mCompiledStatements.get(index);
+ if (compiled == null) {
+ compiled = mDatabase.compileStatement(statement);
+ Assert.isTrue(compiled.toString().contains(statement.trim()));
+ mCompiledStatements.put(index, compiled);
+ }
+ return compiled;
+ }
+
+ private void maybePlayDebugNoise() {
+ DebugUtils.maybePlayDebugNoise(mContext, DebugUtils.DEBUG_SOUND_DB_OP);
+ }
+
+ private static void printTiming(final long t1, final String msg) {
+ final int transactionDepth = sTransactionDepth.get().size();
+ final long t2 = System.currentTimeMillis();
+ final long delta = t2 - t1;
+ if (delta > sTimingThreshold) {
+ LogUtil.v(LogUtil.BUGLE_DATABASE_PERF_TAG, String.format(Locale.US,
+ sFormatStrings[Math.min(sFormatStrings.length - 1, transactionDepth)],
+ delta,
+ msg));
+ }
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public void beginTransaction() {
+ final long t1 = System.currentTimeMillis();
+
+ // push the current time onto the transaction stack
+ final TransactionData f = new TransactionData();
+ f.time = t1;
+ sTransactionDepth.get().push(f);
+
+ mDatabase.beginTransaction();
+ }
+
+ public void setTransactionSuccessful() {
+ final TransactionData f = sTransactionDepth.get().peek();
+ f.transactionSuccessful = true;
+ mDatabase.setTransactionSuccessful();
+ }
+
+ public void endTransaction() {
+ long t1 = 0;
+ long transactionStartTime = 0;
+ final TransactionData f = sTransactionDepth.get().pop();
+ if (f.transactionSuccessful == false) {
+ LogUtil.w(TAG, "endTransaction without setting successful");
+ for (final StackTraceElement st : (new Exception()).getStackTrace()) {
+ LogUtil.w(TAG, " " + st.toString());
+ }
+ }
+ if (mLog) {
+ transactionStartTime = f.time;
+ t1 = System.currentTimeMillis();
+ }
+ try {
+ mDatabase.endTransaction();
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to endTransaction", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US,
+ ">>> endTransaction (total for this transaction: %d)",
+ (System.currentTimeMillis() - transactionStartTime)));
+ }
+ }
+
+ public void yieldTransaction() {
+ long yieldStartTime = 0;
+ if (mLog) {
+ yieldStartTime = System.currentTimeMillis();
+ }
+ final boolean wasYielded = mDatabase.yieldIfContendedSafely();
+ if (wasYielded && mLog) {
+ printTiming(yieldStartTime, "yieldTransaction");
+ }
+ }
+
+ public void insertWithOnConflict(final String searchTable, final String nullColumnHack,
+ final ContentValues initialValues, final int conflictAlgorithm) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ try {
+ mDatabase.insertWithOnConflict(searchTable, nullColumnHack, initialValues,
+ conflictAlgorithm);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to insertWithOnConflict", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US,
+ "insertWithOnConflict with ", searchTable));
+ }
+ }
+
+ private void explainQueryPlan(final SQLiteQueryBuilder qb, final SQLiteDatabase db,
+ final String[] projection, final String selection,
+ @SuppressWarnings("unused")
+ final String[] queryArgs,
+ final String groupBy,
+ @SuppressWarnings("unused")
+ final String having,
+ final String sortOrder, final String limit) {
+ final String queryString = qb.buildQuery(
+ projection,
+ selection,
+ groupBy,
+ null/*having*/,
+ sortOrder,
+ limit);
+ explainQueryPlan(db, queryString, queryArgs);
+ }
+
+ private void explainQueryPlan(final SQLiteDatabase db, final String sql,
+ final String[] queryArgs) {
+ if (!Pattern.matches(mExplainQueryPlanRegexp, sql)) {
+ return;
+ }
+ final Cursor planCursor = db.rawQuery("explain query plan " + sql, queryArgs);
+ try {
+ if (planCursor != null && planCursor.moveToFirst()) {
+ final int detailColumn = planCursor.getColumnIndex("detail");
+ final StringBuilder sb = new StringBuilder();
+ do {
+ sb.append(planCursor.getString(detailColumn));
+ sb.append("\n");
+ } while (planCursor.moveToNext());
+ if (sb.length() > 0) {
+ sb.setLength(sb.length() - 1);
+ }
+ LogUtil.v(TAG, "for query " + sql + "\nplan is: "
+ + sb.toString());
+ }
+ } catch (final Exception e) {
+ LogUtil.w(TAG, "Query plan failed ", e);
+ } finally {
+ if (planCursor != null) {
+ planCursor.close();
+ }
+ }
+ }
+
+ public Cursor query(final String searchTable, final String[] projection,
+ final String selection, final String[] selectionArgs, final String groupBy,
+ final String having, final String orderBy, final String limit) {
+ if (mExplainQueryPlanRegexp != null) {
+ final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(searchTable);
+ explainQueryPlan(qb, mDatabase, projection, selection, selectionArgs,
+ groupBy, having, orderBy, limit);
+ }
+
+ maybePlayDebugNoise();
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ final Cursor cursor = mDatabase.query(searchTable, projection, selection, selectionArgs,
+ groupBy, having, orderBy, limit);
+ if (mLog) {
+ printTiming(
+ t1,
+ String.format(Locale.US, "query %s with %s ==> %d",
+ searchTable, selection, cursor.getCount()));
+ }
+ return cursor;
+ }
+
+ public Cursor query(final String searchTable, final String[] columns,
+ final String selection, final String[] selectionArgs, final String groupBy,
+ final String having, final String orderBy) {
+ return query(
+ searchTable, columns, selection, selectionArgs,
+ groupBy, having, orderBy, null);
+ }
+
+ public Cursor query(final SQLiteQueryBuilder qb,
+ final String[] projection, final String selection, final String[] queryArgs,
+ final String groupBy, final String having, final String sortOrder, final String limit) {
+ if (mExplainQueryPlanRegexp != null) {
+ explainQueryPlan(qb, mDatabase, projection, selection, queryArgs,
+ groupBy, having, sortOrder, limit);
+ }
+ maybePlayDebugNoise();
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ final Cursor cursor = qb.query(mDatabase, projection, selection, queryArgs, groupBy,
+ having, sortOrder, limit);
+ if (mLog) {
+ printTiming(
+ t1,
+ String.format(Locale.US, "query %s with %s ==> %d",
+ qb.getTables(), selection, cursor.getCount()));
+ }
+ return cursor;
+ }
+
+ public long queryNumEntries(final String table, final String selection,
+ final String[] selectionArgs) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ final long retval =
+ DatabaseUtils.queryNumEntries(mDatabase, table, selection, selectionArgs);
+ if (mLog){
+ printTiming(
+ t1,
+ String.format(Locale.US, "queryNumEntries %s with %s ==> %d", table,
+ selection, retval));
+ }
+ return retval;
+ }
+
+ public Cursor rawQuery(final String sql, final String[] args) {
+ if (mExplainQueryPlanRegexp != null) {
+ explainQueryPlan(mDatabase, sql, args);
+ }
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ final Cursor cursor = mDatabase.rawQuery(sql, args);
+ if (mLog) {
+ printTiming(
+ t1,
+ String.format(Locale.US, "rawQuery %s ==> %d", sql, cursor.getCount()));
+ }
+ return cursor;
+ }
+
+ public int update(final String table, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ int count = 0;
+ try {
+ count = mDatabase.update(table, values, selection, selectionArgs);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to update", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US, "update %s with %s ==> %d",
+ table, selection, count));
+ }
+ return count;
+ }
+
+ public int delete(final String table, final String whereClause, final String[] whereArgs) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ int count = 0;
+ try {
+ count = mDatabase.delete(table, whereClause, whereArgs);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to delete", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1,
+ String.format(Locale.US, "delete from %s with %s ==> %d", table,
+ whereClause, count));
+ }
+ return count;
+ }
+
+ public long insert(final String table, final String nullColumnHack,
+ final ContentValues values) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ long rowId = -1;
+ try {
+ rowId = mDatabase.insert(table, nullColumnHack, values);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to insert", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US, "insert to %s", table));
+ }
+ return rowId;
+ }
+
+ public long replace(final String table, final String nullColumnHack,
+ final ContentValues values) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ long rowId = -1;
+ try {
+ rowId = mDatabase.replace(table, nullColumnHack, values);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to replace", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US, "replace to %s", table));
+ }
+ return rowId;
+ }
+
+ public void setLocale(final Locale locale) {
+ mDatabase.setLocale(locale);
+ }
+
+ public void execSQL(final String sql, final String[] bindArgs) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ try {
+ mDatabase.execSQL(sql, bindArgs);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to execSQL", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US, "execSQL %s", sql));
+ }
+ }
+
+ public void execSQL(final String sql) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ try {
+ mDatabase.execSQL(sql);
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to execSQL", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US, "execSQL %s", sql));
+ }
+ }
+
+ public int execSQLUpdateDelete(final String sql) {
+ long t1 = 0;
+ if (mLog) {
+ t1 = System.currentTimeMillis();
+ }
+ maybePlayDebugNoise();
+ final SQLiteStatement statement = mDatabase.compileStatement(sql);
+ int rowsUpdated = 0;
+ try {
+ rowsUpdated = statement.executeUpdateDelete();
+ } catch (SQLiteFullException ex) {
+ LogUtil.e(TAG, "Database full, unable to execSQLUpdateDelete", ex);
+ UiUtils.showToastAtBottom(R.string.db_full);
+ }
+ if (mLog) {
+ printTiming(t1, String.format(Locale.US, "execSQLUpdateDelete %s", sql));
+ }
+ return rowsUpdated;
+ }
+
+ public SQLiteDatabase getDatabase() {
+ return mDatabase;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/FileProvider.java b/src/com/android/messaging/datamodel/FileProvider.java
new file mode 100644
index 0000000..ee332cd
--- /dev/null
+++ b/src/com/android/messaging/datamodel/FileProvider.java
@@ -0,0 +1,151 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * A very simple content provider that can serve files.
+ */
+public abstract class FileProvider extends ContentProvider {
+ // Object to generate random id for temp images.
+ private static final Random RANDOM_ID = new Random();
+
+ abstract File getFile(final String path, final String extension);
+
+ private static final String FILE_EXTENSION_PARAM_KEY = "ext";
+
+ /**
+ * Check if filename conforms to requirement for our provider
+ * @param fileId filename (optionally starting with path character
+ * @return true if filename consists only of digits
+ */
+ protected static boolean isValidFileId(final String fileId) {
+ // Ignore initial "/"
+ for (int index = (fileId.startsWith("/") ? 1 : 0); index < fileId.length(); index++) {
+ final Character c = fileId.charAt(index);
+ if (!Character.isDigit(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Create a temp file (to allow writing to that one particular file)
+ * @param file the file to create
+ * @return true if file successfully created
+ */
+ protected static boolean ensureFileExists(final File file) {
+ try {
+ final File parentDir = file.getParentFile();
+ if (parentDir.exists() || parentDir.mkdirs()) {
+ return file.createNewFile();
+ }
+ } catch (final IOException e) {
+ // fail on exceptions creating the file
+ }
+ return false;
+ }
+
+ /**
+ * Build uri for a new temporary file (creating file)
+ * @param authority authority with which to populate uri
+ * @param extension optional file extension
+ * @return unique uri that can be used to write temporary files
+ */
+ protected static Uri buildFileUri(final String authority, final String extension) {
+ final long fileId = Math.abs(RANDOM_ID.nextLong());
+ final Uri.Builder builder = (new Uri.Builder()).authority(authority).scheme(
+ ContentResolver.SCHEME_CONTENT);
+ builder.appendPath(String.valueOf(fileId));
+ if (!TextUtils.isEmpty(extension)) {
+ builder.appendQueryParameter(FILE_EXTENSION_PARAM_KEY, extension);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
+ final String fileId = uri.getPath();
+ if (isValidFileId(fileId)) {
+ final File file = getFile(fileId, getExtensionFromUri(uri));
+ return file.delete() ? 1 : 0;
+ }
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(final Uri uri, final String fileMode)
+ throws FileNotFoundException {
+ final String fileId = uri.getPath();
+ if (isValidFileId(fileId)) {
+ final File file = getFile(fileId, getExtensionFromUri(uri));
+ final int mode =
+ (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
+ return ParcelFileDescriptor.open(file, mode);
+ }
+ return null;
+ }
+
+ protected static String getExtensionFromUri(final Uri uri) {
+ return uri.getQueryParameter(FILE_EXTENSION_PARAM_KEY);
+ }
+
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ // Don't support queries.
+ return null;
+ }
+
+ @Override
+ public Uri insert(final Uri uri, final ContentValues values) {
+ // Don't support inserts.
+ return null;
+ }
+
+ @Override
+ public int update(final Uri uri, final ContentValues values, final String selection,
+ final String[] selectionArgs) {
+ // Don't support updates.
+ return 0;
+ }
+
+ @Override
+ public String getType(final Uri uri) {
+ // No need for mime types.
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java b/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java
new file mode 100644
index 0000000..62483a0
--- /dev/null
+++ b/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java
@@ -0,0 +1,179 @@
+/*
+ * 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.datamodel;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.v4.util.SimpleArrayMap;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * A cursor builder that takes the frequent contacts cursor and aggregate it with the all contacts
+ * cursor to fill in contact details such as phone numbers and strip away invalid contacts.
+ *
+ * Because the frequent contact list depends on the loading of two cursors, it needs to temporarily
+ * store the cursor that it receives with setFrequents() and setAllContacts() calls. Because it
+ * doesn't know which one will be finished first, it always checks whether both cursors are ready
+ * to pull data from and construct the aggregate cursor when it's ready to do so. Note that
+ * this cursor builder doesn't assume ownership of the cursors passed in - it merely references
+ * them and always does a isClosed() check before consuming them. The ownership still belongs to
+ * the loader framework and the cursor may be closed when the UI is torn down.
+ */
+public class FrequentContactsCursorBuilder {
+ private Cursor mAllContactsCursor;
+ private Cursor mFrequentContactsCursor;
+
+ /**
+ * Sets the frequent contacts cursor as soon as it is loaded, or null if it's reset.
+ * @return this builder instance for chained operations
+ */
+ public FrequentContactsCursorBuilder setFrequents(final Cursor frequentContactsCursor) {
+ mFrequentContactsCursor = frequentContactsCursor;
+ return this;
+ }
+
+ /**
+ * Sets the all contacts cursor as soon as it is loaded, or null if it's reset.
+ * @return this builder instance for chained operations
+ */
+ public FrequentContactsCursorBuilder setAllContacts(final Cursor allContactsCursor) {
+ mAllContactsCursor = allContactsCursor;
+ return this;
+ }
+
+ /**
+ * Reset this builder. Must be called when the consumer resets its data.
+ */
+ public void resetBuilder() {
+ mAllContactsCursor = null;
+ mFrequentContactsCursor = null;
+ }
+
+ /**
+ * Attempt to build the cursor records from the frequent and all contacts cursor if they
+ * are both ready to be consumed.
+ * @return the frequent contact cursor if built successfully, or null if it can't be built yet.
+ */
+ public Cursor build() {
+ if (mFrequentContactsCursor != null && mAllContactsCursor != null) {
+ Assert.isTrue(!mFrequentContactsCursor.isClosed());
+ Assert.isTrue(!mAllContactsCursor.isClosed());
+
+ // Frequent contacts cursor has one record per contact, plus it doesn't contain info
+ // such as phone number and type. In order for the records to be usable by Bugle, we
+ // would like to populate it with information from the all contacts cursor.
+ final MatrixCursor retCursor = new MatrixCursor(ContactUtil.PhoneQuery.PROJECTION);
+
+ // First, go through the frequents cursor and take note of all lookup keys and their
+ // corresponding rank in the frequents list.
+ final SimpleArrayMap<String, Integer> lookupKeyToRankMap =
+ new SimpleArrayMap<String, Integer>();
+ int oldPosition = mFrequentContactsCursor.getPosition();
+ int rank = 0;
+ mFrequentContactsCursor.moveToPosition(-1);
+ while (mFrequentContactsCursor.moveToNext()) {
+ final String lookupKey = mFrequentContactsCursor.getString(
+ ContactUtil.INDEX_LOOKUP_KEY_FREQUENT);
+ lookupKeyToRankMap.put(lookupKey, rank++);
+ }
+ mFrequentContactsCursor.moveToPosition(oldPosition);
+
+ // Second, go through the all contacts cursor once and retrieve all information
+ // (multiple phone numbers etc.) and store that in an array list. Since the all
+ // contacts list only contains phone contacts, this step will ensure that we filter
+ // out any invalid/email contacts in the frequents list.
+ final ArrayList<Object[]> rows =
+ new ArrayList<Object[]>(mFrequentContactsCursor.getCount());
+ oldPosition = mAllContactsCursor.getPosition();
+ mAllContactsCursor.moveToPosition(-1);
+ while (mAllContactsCursor.moveToNext()) {
+ final String lookupKey = mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
+ if (lookupKeyToRankMap.containsKey(lookupKey)) {
+ final Object[] row = new Object[ContactUtil.PhoneQuery.PROJECTION.length];
+ row[ContactUtil.INDEX_DATA_ID] =
+ mAllContactsCursor.getLong(ContactUtil.INDEX_DATA_ID);
+ row[ContactUtil.INDEX_CONTACT_ID] =
+ mAllContactsCursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ row[ContactUtil.INDEX_LOOKUP_KEY] =
+ mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
+ row[ContactUtil.INDEX_DISPLAY_NAME] =
+ mAllContactsCursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
+ row[ContactUtil.INDEX_PHOTO_URI] =
+ mAllContactsCursor.getString(ContactUtil.INDEX_PHOTO_URI);
+ row[ContactUtil.INDEX_PHONE_EMAIL] =
+ mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL);
+ row[ContactUtil.INDEX_PHONE_EMAIL_TYPE] =
+ mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
+ row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] =
+ mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
+ rows.add(row);
+ }
+ }
+ mAllContactsCursor.moveToPosition(oldPosition);
+
+ // Now we have a list of rows containing frequent contacts in alphabetical order.
+ // Therefore, sort all the rows according to their actual ranks in the frequents list.
+ Collections.sort(rows, new Comparator<Object[]>() {
+ @Override
+ public int compare(final Object[] lhs, final Object[] rhs) {
+ final String lookupKeyLhs = (String) lhs[ContactUtil.INDEX_LOOKUP_KEY];
+ final String lookupKeyRhs = (String) rhs[ContactUtil.INDEX_LOOKUP_KEY];
+ Assert.isTrue(lookupKeyToRankMap.containsKey(lookupKeyLhs) &&
+ lookupKeyToRankMap.containsKey(lookupKeyRhs));
+ final int rankLhs = lookupKeyToRankMap.get(lookupKeyLhs);
+ final int rankRhs = lookupKeyToRankMap.get(lookupKeyRhs);
+ if (rankLhs < rankRhs) {
+ return -1;
+ } else if (rankLhs > rankRhs) {
+ return 1;
+ } else {
+ // Same rank, so it's two contact records for the same contact.
+ // Perform secondary sorting on the phone type. Always place
+ // mobile before everything else.
+ final int phoneTypeLhs = (int) lhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE];
+ final int phoneTypeRhs = (int) rhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE];
+ if (phoneTypeLhs == Phone.TYPE_MOBILE &&
+ phoneTypeRhs == Phone.TYPE_MOBILE) {
+ return 0;
+ } else if (phoneTypeLhs == Phone.TYPE_MOBILE) {
+ return -1;
+ } else if (phoneTypeRhs == Phone.TYPE_MOBILE) {
+ return 1;
+ } else {
+ // Use the default sort order, i.e. sort by phoneType value.
+ return phoneTypeLhs < phoneTypeRhs ? -1 :
+ (phoneTypeLhs == phoneTypeRhs ? 0 : 1);
+ }
+ }
+ }
+ });
+
+ // Finally, add all the rows to this cursor.
+ for (final Object[] row : rows) {
+ retCursor.addRow(row);
+ }
+ return retCursor;
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java b/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java
new file mode 100644
index 0000000..d1759ad
--- /dev/null
+++ b/src/com/android/messaging/datamodel/FrequentContactsCursorQueryData.java
@@ -0,0 +1,114 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+
+import com.android.messaging.util.FallbackStrategies;
+import com.android.messaging.util.FallbackStrategies.Strategy;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Helper for querying frequent (and/or starred) contacts.
+ */
+public class FrequentContactsCursorQueryData extends CursorQueryData {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static class FrequentContactsCursorLoader extends BoundCursorLoader {
+ private final Uri mOriginalUri;
+
+ FrequentContactsCursorLoader(String bindingId, Context context, Uri uri,
+ String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ super(bindingId, context, uri, projection, selection, selectionArgs, sortOrder);
+ mOriginalUri = uri;
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ return FallbackStrategies
+ .startWith(new PrimaryStrequentContactsQueryStrategy())
+ .thenTry(new FrequentOnlyContactsQueryStrategy())
+ .thenTry(new PhoneOnlyStrequentContactsQueryStrategy())
+ .execute(null);
+ }
+
+ private abstract class StrequentContactsQueryStrategy implements Strategy<Void, Cursor> {
+ @Override
+ public Cursor execute(Void params) throws Exception {
+ final Uri uri = getUri();
+ if (uri != null) {
+ setUri(uri);
+ }
+ return FrequentContactsCursorLoader.super.loadInBackground();
+ }
+ protected abstract Uri getUri();
+ }
+
+ private class PrimaryStrequentContactsQueryStrategy extends StrequentContactsQueryStrategy {
+ @Override
+ protected Uri getUri() {
+ // Use the original URI requested.
+ return mOriginalUri;
+ }
+ }
+
+ private class FrequentOnlyContactsQueryStrategy extends StrequentContactsQueryStrategy {
+ @Override
+ protected Uri getUri() {
+ // Some phones have a buggy implementation of the Contacts provider which crashes
+ // when we query for strequent (starred+frequent) contacts (b/17991485).
+ // If this happens, switch to just querying for frequent contacts.
+ return Contacts.CONTENT_FREQUENT_URI;
+ }
+ }
+
+ private class PhoneOnlyStrequentContactsQueryStrategy extends
+ StrequentContactsQueryStrategy {
+ @Override
+ protected Uri getUri() {
+ // Some 3rd party ROMs have content provider
+ // implementation where invalid SQL queries are returned for regular strequent
+ // queries. Using strequent_phone_only query as a fallback to display only phone
+ // contacts. This is the last-ditch effort; if this fails, we will display an
+ // empty frequent list (b/18354836).
+ final String strequentQueryParam = OsUtil.isAtLeastL() ?
+ ContactsContract.STREQUENT_PHONE_ONLY : "strequent_phone_only";
+ // TODO: Handle enterprise contacts post M once contacts provider supports it
+ return Contacts.CONTENT_STREQUENT_URI.buildUpon()
+ .appendQueryParameter(strequentQueryParam, "true").build();
+ }
+ }
+ }
+
+ public FrequentContactsCursorQueryData(Context context, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ // TODO: Handle enterprise contacts post M once contacts provider supports it
+ super(context, Contacts.CONTENT_STREQUENT_URI, projection, selection, selectionArgs,
+ sortOrder);
+ }
+
+ @Override
+ public BoundCursorLoader createBoundCursorLoader(String bindingId) {
+ return new FrequentContactsCursorLoader(bindingId, mContext, mUri, mProjection, mSelection,
+ mSelectionArgs, mSortOrder);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
new file mode 100644
index 0000000..28ec303
--- /dev/null
+++ b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
@@ -0,0 +1,49 @@
+/*
+ * 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.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.GalleryGridItemData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.google.common.base.Joiner;
+
+/**
+ * A BoundCursorLoader that reads local media on the device.
+ */
+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 IMAGE_SELECTION = createSelection(
+ MessagePartData.ACCEPTABLE_IMAGE_TYPES,
+ new Integer[] { FileColumns.MEDIA_TYPE_IMAGE });
+
+ public GalleryBoundCursorLoader(final String bindingId, final Context context) {
+ super(bindingId, context, STORAGE_URI, GalleryGridItemData.IMAGE_PROJECTION,
+ IMAGE_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/MediaScratchFileProvider.java b/src/com/android/messaging/datamodel/MediaScratchFileProvider.java
new file mode 100644
index 0000000..29ae4f4
--- /dev/null
+++ b/src/com/android/messaging/datamodel/MediaScratchFileProvider.java
@@ -0,0 +1,132 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+import android.support.v4.util.SimpleArrayMap;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A very simple content provider that can serve media files from our cache directory.
+ */
+public class MediaScratchFileProvider extends FileProvider {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap =
+ new SimpleArrayMap<Uri, String>();
+
+ @VisibleForTesting
+ public static final String AUTHORITY =
+ "com.android.messaging.datamodel.MediaScratchFileProvider";
+ private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
+
+ public static boolean isMediaScratchSpaceUri(final Uri uri) {
+ if (uri == null) {
+ return false;
+ }
+
+ final List<String> segments = uri.getPathSegments();
+ return (TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_CONTENT) &&
+ TextUtils.equals(uri.getAuthority(), AUTHORITY) &&
+ segments.size() == 1 && FileProvider.isValidFileId(segments.get(0)));
+ }
+
+ /**
+ * Returns a uri that can be used to access a raw mms file.
+ *
+ * @return the URI for an raw mms file
+ */
+ public static Uri buildMediaScratchSpaceUri(final String extension) {
+ final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension);
+ final File file = getFileWithExtension(uri.getPath(), extension);
+ if (!ensureFileExists(file)) {
+ LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
+ }
+ return uri;
+ }
+
+ public static File getFileFromUri(final Uri uri) {
+ Assert.equals(AUTHORITY, uri.getAuthority());
+ return getFileWithExtension(uri.getPath(), getExtensionFromUri(uri));
+ }
+
+ public static Uri.Builder getUriBuilder() {
+ return (new Uri.Builder()).authority(AUTHORITY).scheme(ContentResolver.SCHEME_CONTENT);
+ }
+
+ @Override
+ File getFile(final String path, final String extension) {
+ return getFileWithExtension(path, extension);
+ }
+
+ private static File getFileWithExtension(final String path, final String extension) {
+ final Context context = Factory.get().getApplicationContext();
+ return new File(getDirectory(context),
+ TextUtils.isEmpty(extension) ? path : path + "." + extension);
+ }
+
+ private static File getDirectory(final Context context) {
+ return new File(context.getCacheDir(), MEDIA_SCRATCH_SPACE_DIR);
+ }
+
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (projection != null && projection.length > 0 &&
+ TextUtils.equals(projection[0], OpenableColumns.DISPLAY_NAME) &&
+ isMediaScratchSpaceUri(uri)) {
+ // Retrieve the display name associated with a temp file. This is used by the Contacts
+ // ImportVCardActivity to retrieve the name of the contact(s) being imported.
+ String displayName;
+ synchronized (sUriToDisplayNameMap) {
+ displayName = sUriToDisplayNameMap.get(uri);
+ }
+ if (!TextUtils.isEmpty(displayName)) {
+ MatrixCursor cursor =
+ new MatrixCursor(new String[] { OpenableColumns.DISPLAY_NAME });
+ RowBuilder row = cursor.newRow();
+ row.add(displayName);
+ return cursor;
+ }
+ }
+ return null;
+ }
+
+ public static void addUriToDisplayNameEntry(final Uri scratchFileUri,
+ final String displayName) {
+ if (TextUtils.isEmpty(displayName)) {
+ return;
+ }
+ synchronized (sUriToDisplayNameMap) {
+ sUriToDisplayNameMap.put(scratchFileUri, displayName);
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/MemoryCacheManager.java b/src/com/android/messaging/datamodel/MemoryCacheManager.java
new file mode 100644
index 0000000..0968cff
--- /dev/null
+++ b/src/com/android/messaging/datamodel/MemoryCacheManager.java
@@ -0,0 +1,75 @@
+/*
+ * 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.datamodel;
+
+import com.android.messaging.Factory;
+
+import java.util.HashSet;
+
+/**
+ * Utility abstraction which allows MemoryCaches in an application to register and then when there
+ * is memory pressure provide a callback to reclaim the memory in the caches.
+ */
+public class MemoryCacheManager {
+ private final HashSet<MemoryCache> mMemoryCaches = new HashSet<MemoryCache>();
+ private final Object mMemoryCacheLock = new Object();
+
+ public static MemoryCacheManager get() {
+ return Factory.get().getMemoryCacheManager();
+ }
+
+ /**
+ * Extend this interface to provide a reclaim method on a memory cache.
+ */
+ public interface MemoryCache {
+ void reclaim();
+ }
+
+ /**
+ * Register the memory cache with the application.
+ */
+ public void registerMemoryCache(final MemoryCache cache) {
+ synchronized (mMemoryCacheLock) {
+ mMemoryCaches.add(cache);
+ }
+ }
+
+ /**
+ * Unregister the memory cache with the application.
+ */
+ public void unregisterMemoryCache(final MemoryCache cache) {
+ synchronized (mMemoryCacheLock) {
+ mMemoryCaches.remove(cache);
+ }
+ }
+
+ /**
+ * Reclaim memory in all the memory caches in the application.
+ */
+ @SuppressWarnings("unchecked")
+ public void reclaimMemory() {
+ // We're creating a cache copy in the lock to ensure we're not working on a concurrently
+ // modified set, then reclaim outside of the lock to minimize the time within the lock.
+ final HashSet<MemoryCache> shallowCopy;
+ synchronized (mMemoryCacheLock) {
+ shallowCopy = (HashSet<MemoryCache>) mMemoryCaches.clone();
+ }
+ for (final MemoryCache cache : shallowCopy) {
+ cache.reclaim();
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/MessageNotificationState.java b/src/com/android/messaging/datamodel/MessageNotificationState.java
new file mode 100644
index 0000000..0bd4aaa
--- /dev/null
+++ b/src/com/android/messaging/datamodel/MessageNotificationState.java
@@ -0,0 +1,1342 @@
+/*
+ * 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.datamodel;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationCompat.Builder;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+import android.support.v4.app.NotificationManagerCompat;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.URLSpan;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.datamodel.data.ConversationParticipantsData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.media.VideoThumbnailRequest;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ConversationIdSet;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PendingIntentConstants;
+import com.android.messaging.util.UriUtil;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Notification building class for conversation messages.
+ *
+ * Message Notifications are built in several stages with several utility classes.
+ * 1) Perform a database query and fill a data structure with information on messages and
+ * conversations which need to be notified.
+ * 2) Based on the data structure choose an appropriate NotificationState subclass to
+ * represent all the notifications.
+ * -- For one or more messages in one conversation: MultiMessageNotificationState.
+ * -- For multiple messages in multiple conversations: MultiConversationNotificationState
+ *
+ * A three level structure is used to coalesce the data from the database. From bottom to top:
+ * 1) NotificationLineInfo - A single message that needs to be notified.
+ * 2) ConversationLineInfo - A list of NotificationLineInfo in a single conversation.
+ * 3) ConversationInfoList - A list of ConversationLineInfo and the total number of messages.
+ *
+ * The createConversationInfoList function performs the query and creates the data structure.
+ */
+public abstract class MessageNotificationState extends NotificationState {
+ // Logging
+ static final String TAG = LogUtil.BUGLE_NOTIFICATIONS_TAG;
+ private static final int MAX_MESSAGES_IN_WEARABLE_PAGE = 20;
+
+ private static final int MAX_CHARACTERS_IN_GROUP_NAME = 30;
+
+ private static final int REPLY_INTENT_REQUEST_CODE_OFFSET = 0;
+ private static final int NUM_EXTRA_REQUEST_CODES_NEEDED = 1;
+ protected String mTickerSender = null;
+ protected CharSequence mTickerText = null;
+ protected String mTitle = null;
+ protected CharSequence mContent = null;
+ protected Uri mAttachmentUri = null;
+ protected String mAttachmentType = null;
+ protected boolean mTickerNoContent;
+
+ @Override
+ protected Uri getAttachmentUri() {
+ return mAttachmentUri;
+ }
+
+ @Override
+ protected String getAttachmentType() {
+ return mAttachmentType;
+ }
+
+ @Override
+ public int getIcon() {
+ return R.drawable.ic_sms_light;
+ }
+
+ @Override
+ public int getPriority() {
+ // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker
+ // isn't displayed.
+ return Notification.PRIORITY_HIGH;
+ }
+
+ /**
+ * Base class for single notification events for messages. Multiple of these
+ * may be grouped into a single conversation.
+ */
+ static class NotificationLineInfo {
+
+ final int mNotificationType;
+
+ NotificationLineInfo() {
+ mNotificationType = BugleNotifications.LOCAL_SMS_NOTIFICATION;
+ }
+
+ NotificationLineInfo(final int notificationType) {
+ mNotificationType = notificationType;
+ }
+ }
+
+ /**
+ * Information on a single chat message which should be shown in a notification.
+ */
+ static class MessageLineInfo extends NotificationLineInfo {
+ final CharSequence mText;
+ Uri mAttachmentUri;
+ String mAttachmentType;
+ final String mAuthorFullName;
+ final String mAuthorFirstName;
+ boolean mIsManualDownloadNeeded;
+ final String mMessageId;
+
+ MessageLineInfo(final boolean isGroup, final String authorFullName,
+ final String authorFirstName, final CharSequence text, final Uri attachmentUrl,
+ final String attachmentType, final boolean isManualDownloadNeeded,
+ final String messageId) {
+ super(BugleNotifications.LOCAL_SMS_NOTIFICATION);
+ mAuthorFullName = authorFullName;
+ mAuthorFirstName = authorFirstName;
+ mText = text;
+ mAttachmentUri = attachmentUrl;
+ mAttachmentType = attachmentType;
+ mIsManualDownloadNeeded = isManualDownloadNeeded;
+ mMessageId = messageId;
+ }
+ }
+
+ /**
+ * Information on all the notification messages within a single conversation.
+ */
+ static class ConversationLineInfo {
+ // Conversation id of the latest message in the notification for this merged conversation.
+ final String mConversationId;
+
+ // True if this represents a group conversation.
+ final boolean mIsGroup;
+
+ // Name of the group conversation if available.
+ final String mGroupConversationName;
+
+ // True if this conversation's recipients includes one or more email address(es)
+ // (see ConversationColumns.INCLUDE_EMAIL_ADDRESS)
+ final boolean mIncludeEmailAddress;
+
+ // Timestamp of the latest message
+ final long mReceivedTimestamp;
+
+ // Self participant id.
+ final String mSelfParticipantId;
+
+ // List of individual line notifications to be parsed later.
+ final List<NotificationLineInfo> mLineInfos;
+
+ // Total number of messages. Might be different that mLineInfos.size() as the number of
+ // line infos is capped.
+ int mTotalMessageCount;
+
+ // Custom ringtone if set
+ final String mRingtoneUri;
+
+ // Should notification be enabled for this conversation?
+ final boolean mNotificationEnabled;
+
+ // Should notifications vibrate for this conversation?
+ final boolean mNotificationVibrate;
+
+ // Avatar uri of sender
+ final Uri mAvatarUri;
+
+ // Contact uri of sender
+ final Uri mContactUri;
+
+ // Subscription id.
+ final int mSubId;
+
+ // Number of participants
+ final int mParticipantCount;
+
+ public ConversationLineInfo(final String conversationId,
+ final boolean isGroup,
+ final String groupConversationName,
+ final boolean includeEmailAddress,
+ final long receivedTimestamp,
+ final String selfParticipantId,
+ final String ringtoneUri,
+ final boolean notificationEnabled,
+ final boolean notificationVibrate,
+ final Uri avatarUri,
+ final Uri contactUri,
+ final int subId,
+ final int participantCount) {
+ mConversationId = conversationId;
+ mIsGroup = isGroup;
+ mGroupConversationName = groupConversationName;
+ mIncludeEmailAddress = includeEmailAddress;
+ mReceivedTimestamp = receivedTimestamp;
+ mSelfParticipantId = selfParticipantId;
+ mLineInfos = new ArrayList<NotificationLineInfo>();
+ mTotalMessageCount = 0;
+ mRingtoneUri = ringtoneUri;
+ mAvatarUri = avatarUri;
+ mContactUri = contactUri;
+ mNotificationEnabled = notificationEnabled;
+ mNotificationVibrate = notificationVibrate;
+ mSubId = subId;
+ mParticipantCount = participantCount;
+ }
+
+ public int getLatestMessageNotificationType() {
+ final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
+ if (messageLineInfo == null) {
+ return BugleNotifications.LOCAL_SMS_NOTIFICATION;
+ }
+ return messageLineInfo.mNotificationType;
+ }
+
+ public String getLatestMessageId() {
+ final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
+ if (messageLineInfo == null) {
+ return null;
+ }
+ return messageLineInfo.mMessageId;
+ }
+
+ public boolean getDoesLatestMessageNeedDownload() {
+ final MessageLineInfo messageLineInfo = getLatestMessageLineInfo();
+ if (messageLineInfo == null) {
+ return false;
+ }
+ return messageLineInfo.mIsManualDownloadNeeded;
+ }
+
+ private MessageLineInfo getLatestMessageLineInfo() {
+ // The latest message is stored at index zero of the message line infos.
+ if (mLineInfos.size() > 0 && mLineInfos.get(0) instanceof MessageLineInfo) {
+ return (MessageLineInfo) mLineInfos.get(0);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Information on all the notification messages across all conversations.
+ */
+ public static class ConversationInfoList {
+ final int mMessageCount;
+ final List<ConversationLineInfo> mConvInfos;
+ public ConversationInfoList(final int count, final List<ConversationLineInfo> infos) {
+ mMessageCount = count;
+ mConvInfos = infos;
+ }
+ }
+
+ final ConversationInfoList mConvList;
+ private long mLatestReceivedTimestamp;
+
+ private static ConversationIdSet makeConversationIdSet(final ConversationInfoList convList) {
+ ConversationIdSet set = null;
+ if (convList != null && convList.mConvInfos != null && convList.mConvInfos.size() > 0) {
+ set = new ConversationIdSet();
+ for (final ConversationLineInfo info : convList.mConvInfos) {
+ set.add(info.mConversationId);
+ }
+ }
+ return set;
+ }
+
+ protected MessageNotificationState(final ConversationInfoList convList) {
+ super(makeConversationIdSet(convList));
+ mConvList = convList;
+ mType = PendingIntentConstants.SMS_NOTIFICATION_ID;
+ mLatestReceivedTimestamp = Long.MIN_VALUE;
+ if (convList != null) {
+ for (final ConversationLineInfo info : convList.mConvInfos) {
+ mLatestReceivedTimestamp = Math.max(mLatestReceivedTimestamp,
+ info.mReceivedTimestamp);
+ }
+ }
+ }
+
+ @Override
+ public long getLatestReceivedTimestamp() {
+ return mLatestReceivedTimestamp;
+ }
+
+ @Override
+ public int getNumRequestCodesNeeded() {
+ // Get additional request codes for the Reply PendingIntent (wearables only)
+ // and the DND PendingIntent.
+ return super.getNumRequestCodesNeeded() + NUM_EXTRA_REQUEST_CODES_NEEDED;
+ }
+
+ private int getBaseExtraRequestCode() {
+ return mBaseRequestCode + super.getNumRequestCodesNeeded();
+ }
+
+ public int getReplyIntentRequestCode() {
+ return getBaseExtraRequestCode() + REPLY_INTENT_REQUEST_CODE_OFFSET;
+ }
+
+ @Override
+ public PendingIntent getClearIntent() {
+ return UIIntents.get().getPendingIntentForClearingNotifications(
+ Factory.get().getApplicationContext(),
+ BugleNotifications.UPDATE_MESSAGES,
+ mConversationIds,
+ getClearIntentRequestCode());
+ }
+
+ /**
+ * Notification for multiple messages in at least 2 different conversations.
+ */
+ public static class MultiConversationNotificationState extends MessageNotificationState {
+
+ public final List<MessageNotificationState>
+ mChildren = new ArrayList<MessageNotificationState>();
+
+ public MultiConversationNotificationState(
+ final ConversationInfoList convList, final MessageNotificationState state) {
+ super(convList);
+ mAttachmentUri = null;
+ mAttachmentType = null;
+
+ // Pull the ticker title/text from the single notification
+ mTickerSender = state.getTitle();
+ mTitle = Factory.get().getApplicationContext().getResources().getQuantityString(
+ R.plurals.notification_new_messages,
+ convList.mMessageCount, convList.mMessageCount);
+ mTickerText = state.mContent;
+
+ // Create child notifications for each conversation,
+ // which will be displayed (only) on a wearable device.
+ for (int i = 0; i < convList.mConvInfos.size(); i++) {
+ final ConversationLineInfo convInfo = convList.mConvInfos.get(i);
+ if (!(convInfo.mLineInfos.get(0) instanceof MessageLineInfo)) {
+ continue;
+ }
+ setPeopleForConversation(convInfo.mConversationId);
+ final ConversationInfoList list = new ConversationInfoList(
+ convInfo.mTotalMessageCount, Lists.newArrayList(convInfo));
+ mChildren.add(new BundledMessageNotificationState(list, i));
+ }
+ }
+
+ @Override
+ public int getIcon() {
+ return R.drawable.ic_sms_multi_light;
+ }
+
+ @Override
+ protected NotificationCompat.Style build(final Builder builder) {
+ builder.setContentTitle(mTitle);
+ NotificationCompat.InboxStyle inboxStyle = null;
+ inboxStyle = new NotificationCompat.InboxStyle(builder);
+
+ final Context context = Factory.get().getApplicationContext();
+ // enumeration_comma is defined as ", "
+ final String separator = context.getString(R.string.enumeration_comma);
+ final StringBuilder senders = new StringBuilder();
+ long when = 0;
+ for (int i = 0; i < mConvList.mConvInfos.size(); i++) {
+ final ConversationLineInfo convInfo = mConvList.mConvInfos.get(i);
+ if (convInfo.mReceivedTimestamp > when) {
+ when = convInfo.mReceivedTimestamp;
+ }
+ String sender;
+ CharSequence text;
+ final NotificationLineInfo lineInfo = convInfo.mLineInfos.get(0);
+ final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfo;
+ if (convInfo.mIsGroup) {
+ sender = (convInfo.mGroupConversationName.length() >
+ MAX_CHARACTERS_IN_GROUP_NAME) ?
+ truncateGroupMessageName(convInfo.mGroupConversationName)
+ : convInfo.mGroupConversationName;
+ } else {
+ sender = messageLineInfo.mAuthorFullName;
+ }
+ text = messageLineInfo.mText;
+ mAttachmentUri = messageLineInfo.mAttachmentUri;
+ mAttachmentType = messageLineInfo.mAttachmentType;
+
+ inboxStyle.addLine(BugleNotifications.formatInboxMessage(
+ sender, text, mAttachmentUri, mAttachmentType));
+ if (sender != null) {
+ if (senders.length() > 0) {
+ senders.append(separator);
+ }
+ senders.append(sender);
+ }
+ }
+ // for collapsed state
+ mContent = senders;
+ builder.setContentText(senders)
+ .setTicker(getTicker())
+ .setWhen(when);
+
+ return inboxStyle;
+ }
+ }
+
+ /**
+ * Truncate group conversation name to be displayed in the notifications. This either truncates
+ * the entire group name or finds the last comma in the available length and truncates the name
+ * at that point
+ */
+ private static String truncateGroupMessageName(final String conversationName) {
+ int endIndex = MAX_CHARACTERS_IN_GROUP_NAME;
+ for (int i = MAX_CHARACTERS_IN_GROUP_NAME; i >= 0; i--) {
+ // The dividing marker should stay consistent with ConversationListItemData.DIVIDER_TEXT
+ if (conversationName.charAt(i) == ',') {
+ endIndex = i;
+ break;
+ }
+ }
+ return conversationName.substring(0, endIndex) + '\u2026';
+ }
+
+ /**
+ * Notification for multiple messages in a single conversation. Also used if there is a single
+ * message in a single conversation.
+ */
+ public static class MultiMessageNotificationState extends MessageNotificationState {
+
+ public MultiMessageNotificationState(final ConversationInfoList convList) {
+ super(convList);
+ // This conversation has been accepted.
+ final ConversationLineInfo convInfo = convList.mConvInfos.get(0);
+ setAvatarUrlsForConversation(convInfo.mConversationId);
+ setPeopleForConversation(convInfo.mConversationId);
+
+ final Context context = Factory.get().getApplicationContext();
+ MessageLineInfo messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0);
+ // attached photo
+ mAttachmentUri = messageInfo.mAttachmentUri;
+ mAttachmentType = messageInfo.mAttachmentType;
+ mContent = messageInfo.mText;
+
+ if (mAttachmentUri != null) {
+ // The default attachment type is an image, since that's what was originally
+ // supported. When there's no content type, assume it's an image.
+ int message = R.string.notification_picture;
+ if (ContentType.isAudioType(mAttachmentType)) {
+ message = R.string.notification_audio;
+ } else if (ContentType.isVideoType(mAttachmentType)) {
+ message = R.string.notification_video;
+ } else if (ContentType.isVCardType(mAttachmentType)) {
+ message = R.string.notification_vcard;
+ }
+ final String attachment = context.getString(message);
+ final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
+ if (!TextUtils.isEmpty(mContent)) {
+ spanBuilder.append(mContent).append(System.getProperty("line.separator"));
+ }
+ final int start = spanBuilder.length();
+ spanBuilder.append(attachment);
+ spanBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, spanBuilder.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mContent = spanBuilder;
+ }
+ if (convInfo.mIsGroup) {
+ // When the message is part of a group, the sender's first name
+ // is prepended to the message, but not for the ticker message.
+ mTickerText = mContent;
+ mTickerSender = messageInfo.mAuthorFullName;
+ // append the bold name to the front of the message
+ mContent = BugleNotifications.buildSpaceSeparatedMessage(
+ messageInfo.mAuthorFullName, mContent, mAttachmentUri,
+ mAttachmentType);
+ mTitle = convInfo.mGroupConversationName;
+ } else {
+ // No matter how many messages there are, since this is a 1:1, just
+ // get the author full name from the first one.
+ messageInfo = (MessageLineInfo) convInfo.mLineInfos.get(0);
+ mTitle = messageInfo.mAuthorFullName;
+ }
+ }
+
+ @Override
+ protected NotificationCompat.Style build(final Builder builder) {
+ builder.setContentTitle(mTitle)
+ .setTicker(getTicker());
+
+ NotificationCompat.Style notifStyle = null;
+ final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
+ final List<NotificationLineInfo> lineInfos = convInfo.mLineInfos;
+ final int messageCount = lineInfos.size();
+ // At this point, all the messages come from the same conversation. We need to load
+ // the sender's avatar and then finish building the notification on a callback.
+
+ builder.setContentText(mContent); // for collapsed state
+
+ if (messageCount == 1) {
+ final boolean shouldShowImage = ContentType.isImageType(mAttachmentType)
+ || (ContentType.isVideoType(mAttachmentType)
+ && VideoThumbnailRequest.shouldShowIncomingVideoThumbnails());
+ if (mAttachmentUri != null && shouldShowImage) {
+ // Show "Picture" as the content
+ final MessageLineInfo messageLineInfo = (MessageLineInfo) lineInfos.get(0);
+ String authorFirstName = messageLineInfo.mAuthorFirstName;
+
+ // For the collapsed state, just show "picture" unless this is a
+ // group conversation. If it's a group, show the sender name and
+ // "picture".
+ final CharSequence tickerTag =
+ BugleNotifications.formatAttachmentTag(authorFirstName,
+ mAttachmentType);
+ // For 1:1 notifications don't show first name in the notification, but
+ // do show it in the ticker text
+ CharSequence pictureTag = tickerTag;
+ if (!convInfo.mIsGroup) {
+ authorFirstName = null;
+ pictureTag = BugleNotifications.formatAttachmentTag(authorFirstName,
+ mAttachmentType);
+ }
+ builder.setContentText(pictureTag);
+ builder.setTicker(tickerTag);
+
+ notifStyle = new NotificationCompat.BigPictureStyle(builder)
+ .setSummaryText(BugleNotifications.formatInboxMessage(
+ authorFirstName,
+ null, null,
+ null)); // expanded state, just show sender
+ } else {
+ notifStyle = new NotificationCompat.BigTextStyle(builder)
+ .bigText(mContent);
+ }
+ } else {
+ // We've got multiple messages for the same sender.
+ // Starting with the oldest new message, display the full text of each message.
+ // Begin a line for each subsequent message.
+ final SpannableStringBuilder buf = new SpannableStringBuilder();
+
+ for (int i = lineInfos.size() - 1; i >= 0; --i) {
+ final NotificationLineInfo info = lineInfos.get(i);
+ final MessageLineInfo messageLineInfo = (MessageLineInfo) info;
+ mAttachmentUri = messageLineInfo.mAttachmentUri;
+ mAttachmentType = messageLineInfo.mAttachmentType;
+ CharSequence text = messageLineInfo.mText;
+ if (!TextUtils.isEmpty(text) || mAttachmentUri != null) {
+ if (convInfo.mIsGroup) {
+ // append the bold name to the front of the message
+ text = BugleNotifications.buildSpaceSeparatedMessage(
+ messageLineInfo.mAuthorFullName, text, mAttachmentUri,
+ mAttachmentType);
+ } else {
+ text = BugleNotifications.buildSpaceSeparatedMessage(
+ null, text, mAttachmentUri, mAttachmentType);
+ }
+ buf.append(text);
+ if (i > 0) {
+ buf.append('\n');
+ }
+ }
+ }
+
+ // Show a single notification -- big style with the text of all the messages
+ notifStyle = new NotificationCompat.BigTextStyle(builder).bigText(buf);
+ }
+ builder.setWhen(convInfo.mReceivedTimestamp);
+ return notifStyle;
+ }
+
+ }
+
+ private static boolean firstNameUsedMoreThanOnce(
+ final HashMap<String, Integer> map, final String firstName) {
+ if (map == null) {
+ return false;
+ }
+ if (firstName == null) {
+ return false;
+ }
+ final Integer count = map.get(firstName);
+ if (count != null) {
+ return count > 1;
+ } else {
+ return false;
+ }
+ }
+
+ private static HashMap<String, Integer> scanFirstNames(final String conversationId) {
+ final Context context = Factory.get().getApplicationContext();
+ final Uri uri =
+ MessagingContentProvider.buildConversationParticipantsUri(conversationId);
+ final Cursor participantsCursor = context.getContentResolver().query(
+ uri, ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
+ final ConversationParticipantsData participantsData = new ConversationParticipantsData();
+ participantsData.bind(participantsCursor);
+ final Iterator<ParticipantData> iter = participantsData.iterator();
+
+ final HashMap<String, Integer> firstNames = new HashMap<String, Integer>();
+ boolean seenSelf = false;
+ while (iter.hasNext()) {
+ final ParticipantData participant = iter.next();
+ // Make sure we only add the self participant once
+ if (participant.isSelf()) {
+ if (seenSelf) {
+ continue;
+ } else {
+ seenSelf = true;
+ }
+ }
+
+ final String firstName = participant.getFirstName();
+ if (firstName == null) {
+ continue;
+ }
+
+ final int currentCount = firstNames.containsKey(firstName)
+ ? firstNames.get(firstName)
+ : 0;
+ firstNames.put(firstName, currentCount + 1);
+ }
+ return firstNames;
+ }
+
+ // Essentially, we're building a list of the past 20 messages for this conversation to display
+ // on the wearable.
+ public static Notification buildConversationPageForWearable(final String conversationId,
+ int participantCount) {
+ final Context context = Factory.get().getApplicationContext();
+
+ // Limit the number of messages to show. We just want enough to provide context for the
+ // notification. Fetch one more than we need, so we can tell if there are more messages
+ // before the one we're showing.
+ // TODO: in the query, a multipart message will contain a row for each part.
+ // We might need a smarter GROUP_BY. On the other hand, we might want to show each of the
+ // parts as separate messages on the wearable.
+ final int limit = MAX_MESSAGES_IN_WEARABLE_PAGE + 1;
+
+ final List<CharSequence> messages = Lists.newArrayList();
+ boolean hasSeenMessagesBeforeNotification = false;
+ Cursor convMessageCursor = null;
+ try {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final String[] queryArgs = { conversationId };
+ final String convPageSql = ConversationMessageData.getWearableQuerySql() + " LIMIT " +
+ limit;
+ convMessageCursor = db.rawQuery(
+ convPageSql,
+ queryArgs);
+
+ if (convMessageCursor == null || !convMessageCursor.moveToFirst()) {
+ return null;
+ }
+ final ConversationMessageData convMessageData =
+ new ConversationMessageData();
+
+ final HashMap<String, Integer> firstNames = scanFirstNames(conversationId);
+ do {
+ convMessageData.bind(convMessageCursor);
+
+ final String authorFullName = convMessageData.getSenderFullName();
+ final String authorFirstName = convMessageData.getSenderFirstName();
+ String text = convMessageData.getText();
+
+ final boolean isSmsPushNotification = convMessageData.getIsMmsNotification();
+
+ // if auto-download was off to show a message to tap to download the message. We
+ // might need to get that working again.
+ if (isSmsPushNotification && text != null) {
+ text = convertHtmlAndStripUrls(text).toString();
+ }
+ // Skip messages without any content
+ if (TextUtils.isEmpty(text) && !convMessageData.hasAttachments()) {
+ continue;
+ }
+ // Track whether there are messages prior to the one(s) shown in the notification.
+ if (convMessageData.getIsSeen()) {
+ hasSeenMessagesBeforeNotification = true;
+ }
+
+ final boolean usedMoreThanOnce = firstNameUsedMoreThanOnce(
+ firstNames, authorFirstName);
+ String displayName = usedMoreThanOnce ? authorFullName : authorFirstName;
+ if (TextUtils.isEmpty(displayName)) {
+ if (convMessageData.getIsIncoming()) {
+ displayName = convMessageData.getSenderDisplayDestination();
+ if (TextUtils.isEmpty(displayName)) {
+ displayName = context.getString(R.string.unknown_sender);
+ }
+ } else {
+ displayName = context.getString(R.string.unknown_self_participant);
+ }
+ }
+
+ Uri attachmentUri = null;
+ String attachmentType = null;
+ final List<MessagePartData> attachments = convMessageData.getAttachments();
+ for (final MessagePartData messagePartData : attachments) {
+ // Look for the first attachment that's not the text piece.
+ if (!messagePartData.isText()) {
+ attachmentUri = messagePartData.getContentUri();
+ attachmentType = messagePartData.getContentType();
+ break;
+ }
+ }
+
+ final CharSequence message = BugleNotifications.buildSpaceSeparatedMessage(
+ displayName, text, attachmentUri, attachmentType);
+ messages.add(message);
+
+ } while (convMessageCursor.moveToNext());
+ } finally {
+ if (convMessageCursor != null) {
+ convMessageCursor.close();
+ }
+ }
+
+ // If there is no conversation history prior to what is already visible in the main
+ // notification, there's no need to include the conversation log, too.
+ final int maxMessagesInNotification = getMaxMessagesInConversationNotification();
+ if (!hasSeenMessagesBeforeNotification && messages.size() <= maxMessagesInNotification) {
+ return null;
+ }
+
+ final SpannableStringBuilder bigText = new SpannableStringBuilder();
+ // There is at least 1 message prior to the first one that we're going to show.
+ // Indicate this by inserting an ellipsis at the beginning of the conversation log.
+ if (convMessageCursor.getCount() == limit) {
+ bigText.append(context.getString(R.string.ellipsis) + "\n\n");
+ if (messages.size() > MAX_MESSAGES_IN_WEARABLE_PAGE) {
+ messages.remove(messages.size() - 1);
+ }
+ }
+ // Messages are sorted in descending timestamp order, so iterate backwards
+ // to get them back in ascending order for display purposes.
+ for (int i = messages.size() - 1; i >= 0; --i) {
+ bigText.append(messages.get(i));
+ if (i > 0) {
+ bigText.append("\n\n");
+ }
+ }
+ ++participantCount; // Add in myself
+
+ if (participantCount > 2) {
+ final SpannableString statusText = new SpannableString(
+ context.getResources().getQuantityString(R.plurals.wearable_participant_count,
+ participantCount, participantCount));
+ statusText.setSpan(new ForegroundColorSpan(context.getResources().getColor(
+ R.color.wearable_notification_participants_count)), 0, statusText.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ bigText.append("\n\n").append(statusText);
+ }
+
+ final NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context);
+ final NotificationCompat.Style notifStyle =
+ new NotificationCompat.BigTextStyle(notifBuilder).bigText(bigText);
+ notifBuilder.setStyle(notifStyle);
+
+ final WearableExtender wearableExtender = new WearableExtender();
+ wearableExtender.setStartScrollBottom(true);
+ notifBuilder.extend(wearableExtender);
+
+ return notifBuilder.build();
+ }
+
+ /**
+ * Notification for one or more messages in a single conversation, which is bundled together
+ * with notifications for other conversations on a wearable device.
+ */
+ public static class BundledMessageNotificationState extends MultiMessageNotificationState {
+ public int mGroupOrder;
+ public BundledMessageNotificationState(final ConversationInfoList convList,
+ final int groupOrder) {
+ super(convList);
+ mGroupOrder = groupOrder;
+ }
+ }
+
+ /**
+ * Performs a query on the database.
+ */
+ private static ConversationInfoList createConversationInfoList() {
+ // Map key is conversation id. We use LinkedHashMap to ensure that entries are iterated in
+ // the same order they were originally added. We scan unseen messages from newest to oldest,
+ // so the corresponding conversations are added in that order, too.
+ final Map<String, ConversationLineInfo> convLineInfos = new LinkedHashMap<>();
+ int messageCount = 0;
+
+ Cursor convMessageCursor = null;
+ try {
+ final Context context = Factory.get().getApplicationContext();
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ convMessageCursor = db.rawQuery(
+ ConversationMessageData.getNotificationQuerySql(),
+ null);
+
+ if (convMessageCursor != null && convMessageCursor.moveToFirst()) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "MessageNotificationState: Found unseen message notifications.");
+ }
+ final ConversationMessageData convMessageData =
+ new ConversationMessageData();
+
+ HashMap<String, Integer> firstNames = null;
+ String conversationIdForFirstNames = null;
+ String groupConversationName = null;
+ final int maxMessages = getMaxMessagesInConversationNotification();
+
+ do {
+ convMessageData.bind(convMessageCursor);
+
+ // First figure out if this is a valid message.
+ String authorFullName = convMessageData.getSenderFullName();
+ String authorFirstName = convMessageData.getSenderFirstName();
+ final String messageText = convMessageData.getText();
+
+ final String convId = convMessageData.getConversationId();
+ final String messageId = convMessageData.getMessageId();
+
+ CharSequence text = messageText;
+ final boolean isManualDownloadNeeded = convMessageData.getIsMmsNotification();
+ if (isManualDownloadNeeded) {
+ // Don't try and convert the text from html if it's sms and not a sms push
+ // notification.
+ Assert.equals(MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD,
+ convMessageData.getStatus());
+ text = context.getResources().getString(
+ R.string.message_title_manual_download);
+ }
+ ConversationLineInfo currConvInfo = convLineInfos.get(convId);
+ if (currConvInfo == null) {
+ final ConversationListItemData convData =
+ ConversationListItemData.getExistingConversation(db, convId);
+ if (!convData.getNotificationEnabled()) {
+ // Skip conversations that have notifications disabled.
+ continue;
+ }
+ final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db,
+ convData.getSelfId());
+ groupConversationName = convData.getName();
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ convMessageData.getSenderProfilePhotoUri(),
+ convMessageData.getSenderFullName(),
+ convMessageData.getSenderNormalizedDestination(),
+ convMessageData.getSenderContactLookupKey());
+ currConvInfo = new ConversationLineInfo(convId,
+ convData.getIsGroup(),
+ groupConversationName,
+ convData.getIncludeEmailAddress(),
+ convMessageData.getReceivedTimeStamp(),
+ convData.getSelfId(),
+ convData.getNotificationSoundUri(),
+ convData.getNotificationEnabled(),
+ convData.getNotifiationVibrate(),
+ avatarUri,
+ convMessageData.getSenderContactLookupUri(),
+ subId,
+ convData.getParticipantCount());
+ convLineInfos.put(convId, currConvInfo);
+ }
+ // Prepare the message line
+ if (currConvInfo.mTotalMessageCount < maxMessages) {
+ if (currConvInfo.mIsGroup) {
+ if (authorFirstName == null) {
+ // authorFullName might be null as well. In that case, we won't
+ // show an author. That is better than showing all the group
+ // names again on the 2nd line.
+ authorFirstName = authorFullName;
+ }
+ } else {
+ // don't recompute this if we don't need to
+ if (!TextUtils.equals(conversationIdForFirstNames, convId)) {
+ firstNames = scanFirstNames(convId);
+ conversationIdForFirstNames = convId;
+ }
+ if (firstNames != null) {
+ final Integer count = firstNames.get(authorFirstName);
+ if (count != null && count > 1) {
+ authorFirstName = authorFullName;
+ }
+ }
+
+ if (authorFullName == null) {
+ authorFullName = groupConversationName;
+ }
+ if (authorFirstName == null) {
+ authorFirstName = groupConversationName;
+ }
+ }
+ final String subjectText = MmsUtils.cleanseMmsSubject(
+ context.getResources(),
+ convMessageData.getMmsSubject());
+ if (!TextUtils.isEmpty(subjectText)) {
+ final String subjectLabel =
+ context.getString(R.string.subject_label);
+ final SpannableStringBuilder spanBuilder =
+ new SpannableStringBuilder();
+
+ spanBuilder.append(context.getString(R.string.notification_subject,
+ subjectLabel, subjectText));
+ spanBuilder.setSpan(new TextAppearanceSpan(
+ context, R.style.NotificationSubjectText), 0,
+ subjectLabel.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (!TextUtils.isEmpty(text)) {
+ // Now add the actual message text below the subject header.
+ spanBuilder.append(System.getProperty("line.separator") + text);
+ }
+ text = spanBuilder;
+ }
+ // If we've got attachments, find the best one. If one of the messages is
+ // a photo, save the url so we'll display a big picture notification.
+ // Otherwise, show the first one we find.
+ Uri attachmentUri = null;
+ String attachmentType = null;
+ final MessagePartData messagePartData =
+ getMostInterestingAttachment(convMessageData);
+ if (messagePartData != null) {
+ attachmentUri = messagePartData.getContentUri();
+ attachmentType = messagePartData.getContentType();
+ }
+ currConvInfo.mLineInfos.add(new MessageLineInfo(currConvInfo.mIsGroup,
+ authorFullName, authorFirstName, text,
+ attachmentUri, attachmentType, isManualDownloadNeeded, messageId));
+ }
+ messageCount++;
+ currConvInfo.mTotalMessageCount++;
+ } while (convMessageCursor.moveToNext());
+ }
+ } finally {
+ if (convMessageCursor != null) {
+ convMessageCursor.close();
+ }
+ }
+ if (convLineInfos.isEmpty()) {
+ return null;
+ } else {
+ return new ConversationInfoList(messageCount,
+ Lists.newLinkedList(convLineInfos.values()));
+ }
+ }
+
+ /**
+ * Scans all the attachments for a message and returns the most interesting one that we'll
+ * show in a notification. By order of importance, in case there are multiple attachments:
+ * 1- an image (because we can show the image as a BigPictureNotification)
+ * 2- a video (because we can show a video frame as a BigPictureNotification)
+ * 3- a vcard
+ * 4- an audio attachment
+ * @return MessagePartData for the most interesting part. Can be null.
+ */
+ private static MessagePartData getMostInterestingAttachment(
+ final ConversationMessageData convMessageData) {
+ final List<MessagePartData> attachments = convMessageData.getAttachments();
+
+ MessagePartData imagePart = null;
+ MessagePartData audioPart = null;
+ MessagePartData vcardPart = null;
+ MessagePartData videoPart = null;
+
+ // 99.99% of the time there will be 0 or 1 part, since receiving slideshows is so
+ // uncommon.
+
+ // Remember the first of each type of part.
+ for (final MessagePartData messagePartData : attachments) {
+ if (messagePartData.isImage() && imagePart == null) {
+ imagePart = messagePartData;
+ }
+ if (messagePartData.isVideo() && videoPart == null) {
+ videoPart = messagePartData;
+ }
+ if (messagePartData.isVCard() && vcardPart == null) {
+ vcardPart = messagePartData;
+ }
+ if (messagePartData.isAudio() && audioPart == null) {
+ audioPart = messagePartData;
+ }
+ }
+ if (imagePart != null) {
+ return imagePart;
+ } else if (videoPart != null) {
+ return videoPart;
+ } else if (audioPart != null) {
+ return audioPart;
+ } else if (vcardPart != null) {
+ return vcardPart;
+ }
+ return null;
+ }
+
+ private static int getMaxMessagesInConversationNotification() {
+ if (!BugleNotifications.isWearCompanionAppInstalled()) {
+ return BugleGservices.get().getInt(
+ BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION,
+ BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_DEFAULT);
+ }
+ return BugleGservices.get().getInt(
+ BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE,
+ BugleGservicesKeys.MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE_DEFAULT);
+ }
+
+ /**
+ * Scans the database for messages that need to go into notifications. Creates the appropriate
+ * MessageNotificationState depending on if there are multiple senders, or
+ * messages from one sender.
+ * @return NotificationState for the notification created.
+ */
+ public static NotificationState getNotificationState() {
+ MessageNotificationState state = null;
+ final ConversationInfoList convList = createConversationInfoList();
+
+ if (convList == null || convList.mConvInfos.size() == 0) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "MessageNotificationState: No unseen notifications");
+ }
+ } else {
+ final ConversationLineInfo convInfo = convList.mConvInfos.get(0);
+ state = new MultiMessageNotificationState(convList);
+
+ if (convList.mConvInfos.size() > 1) {
+ // We've got notifications across multiple conversations. Pass in the notification
+ // we just built of the most recent notification so we can use that to show the
+ // user the new message in the ticker.
+ state = new MultiConversationNotificationState(convList, state);
+ } else {
+ // For now, only show avatars for notifications for a single conversation.
+ if (convInfo.mAvatarUri != null) {
+ if (state.mParticipantAvatarsUris == null) {
+ state.mParticipantAvatarsUris = new ArrayList<Uri>(1);
+ }
+ state.mParticipantAvatarsUris.add(convInfo.mAvatarUri);
+ }
+ if (convInfo.mContactUri != null) {
+ if (state.mParticipantContactUris == null) {
+ state.mParticipantContactUris = new ArrayList<Uri>(1);
+ }
+ state.mParticipantContactUris.add(convInfo.mContactUri);
+ }
+ }
+ }
+ if (state != null && LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "MessageNotificationState: Notification state created"
+ + ", title = " + LogUtil.sanitizePII(state.mTitle)
+ + ", content = " + LogUtil.sanitizePII(state.mContent.toString()));
+ }
+ return state;
+ }
+
+ protected String getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public int getLatestMessageNotificationType() {
+ // This function is called to determine whether the most recent notification applies
+ // to an sms conversation or a hangout conversation. We have different ringtone/vibrate
+ // settings for both types of conversations.
+ if (mConvList.mConvInfos.size() > 0) {
+ final ConversationLineInfo convInfo = mConvList.mConvInfos.get(0);
+ return convInfo.getLatestMessageNotificationType();
+ }
+ return BugleNotifications.LOCAL_SMS_NOTIFICATION;
+ }
+
+ @Override
+ public String getRingtoneUri() {
+ if (mConvList.mConvInfos.size() > 0) {
+ return mConvList.mConvInfos.get(0).mRingtoneUri;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean getNotificationVibrate() {
+ if (mConvList.mConvInfos.size() > 0) {
+ return mConvList.mConvInfos.get(0).mNotificationVibrate;
+ }
+ return false;
+ }
+
+ protected CharSequence getTicker() {
+ return BugleNotifications.buildColonSeparatedMessage(
+ mTickerSender != null ? mTickerSender : mTitle,
+ mTickerText != null ? mTickerText : (mTickerNoContent ? null : mContent), null,
+ null);
+ }
+
+ private static CharSequence convertHtmlAndStripUrls(final String s) {
+ final Spanned text = Html.fromHtml(s);
+ if (text instanceof Spannable) {
+ stripUrls((Spannable) text);
+ }
+ return text;
+ }
+
+ // Since we don't want to show URLs in notifications, a function
+ // to remove them in place.
+ private static void stripUrls(final Spannable text) {
+ final URLSpan[] spans = text.getSpans(0, text.length(), URLSpan.class);
+ for (final URLSpan span : spans) {
+ text.removeSpan(span);
+ }
+ }
+
+ /*
+ private static void updateAlertStatusMessages(final long thresholdDeltaMs) {
+ // TODO may need this when supporting error notifications
+ final EsDatabaseHelper helper = EsDatabaseHelper.getDatabaseHelper();
+ final ContentValues values = new ContentValues();
+ final long nowMicros = System.currentTimeMillis() * 1000;
+ values.put(MessageColumns.ALERT_STATUS, "1");
+ final String selection =
+ MessageColumns.ALERT_STATUS + "=0 AND (" +
+ MessageColumns.STATUS + "=" + EsProvider.MESSAGE_STATUS_FAILED_TO_SEND + " OR (" +
+ MessageColumns.STATUS + "!=" + EsProvider.MESSAGE_STATUS_ON_SERVER + " AND " +
+ MessageColumns.TIMESTAMP + "+" + thresholdDeltaMs*1000 + "<" + nowMicros + ")) ";
+
+ final int updateCount = helper.getWritableDatabaseWrapper().update(
+ EsProvider.MESSAGES_TABLE,
+ values,
+ selection,
+ null);
+ if (updateCount > 0) {
+ EsConversationsData.notifyConversationsChanged();
+ }
+ }*/
+
+ static CharSequence applyWarningTextColor(final Context context,
+ final CharSequence text) {
+ if (text == null) {
+ return null;
+ }
+ final SpannableStringBuilder spanBuilder = new SpannableStringBuilder();
+ spanBuilder.append(text);
+ spanBuilder.setSpan(new ForegroundColorSpan(context.getResources().getColor(
+ R.color.notification_warning_color)), 0, text.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return spanBuilder;
+ }
+
+ /**
+ * Check for failed messages and post notifications as needed.
+ * TODO: Rewrite this as a NotificationState.
+ */
+ public static void checkFailedMessages() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final Cursor messageDataCursor = db.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(),
+ FailedMessageQuery.FAILED_MESSAGES_WHERE_CLAUSE,
+ null /*selectionArgs*/,
+ null /*groupBy*/,
+ null /*having*/,
+ FailedMessageQuery.FAILED_ORDER_BY);
+
+ try {
+ final Context context = Factory.get().getApplicationContext();
+ final Resources resources = context.getResources();
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(context);
+ if (messageDataCursor != null) {
+ final MessageData messageData = new MessageData();
+
+ final HashSet<String> conversationsWithFailedMessages = new HashSet<String>();
+
+ // track row ids in case we want to display something that requires this
+ // information
+ final ArrayList<Integer> failedMessages = new ArrayList<Integer>();
+
+ int cursorPosition = -1;
+ final long when = 0;
+
+ messageDataCursor.moveToPosition(-1);
+ while (messageDataCursor.moveToNext()) {
+ messageData.bind(messageDataCursor);
+
+ final String conversationId = messageData.getConversationId();
+ if (DataModel.get().isNewMessageObservable(conversationId)) {
+ // Don't post a system notification for an observable conversation
+ // because we already show an angry red annotation in the conversation
+ // itself or in the conversation preview snippet.
+ continue;
+ }
+
+ cursorPosition = messageDataCursor.getPosition();
+ failedMessages.add(cursorPosition);
+ conversationsWithFailedMessages.add(conversationId);
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "Found " + failedMessages.size() + " failed messages");
+ }
+ if (failedMessages.size() > 0) {
+ final NotificationCompat.Builder builder =
+ new NotificationCompat.Builder(context);
+
+ CharSequence line1;
+ CharSequence line2;
+ final boolean isRichContent = false;
+ ConversationIdSet conversationIds = null;
+ PendingIntent destinationIntent;
+ if (failedMessages.size() == 1) {
+ messageDataCursor.moveToPosition(cursorPosition);
+ messageData.bind(messageDataCursor);
+ final String conversationId = messageData.getConversationId();
+
+ // We have a single conversation, go directly to that conversation.
+ destinationIntent = UIIntents.get()
+ .getPendingIntentForConversationActivity(context,
+ conversationId,
+ null /*draft*/);
+
+ conversationIds = ConversationIdSet.createSet(conversationId);
+
+ final String failedMessgeSnippet = messageData.getMessageText();
+ int failureStringId;
+ if (messageData.getStatus() ==
+ MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED) {
+ failureStringId =
+ R.string.notification_download_failures_line1_singular;
+ } else {
+ failureStringId = R.string.notification_send_failures_line1_singular;
+ }
+ line1 = resources.getString(failureStringId);
+ line2 = failedMessgeSnippet;
+ // Set rich text for non-SMS messages or MMS push notification messages
+ // which we generate locally with rich text
+ // TODO- fix this
+// if (messageData.isMmsInd()) {
+// isRichContent = true;
+// }
+ } else {
+ // We have notifications for multiple conversation, go to the conversation
+ // list.
+ destinationIntent = UIIntents.get()
+ .getPendingIntentForConversationListActivity(context);
+
+ int line1StringId;
+ int line2PluralsId;
+ if (messageData.getStatus() ==
+ MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED) {
+ line1StringId =
+ R.string.notification_download_failures_line1_plural;
+ line2PluralsId = R.plurals.notification_download_failures;
+ } else {
+ line1StringId = R.string.notification_send_failures_line1_plural;
+ line2PluralsId = R.plurals.notification_send_failures;
+ }
+ line1 = resources.getString(line1StringId);
+ line2 = resources.getQuantityString(
+ line2PluralsId,
+ conversationsWithFailedMessages.size(),
+ failedMessages.size(),
+ conversationsWithFailedMessages.size());
+ }
+ line1 = applyWarningTextColor(context, line1);
+ line2 = applyWarningTextColor(context, line2);
+
+ final PendingIntent pendingIntentForDelete =
+ UIIntents.get().getPendingIntentForClearingNotifications(
+ context,
+ BugleNotifications.UPDATE_ERRORS,
+ conversationIds,
+ 0);
+
+ builder
+ .setContentTitle(line1)
+ .setTicker(line1)
+ .setWhen(when > 0 ? when : System.currentTimeMillis())
+ .setSmallIcon(R.drawable.ic_failed_light)
+ .setDeleteIntent(pendingIntentForDelete)
+ .setContentIntent(destinationIntent)
+ .setSound(UriUtil.getUriForResourceId(context, R.raw.message_failure));
+ if (isRichContent && !TextUtils.isEmpty(line2)) {
+ final NotificationCompat.InboxStyle inboxStyle =
+ new NotificationCompat.InboxStyle(builder);
+ if (line2 != null) {
+ inboxStyle.addLine(Html.fromHtml(line2.toString()));
+ }
+ builder.setStyle(inboxStyle);
+ } else {
+ builder.setContentText(line2);
+ }
+
+ if (builder != null) {
+ notificationManager.notify(
+ BugleNotifications.buildNotificationTag(
+ PendingIntentConstants.MSG_SEND_ERROR, null),
+ PendingIntentConstants.MSG_SEND_ERROR,
+ builder.build());
+ }
+ } else {
+ notificationManager.cancel(
+ BugleNotifications.buildNotificationTag(
+ PendingIntentConstants.MSG_SEND_ERROR, null),
+ PendingIntentConstants.MSG_SEND_ERROR);
+ }
+ }
+ } finally {
+ if (messageDataCursor != null) {
+ messageDataCursor.close();
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/MessageTextStats.java b/src/com/android/messaging/datamodel/MessageTextStats.java
new file mode 100644
index 0000000..2bd24ff
--- /dev/null
+++ b/src/com/android/messaging/datamodel/MessageTextStats.java
@@ -0,0 +1,92 @@
+/*
+ * 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.datamodel;
+
+import android.telephony.SmsMessage;
+
+import com.android.messaging.sms.MmsConfig;
+
+public class MessageTextStats {
+ private boolean mMessageLengthRequiresMms;
+ private int mMessageCount;
+ private int mCodePointsRemainingInCurrentMessage;
+
+ public MessageTextStats() {
+ mCodePointsRemainingInCurrentMessage = Integer.MAX_VALUE;
+ }
+
+ public int getNumMessagesToBeSent() {
+ return mMessageCount;
+ }
+
+ public int getCodePointsRemainingInCurrentMessage() {
+ return mCodePointsRemainingInCurrentMessage;
+ }
+
+ public boolean getMessageLengthRequiresMms() {
+ return mMessageLengthRequiresMms;
+ }
+
+ public void updateMessageTextStats(final int selfSubId, final String messageText) {
+ final int[] params = SmsMessage.calculateLength(messageText, false);
+ /* SmsMessage.calculateLength returns an int[4] with:
+ * int[0] being the number of SMS's required,
+ * int[1] the number of code points used,
+ * int[2] is the number of code points remaining until the next message.
+ * int[3] is the encoding type that should be used for the message.
+ */
+ mMessageCount = params[0];
+ mCodePointsRemainingInCurrentMessage = params[2];
+
+ final MmsConfig mmsConfig = MmsConfig.get(selfSubId);
+ if (!mmsConfig.getMultipartSmsEnabled() &&
+ !mmsConfig.getSendMultipartSmsAsSeparateMessages()) {
+ // The provider doesn't support multi-part sms's and we should use MMS to
+ // send multi-part sms, so as soon as the user types
+ // an sms longer than one segment, we have to turn the message into an mms.
+ mMessageLengthRequiresMms = mMessageCount > 1;
+ } else {
+ final int threshold = mmsConfig.getSmsToMmsTextThreshold();
+ mMessageLengthRequiresMms = threshold > 0 && mMessageCount > threshold;
+ }
+ // Some carriers require any SMS message longer than 80 to be sent as MMS
+ // see b/12122333
+ int smsToMmsLengthThreshold = mmsConfig.getSmsToMmsTextLengthThreshold();
+ if (smsToMmsLengthThreshold > 0) {
+ final int usedInCurrentMessage = params[1];
+ /*
+ * A little hacky way to find out if we should count characters in double bytes.
+ * SmsMessage.calculateLength counts message code units based on the characters
+ * in input. If all of them are ascii, the max length is
+ * SmsMessage.MAX_USER_DATA_SEPTETS (160). If any of them are double-byte, like
+ * Korean or Chinese, the max length is SmsMessage.MAX_USER_DATA_BYTES (140) bytes
+ * (70 code units).
+ * Here we check if the total code units we can use is smaller than 140. If so,
+ * we know we should count threshold in double-byte, so divide the threshold by 2.
+ * In this way, we will count Korean text correctly with regard to the length threshold.
+ */
+ if (usedInCurrentMessage + mCodePointsRemainingInCurrentMessage
+ < SmsMessage.MAX_USER_DATA_BYTES) {
+ smsToMmsLengthThreshold /= 2;
+ }
+ if (usedInCurrentMessage > smsToMmsLengthThreshold) {
+ mMessageLengthRequiresMms = true;
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/messaging/datamodel/MessagingContentProvider.java b/src/com/android/messaging/datamodel/MessagingContentProvider.java
new file mode 100644
index 0000000..7688abd
--- /dev/null
+++ b/src/com/android/messaging/datamodel/MessagingContentProvider.java
@@ -0,0 +1,476 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+
+import com.android.messaging.BugleApplication;
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.widget.BugleWidgetProvider;
+import com.android.messaging.widget.WidgetConversationProvider;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+
+/**
+ * A centralized provider for Uris exposed by Bugle.
+ * */
+public class MessagingContentProvider extends ContentProvider {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ @VisibleForTesting
+ public static final String AUTHORITY =
+ "com.android.messaging.datamodel.MessagingContentProvider";
+ private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
+
+ // Conversations query
+ private static final String CONVERSATIONS_QUERY = "conversations";
+
+ public static final Uri CONVERSATIONS_URI = Uri.parse(CONTENT_AUTHORITY + CONVERSATIONS_QUERY);
+ static final Uri PARTS_URI = Uri.parse(CONTENT_AUTHORITY + DatabaseHelper.PARTS_TABLE);
+
+ // Messages query
+ private static final String MESSAGES_QUERY = "messages";
+
+ static final Uri MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY + MESSAGES_QUERY);
+
+ public static final Uri CONVERSATION_MESSAGES_URI = Uri.parse(CONTENT_AUTHORITY +
+ MESSAGES_QUERY + "/conversation");
+
+ // Conversation participants query
+ private static final String PARTICIPANTS_QUERY = "participants";
+
+ static class ConversationParticipantsQueryColumns extends ParticipantColumns {
+ static final String CONVERSATION_ID = ConversationParticipantsColumns.CONVERSATION_ID;
+ }
+
+ static final Uri CONVERSATION_PARTICIPANTS_URI = Uri.parse(CONTENT_AUTHORITY +
+ PARTICIPANTS_QUERY + "/conversation");
+
+ public static final Uri PARTICIPANTS_URI = Uri.parse(CONTENT_AUTHORITY + PARTICIPANTS_QUERY);
+
+ // Conversation images query
+ private static final String CONVERSATION_IMAGES_QUERY = "conversation_images";
+
+ public static final Uri CONVERSATION_IMAGES_URI = Uri.parse(CONTENT_AUTHORITY +
+ CONVERSATION_IMAGES_QUERY);
+
+ private static final String DRAFT_IMAGES_QUERY = "draft_images";
+
+ public static final Uri DRAFT_IMAGES_URI = Uri.parse(CONTENT_AUTHORITY +
+ DRAFT_IMAGES_QUERY);
+
+ /**
+ * Notifies that <i>all</i> data exposed by the provider needs to be refreshed.
+ * <p>
+ * <b>IMPORTANT!</b> You probably shouldn't be calling this. Prefer to notify more specific
+ * uri's instead. Currently only sync uses this, because sync can potentially update many
+ * different tables at once.
+ */
+ public static void notifyEverythingChanged() {
+ final Uri uri = Uri.parse(CONTENT_AUTHORITY);
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver cr = context.getContentResolver();
+ cr.notifyChange(uri, null);
+
+ // Notify any conversations widgets the conversation list has changed.
+ BugleWidgetProvider.notifyConversationListChanged(context);
+
+ // Notify all conversation widgets to update.
+ WidgetConversationProvider.notifyMessagesChanged(context, null /*conversationId*/);
+ }
+
+ /**
+ * Build a participant uri from the conversation id.
+ */
+ public static Uri buildConversationParticipantsUri(final String conversationId) {
+ final Uri.Builder builder = CONVERSATION_PARTICIPANTS_URI.buildUpon();
+ builder.appendPath(conversationId);
+ return builder.build();
+ }
+
+ public static void notifyParticipantsChanged(final String conversationId) {
+ final Uri uri = buildConversationParticipantsUri(conversationId);
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ cr.notifyChange(uri, null);
+ }
+
+ public static void notifyAllMessagesChanged() {
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ cr.notifyChange(CONVERSATION_MESSAGES_URI, null);
+ }
+
+ public static void notifyAllParticipantsChanged() {
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ cr.notifyChange(CONVERSATION_PARTICIPANTS_URI, null);
+ }
+
+ // Default value for unknown dimension of image
+ public static final int UNSPECIFIED_SIZE = -1;
+
+ // Internal
+ private static final int CONVERSATIONS_QUERY_CODE = 10;
+
+ private static final int CONVERSATION_QUERY_CODE = 20;
+ private static final int CONVERSATION_MESSAGES_QUERY_CODE = 30;
+ private static final int CONVERSATION_PARTICIPANTS_QUERY_CODE = 40;
+ private static final int CONVERSATION_IMAGES_QUERY_CODE = 50;
+ private static final int DRAFT_IMAGES_QUERY_CODE = 60;
+ private static final int PARTICIPANTS_QUERY_CODE = 70;
+
+ // TODO: Move to a better structured URI namespace.
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ static {
+ sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY, CONVERSATIONS_QUERY_CODE);
+ sURIMatcher.addURI(AUTHORITY, CONVERSATIONS_QUERY + "/*", CONVERSATION_QUERY_CODE);
+ sURIMatcher.addURI(AUTHORITY, MESSAGES_QUERY + "/conversation/*",
+ CONVERSATION_MESSAGES_QUERY_CODE);
+ sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY + "/conversation/*",
+ CONVERSATION_PARTICIPANTS_QUERY_CODE);
+ sURIMatcher.addURI(AUTHORITY, PARTICIPANTS_QUERY, PARTICIPANTS_QUERY_CODE);
+ sURIMatcher.addURI(AUTHORITY, CONVERSATION_IMAGES_QUERY + "/*",
+ CONVERSATION_IMAGES_QUERY_CODE);
+ sURIMatcher.addURI(AUTHORITY, DRAFT_IMAGES_QUERY + "/*",
+ DRAFT_IMAGES_QUERY_CODE);
+ }
+
+ /**
+ * Build a messages uri from the conversation id.
+ */
+ public static Uri buildConversationMessagesUri(final String conversationId) {
+ final Uri.Builder builder = CONVERSATION_MESSAGES_URI.buildUpon();
+ builder.appendPath(conversationId);
+ return builder.build();
+ }
+
+ public static void notifyMessagesChanged(final String conversationId) {
+ final Uri uri = buildConversationMessagesUri(conversationId);
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver cr = context.getContentResolver();
+ cr.notifyChange(uri, null);
+ notifyConversationListChanged();
+
+ // Notify the widget the messages changed
+ WidgetConversationProvider.notifyMessagesChanged(context, conversationId);
+ }
+
+ /**
+ * Build a conversation metadata uri from a conversation id.
+ */
+ public static Uri buildConversationMetadataUri(final String conversationId) {
+ final Uri.Builder builder = CONVERSATIONS_URI.buildUpon();
+ builder.appendPath(conversationId);
+ return builder.build();
+ }
+
+ public static void notifyConversationMetadataChanged(final String conversationId) {
+ final Uri uri = buildConversationMetadataUri(conversationId);
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ cr.notifyChange(uri, null);
+ notifyConversationListChanged();
+ }
+
+ public static void notifyPartsChanged() {
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ cr.notifyChange(PARTS_URI, null);
+ }
+
+ public static void notifyConversationListChanged() {
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver cr = context.getContentResolver();
+ cr.notifyChange(CONVERSATIONS_URI, null);
+
+ // Notify the widget the conversation list changed
+ BugleWidgetProvider.notifyConversationListChanged(context);
+ }
+
+ /**
+ * Build a conversation images uri from a conversation id.
+ */
+ public static Uri buildConversationImagesUri(final String conversationId) {
+ final Uri.Builder builder = CONVERSATION_IMAGES_URI.buildUpon();
+ builder.appendPath(conversationId);
+ return builder.build();
+ }
+
+ /**
+ * Build a draft images uri from a conversation id.
+ */
+ public static Uri buildDraftImagesUri(final String conversationId) {
+ final Uri.Builder builder = DRAFT_IMAGES_URI.buildUpon();
+ builder.appendPath(conversationId);
+ return builder.build();
+ }
+
+ private DatabaseHelper mDatabaseHelper;
+ private DatabaseWrapper mDatabaseWrapper;
+
+ public MessagingContentProvider() {
+ super();
+ }
+
+ @VisibleForTesting
+ public void setDatabaseForTest(final DatabaseWrapper db) {
+ Assert.isTrue(BugleApplication.isRunningTests());
+ mDatabaseWrapper = db;
+ }
+
+ private DatabaseWrapper getDatabaseWrapper() {
+ if (mDatabaseWrapper == null) {
+ mDatabaseWrapper = mDatabaseHelper.getDatabase();
+ }
+ return mDatabaseWrapper;
+ }
+
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, String selection,
+ final String[] selectionArgs, String sortOrder) {
+
+ // Processes other than self are allowed to temporarily access the media
+ // scratch space; we grant uri read access on a case-by-case basis. Dialer app and
+ // contacts app would doQuery() on the vCard uri before trying to open the inputStream.
+ // There's nothing that we need to return for this uri so just No-Op.
+ //if (isMediaScratchSpaceUri(uri)) {
+ // return null;
+ //}
+
+ final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+
+ String[] queryArgs = selectionArgs;
+ final int match = sURIMatcher.match(uri);
+ String groupBy = null;
+ String limit = null;
+ switch (match) {
+ case CONVERSATIONS_QUERY_CODE:
+ queryBuilder.setTables(ConversationListItemData.getConversationListView());
+ // Hide empty conversations (ones with 0 sort_timestamp)
+ queryBuilder.appendWhere(ConversationColumns.SORT_TIMESTAMP + " > 0 ");
+ break;
+ case CONVERSATION_QUERY_CODE:
+ queryBuilder.setTables(ConversationListItemData.getConversationListView());
+ if (uri.getPathSegments().size() == 2) {
+ queryBuilder.appendWhere(ConversationColumns._ID + "=?");
+ // Get the conversation id from the uri
+ queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1));
+ } else {
+ throw new IllegalArgumentException("Malformed URI " + uri);
+ }
+ break;
+ case CONVERSATION_PARTICIPANTS_QUERY_CODE:
+ queryBuilder.setTables(DatabaseHelper.PARTICIPANTS_TABLE);
+ if (uri.getPathSegments().size() == 3 &&
+ TextUtils.equals(uri.getPathSegments().get(1), "conversation")) {
+ queryBuilder.appendWhere(ParticipantColumns._ID + " IN ( " + "SELECT "
+ + ConversationParticipantsColumns.PARTICIPANT_ID + " AS "
+ + ParticipantColumns._ID
+ + " FROM " + DatabaseHelper.CONVERSATION_PARTICIPANTS_TABLE
+ + " WHERE " + ConversationParticipantsColumns.CONVERSATION_ID
+ + " =? UNION SELECT " + ParticipantColumns._ID + " FROM "
+ + DatabaseHelper.PARTICIPANTS_TABLE + " WHERE "
+ + ParticipantColumns.SUB_ID + " != "
+ + ParticipantData.OTHER_THAN_SELF_SUB_ID + " )");
+ // Get the conversation id from the uri
+ queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(2));
+ } else {
+ throw new IllegalArgumentException("Malformed URI " + uri);
+ }
+ break;
+ case PARTICIPANTS_QUERY_CODE:
+ queryBuilder.setTables(DatabaseHelper.PARTICIPANTS_TABLE);
+ if (uri.getPathSegments().size() != 1) {
+ throw new IllegalArgumentException("Malformed URI " + uri);
+ }
+ break;
+ case CONVERSATION_MESSAGES_QUERY_CODE:
+ if (uri.getPathSegments().size() == 3 &&
+ TextUtils.equals(uri.getPathSegments().get(1), "conversation")) {
+ // Get the conversation id from the uri
+ final String conversationId = uri.getPathSegments().get(2);
+
+ // We need to handle this query differently, instead of falling through to the
+ // generic query call at the bottom. For performance reasons, the conversation
+ // messages query is executed as a raw query. It is invalid to specify
+ // selection/sorting for this query.
+
+ if (selection == null && selectionArgs == null && sortOrder == null) {
+ return queryConversationMessages(conversationId, uri);
+ } else {
+ throw new IllegalArgumentException(
+ "Cannot set selection or sort order with this query");
+ }
+ } else {
+ throw new IllegalArgumentException("Malformed URI " + uri);
+ }
+ case CONVERSATION_IMAGES_QUERY_CODE:
+ queryBuilder.setTables(ConversationImagePartsView.getViewName());
+ if (uri.getPathSegments().size() == 2) {
+ // Exclude draft.
+ queryBuilder.appendWhere(
+ ConversationImagePartsView.Columns.CONVERSATION_ID + " =? AND " +
+ ConversationImagePartsView.Columns.STATUS + "<>" +
+ MessageData.BUGLE_STATUS_OUTGOING_DRAFT);
+ // Get the conversation id from the uri
+ queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1));
+ } else {
+ throw new IllegalArgumentException("Malformed URI " + uri);
+ }
+ break;
+ case DRAFT_IMAGES_QUERY_CODE:
+ queryBuilder.setTables(ConversationImagePartsView.getViewName());
+ if (uri.getPathSegments().size() == 2) {
+ // Draft only.
+ queryBuilder.appendWhere(
+ ConversationImagePartsView.Columns.CONVERSATION_ID + " =? AND " +
+ ConversationImagePartsView.Columns.STATUS + "=" +
+ MessageData.BUGLE_STATUS_OUTGOING_DRAFT);
+ // Get the conversation id from the uri
+ queryArgs = prependArgs(queryArgs, uri.getPathSegments().get(1));
+ } else {
+ throw new IllegalArgumentException("Malformed URI " + uri);
+ }
+ break;
+ default: {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ final Cursor cursor = getDatabaseWrapper().query(queryBuilder, projection, selection,
+ queryArgs, groupBy, null, sortOrder, limit);
+ cursor.setNotificationUri(getContext().getContentResolver(), uri);
+ return cursor;
+ }
+
+ private Cursor queryConversationMessages(final String conversationId, final Uri notifyUri) {
+ final String[] queryArgs = { conversationId };
+ final Cursor cursor = getDatabaseWrapper().rawQuery(
+ ConversationMessageData.getConversationMessagesQuerySql(), queryArgs);
+ cursor.setNotificationUri(getContext().getContentResolver(), notifyUri);
+ return cursor;
+ }
+
+ @Override
+ public String getType(final Uri uri) {
+ final StringBuilder sb = new
+ StringBuilder("vnd.android.cursor.dir/vnd.android.messaging.");
+
+ switch (sURIMatcher.match(uri)) {
+ case CONVERSATIONS_QUERY_CODE: {
+ sb.append(CONVERSATIONS_QUERY);
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+ return sb.toString();
+ }
+
+ protected DatabaseHelper getDatabase() {
+ return DatabaseHelper.getInstance(getContext());
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(final Uri uri, final String fileMode)
+ throws FileNotFoundException {
+ throw new IllegalArgumentException("openFile not supported: " + uri);
+ }
+
+ @Override
+ public Uri insert(final Uri uri, final ContentValues values) {
+ throw new IllegalStateException("Insert not supported " + uri);
+ }
+
+ @Override
+ public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
+ throw new IllegalArgumentException("Delete not supported: " + uri);
+ }
+
+ @Override
+ public int update(final Uri uri, final ContentValues values, final String selection,
+ final String[] selectionArgs) {
+ throw new IllegalArgumentException("Update not supported: " + uri);
+ }
+
+ /**
+ * Prepends new arguments to the existing argument list.
+ *
+ * @param oldArgList The current list of arguments. May be {@code null}
+ * @param args The new arguments to prepend
+ * @return A new argument list with the given arguments prepended
+ */
+ private String[] prependArgs(final String[] oldArgList, final String... args) {
+ if (args == null || args.length == 0) {
+ return oldArgList;
+ }
+ final int oldArgCount = (oldArgList == null ? 0 : oldArgList.length);
+ final int newArgCount = args.length;
+
+ final String[] newArgs = new String[oldArgCount + newArgCount];
+ System.arraycopy(args, 0, newArgs, 0, newArgCount);
+ if (oldArgCount > 0) {
+ System.arraycopy(oldArgList, 0, newArgs, newArgCount, oldArgCount);
+ }
+ return newArgs;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
+ // First dump out the default SMS app package name
+ String defaultSmsApp = PhoneUtils.getDefault().getDefaultSmsApp();
+ if (TextUtils.isEmpty(defaultSmsApp)) {
+ if (OsUtil.isAtLeastKLP()) {
+ defaultSmsApp = "None";
+ } else {
+ defaultSmsApp = "None (pre-Kitkat)";
+ }
+ }
+ writer.println("Default SMS app: " + defaultSmsApp);
+ // Now dump logs
+ LogUtil.dump(writer);
+ }
+
+ @Override
+ public boolean onCreate() {
+ // This is going to wind up calling into createDatabase() below.
+ mDatabaseHelper = (DatabaseHelper) getDatabase();
+ // We cannot initialize mDatabaseWrapper yet as the Factory may not be initialized
+ return true;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/MmsFileProvider.java b/src/com/android/messaging/datamodel/MmsFileProvider.java
new file mode 100644
index 0000000..0022630
--- /dev/null
+++ b/src/com/android/messaging/datamodel/MmsFileProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+
+/**
+ * A very simple content provider that can serve mms files from our cache directory.
+ */
+public class MmsFileProvider extends FileProvider {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ @VisibleForTesting
+ static final String AUTHORITY = "com.android.messaging.datamodel.MmsFileProvider";
+ private static final String RAW_MMS_DIR = "rawmms";
+
+ /**
+ * Returns a uri that can be used to access a raw mms file.
+ *
+ * @return the URI for an raw mms file
+ */
+ public static Uri buildRawMmsUri() {
+ final Uri uri = FileProvider.buildFileUri(AUTHORITY, null);
+ final File file = getFile(uri.getPath());
+ if (!ensureFileExists(file)) {
+ LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
+ }
+ return uri;
+ }
+
+ @Override
+ File getFile(final String path, final String extension) {
+ return getFile(path);
+ }
+
+ public static File getFile(final Uri uri) {
+ return getFile(uri.getPath());
+ }
+
+ private static File getFile(final String path) {
+ final Context context = Factory.get().getApplicationContext();
+ return new File(getDirectory(context), path + ".dat");
+ }
+
+ private static File getDirectory(final Context context) {
+ return new File(context.getCacheDir(), RAW_MMS_DIR);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java b/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java
new file mode 100644
index 0000000..791ff34
--- /dev/null
+++ b/src/com/android/messaging/datamodel/NoConfirmationSmsSendService.java
@@ -0,0 +1,148 @@
+/*
+ * 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.datamodel;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.RemoteInput;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.action.InsertNewMessageAction;
+import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.conversationlist.ConversationListActivity;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Respond to a special intent and send an SMS message without the user's intervention, unless
+ * the intent extra "showUI" is true.
+ */
+public class NoConfirmationSmsSendService extends IntentService {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final String EXTRA_SUBSCRIPTION = "subscription";
+ public static final String EXTRA_SELF_ID = "self_id";
+
+ public NoConfirmationSmsSendService() {
+ // Class name will be the thread name.
+ super(NoConfirmationSmsSendService.class.getName());
+
+ // Intent should be redelivered if the process gets killed before completing the job.
+ setIntentRedelivery(true);
+ }
+
+ @Override
+ protected void onHandleIntent(final Intent intent) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "NoConfirmationSmsSendService onHandleIntent");
+ }
+
+ final String action = intent.getAction();
+ if (!TelephonyManager.ACTION_RESPOND_VIA_MESSAGE.equals(action)) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "NoConfirmationSmsSendService onHandleIntent wrong action: " +
+ action);
+ }
+ return;
+ }
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Called to send SMS but no extras");
+ }
+ return;
+ }
+
+ // Get all possible extras from intent
+ final String conversationId =
+ intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+ final String selfId = intent.getStringExtra(EXTRA_SELF_ID);
+ final boolean requiresMms = intent.getBooleanExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS,
+ false);
+ final String message = getText(intent, Intent.EXTRA_TEXT);
+ final String subject = getText(intent, Intent.EXTRA_SUBJECT);
+ final int subId = extras.getInt(EXTRA_SUBSCRIPTION, ParticipantData.DEFAULT_SELF_SUB_ID);
+
+ final Uri intentUri = intent.getData();
+ final String recipients = intentUri != null ? MmsUtils.getSmsRecipients(intentUri) : null;
+
+ if (TextUtils.isEmpty(recipients) && TextUtils.isEmpty(conversationId)) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Both conversationId and recipient(s) cannot be empty");
+ }
+ return;
+ }
+
+ if (extras.getBoolean("showUI", false)) {
+ startActivity(new Intent(this, ConversationListActivity.class));
+ } else {
+ if (TextUtils.isEmpty(message)) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Message cannot be empty");
+ }
+ return;
+ }
+
+ // TODO: it's possible that a long message would require sending it via mms,
+ // but we're not testing for that here and we're sending the message as an sms.
+
+ if (TextUtils.isEmpty(conversationId)) {
+ InsertNewMessageAction.insertNewMessage(subId, recipients, message, subject);
+ } else {
+ MessageData messageData = null;
+ if (requiresMms) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Auto-sending MMS message in conversation: " +
+ conversationId);
+ }
+ messageData = MessageData.createDraftMmsMessage(conversationId, selfId, message,
+ subject);
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Auto-sending SMS message in conversation: " +
+ conversationId);
+ }
+ messageData = MessageData.createDraftSmsMessage(conversationId, selfId,
+ message);
+ }
+ InsertNewMessageAction.insertNewMessage(messageData);
+ }
+ UpdateMessageNotificationAction.updateMessageNotification();
+ }
+ }
+
+ private String getText(final Intent intent, final String textType) {
+ final String message = intent.getStringExtra(textType);
+ if (message == null) {
+ final Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ final CharSequence extra = remoteInput.getCharSequence(textType);
+ if (extra != null) {
+ return extra.toString();
+ }
+ }
+ }
+ return message;
+ }
+
+}
diff --git a/src/com/android/messaging/datamodel/NotificationState.java b/src/com/android/messaging/datamodel/NotificationState.java
new file mode 100644
index 0000000..d589874
--- /dev/null
+++ b/src/com/android/messaging/datamodel/NotificationState.java
@@ -0,0 +1,149 @@
+/*
+ * 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.datamodel;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.ConversationIdSet;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * Base class for representing notifications. The main reason for this class is that in order to
+ * show pictures or avatars they might need to be loaded in the background. This class and
+ * subclasses can do the main work to get the notification ready and then wait until any images
+ * that are needed are ready before posting.
+ *
+ * The creation of a notification is split into two parts. The NotificationState ctor should
+ * setup the basic information including the mContentIntent. A Notification Builder is created in
+ * RealTimeChatNotifications and passed to the build() method of each notification where the
+ * Notification is fully specified.
+ *
+ * TODO: There is still some duplication and inconsistency in the utility functions and
+ * placement of different building blocks across notification types (e.g. summary text for accounts)
+ */
+public abstract class NotificationState {
+ private static final int CONTENT_INTENT_REQUEST_CODE_OFFSET = 0;
+ private static final int CLEAR_INTENT_REQUEST_CODE_OFFSET = 1;
+ private static final int NUM_REQUEST_CODES_NEEDED = 2;
+
+ public interface FailedMessageQuery {
+ static final String FAILED_MESSAGES_WHERE_CLAUSE =
+ "((" + MessageColumns.STATUS + " = " +
+ MessageData.BUGLE_STATUS_OUTGOING_FAILED + " OR " +
+ MessageColumns.STATUS + " = " +
+ MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED + ") AND " +
+ DatabaseHelper.MessageColumns.SEEN + " = 0)";
+
+ static final String FAILED_ORDER_BY = DatabaseHelper.MessageColumns.CONVERSATION_ID + ", " +
+ DatabaseHelper.MessageColumns.SENT_TIMESTAMP + " asc";
+ }
+
+ public final ConversationIdSet mConversationIds;
+ public final HashSet<String> mPeople;
+
+ public NotificationCompat.Style mNotificationStyle;
+ public NotificationCompat.Builder mNotificationBuilder;
+ public boolean mCanceled;
+ public int mType;
+ public int mBaseRequestCode;
+ public ArrayList<Uri> mParticipantAvatarsUris = null;
+ public ArrayList<Uri> mParticipantContactUris = null;
+
+ NotificationState(final ConversationIdSet conversationIds) {
+ mConversationIds = conversationIds;
+ mPeople = new HashSet<String>();
+ }
+
+ /**
+ * The intent to be triggered when the notification is dismissed.
+ */
+ public abstract PendingIntent getClearIntent();
+
+ protected Uri getAttachmentUri() {
+ return null;
+ }
+
+ // Returns the mime type of the attachment (See ContentType class for definitions)
+ protected String getAttachmentType() {
+ return null;
+ }
+
+ /**
+ * Build the notification using the given builder.
+ * @param builder
+ * @return The style of the notification.
+ */
+ protected abstract NotificationCompat.Style build(NotificationCompat.Builder builder);
+
+ protected void setAvatarUrlsForConversation(final String conversationId) {
+ }
+
+ protected void setPeopleForConversation(final String conversationId) {
+ }
+
+ /**
+ * Reserves request codes for this notification type. By default 2 codes are reserved, one for
+ * the main intent and another for the cancel intent. Override this function to reserve more.
+ */
+ public int getNumRequestCodesNeeded() {
+ return NUM_REQUEST_CODES_NEEDED;
+ }
+
+ public int getContentIntentRequestCode() {
+ return mBaseRequestCode + CONTENT_INTENT_REQUEST_CODE_OFFSET;
+ }
+
+ public int getClearIntentRequestCode() {
+ return mBaseRequestCode + CLEAR_INTENT_REQUEST_CODE_OFFSET;
+ }
+
+ /**
+ * Gets the appropriate icon needed for notifications.
+ */
+ public abstract int getIcon();
+
+ /**
+ * @return the type of notification that should be used from {@link RealTimeChatNotifications}
+ * so that the proper ringtone and vibrate settings can be used.
+ */
+ public int getLatestMessageNotificationType() {
+ return BugleNotifications.LOCAL_SMS_NOTIFICATION;
+ }
+
+ /**
+ * @return the notification priority level for this notification.
+ */
+ public abstract int getPriority();
+
+ /** @return custom ringtone URI or null if not set */
+ public String getRingtoneUri() {
+ return null;
+ }
+
+ public boolean getNotificationVibrate() {
+ return false;
+ }
+
+ public long getLatestReceivedTimestamp() {
+ return Long.MIN_VALUE;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/ParticipantRefresh.java b/src/com/android/messaging/datamodel/ParticipantRefresh.java
new file mode 100644
index 0000000..5324496
--- /dev/null
+++ b/src/com/android/messaging/datamodel/ParticipantRefresh.java
@@ -0,0 +1,738 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.graphics.Color;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.v4.util.ArrayMap;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationParticipantsColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.SafeAsyncTask;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Utility class for refreshing participant information based on matching contact. This updates
+ * 1. name, photo_uri, matching contact_id of participants.
+ * 2. generated_name of conversations.
+ *
+ * There are two kinds of participant refreshes,
+ * 1. Full refresh, this is triggered at application start or activity resumes after contact
+ * change is detected.
+ * 2. Partial refresh, this is triggered when a participant is added to a conversation. This
+ * normally happens during SMS sync.
+ */
+@VisibleForTesting
+public class ParticipantRefresh {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ /**
+ * Refresh all participants including ones that were resolved before.
+ */
+ public static final int REFRESH_MODE_FULL = 0;
+
+ /**
+ * Refresh all unresolved participants.
+ */
+ public static final int REFRESH_MODE_INCREMENTAL = 1;
+
+ /**
+ * Force refresh all self participants.
+ */
+ public static final int REFRESH_MODE_SELF_ONLY = 2;
+
+ public static class ConversationParticipantsQuery {
+ public static final String[] PROJECTION = new String[] {
+ ConversationParticipantsColumns._ID,
+ ConversationParticipantsColumns.CONVERSATION_ID,
+ ConversationParticipantsColumns.PARTICIPANT_ID
+ };
+
+ public static final int INDEX_ID = 0;
+ public static final int INDEX_CONVERSATION_ID = 1;
+ public static final int INDEX_PARTICIPANT_ID = 2;
+ }
+
+ // Track whether observer is initialized or not.
+ private static volatile boolean sObserverInitialized = false;
+ private static final Object sLock = new Object();
+ private static final AtomicBoolean sFullRefreshScheduled = new AtomicBoolean(false);
+ private static final Runnable sFullRefreshRunnable = new Runnable() {
+ @Override
+ public void run() {
+ final boolean oldScheduled = sFullRefreshScheduled.getAndSet(false);
+ Assert.isTrue(oldScheduled);
+ refreshParticipants(REFRESH_MODE_FULL);
+ }
+ };
+ private static final Runnable sSelfOnlyRefreshRunnable = new Runnable() {
+ @Override
+ public void run() {
+ refreshParticipants(REFRESH_MODE_SELF_ONLY);
+ }
+ };
+
+ /**
+ * A customized content resolver to track contact changes.
+ */
+ public static class ContactContentObserver extends ContentObserver {
+ private volatile boolean mContactChanged = false;
+
+ public ContactContentObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(final boolean selfChange) {
+ super.onChange(selfChange);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Contacts changed");
+ }
+ mContactChanged = true;
+ }
+
+ public boolean getContactChanged() {
+ return mContactChanged;
+ }
+
+ public void resetContactChanged() {
+ mContactChanged = false;
+ }
+
+ public void initialize() {
+ // TODO: Handle enterprise contacts post M once contacts provider supports it
+ Factory.get().getApplicationContext().getContentResolver().registerContentObserver(
+ Phone.CONTENT_URI, true, this);
+ mContactChanged = true; // Force a full refresh on initialization.
+ }
+ }
+
+ /**
+ * Refresh participants only if needed, i.e., application start or contact changed.
+ */
+ public static void refreshParticipantsIfNeeded() {
+ if (ParticipantRefresh.getNeedFullRefresh() &&
+ sFullRefreshScheduled.compareAndSet(false, true)) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Started full participant refresh");
+ }
+ SafeAsyncTask.executeOnThreadPool(sFullRefreshRunnable);
+ } else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Skipped full participant refresh");
+ }
+ }
+
+ /**
+ * Refresh self participants on subscription or settings change.
+ */
+ public static void refreshSelfParticipants() {
+ SafeAsyncTask.executeOnThreadPool(sSelfOnlyRefreshRunnable);
+ }
+
+ private static boolean getNeedFullRefresh() {
+ final ContactContentObserver observer = Factory.get().getContactContentObserver();
+ if (observer == null) {
+ // If there is no observer (for unittest cases), we don't need to refresh participants.
+ return false;
+ }
+
+ if (!sObserverInitialized) {
+ synchronized (sLock) {
+ if (!sObserverInitialized) {
+ observer.initialize();
+ sObserverInitialized = true;
+ }
+ }
+ }
+
+ return observer.getContactChanged();
+ }
+
+ private static void resetNeedFullRefresh() {
+ final ContactContentObserver observer = Factory.get().getContactContentObserver();
+ if (observer != null) {
+ observer.resetContactChanged();
+ }
+ }
+
+ /**
+ * This class is totally static. Make constructor to be private so that an instance
+ * of this class would not be created by by mistake.
+ */
+ private ParticipantRefresh() {
+ }
+
+ /**
+ * Refresh participants in Bugle.
+ *
+ * @param refreshMode the refresh mode desired. See {@link #REFRESH_MODE_FULL},
+ * {@link #REFRESH_MODE_INCREMENTAL}, and {@link #REFRESH_MODE_SELF_ONLY}
+ */
+ @VisibleForTesting
+ static void refreshParticipants(final int refreshMode) {
+ Assert.inRange(refreshMode, REFRESH_MODE_FULL, REFRESH_MODE_SELF_ONLY);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ switch (refreshMode) {
+ case REFRESH_MODE_FULL:
+ LogUtil.v(TAG, "Start full participant refresh");
+ break;
+ case REFRESH_MODE_INCREMENTAL:
+ LogUtil.v(TAG, "Start partial participant refresh");
+ break;
+ case REFRESH_MODE_SELF_ONLY:
+ LogUtil.v(TAG, "Start self participant refresh");
+ break;
+ }
+ }
+
+ if (!ContactUtil.hasReadContactsPermission() || !OsUtil.hasPhonePermission()) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Skipping participant referesh because of permissions");
+ }
+ return;
+ }
+
+ if (refreshMode == REFRESH_MODE_FULL) {
+ // resetNeedFullRefresh right away so that we will skip duplicated full refresh
+ // requests.
+ resetNeedFullRefresh();
+ }
+
+ if (refreshMode == REFRESH_MODE_FULL || refreshMode == REFRESH_MODE_SELF_ONLY) {
+ refreshSelfParticipantList();
+ }
+
+ final ArrayList<String> changedParticipants = new ArrayList<String>();
+
+ String selection = null;
+ String[] selectionArgs = null;
+
+ if (refreshMode == REFRESH_MODE_INCREMENTAL) {
+ // In case of incremental refresh, filter out participants that are already resolved.
+ selection = ParticipantColumns.CONTACT_ID + "=?";
+ selectionArgs = new String[] {
+ String.valueOf(ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED) };
+ } else if (refreshMode == REFRESH_MODE_SELF_ONLY) {
+ // In case of self-only refresh, filter out non-self participants.
+ selection = SELF_PARTICIPANTS_CLAUSE;
+ selectionArgs = null;
+ }
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ Cursor cursor = null;
+ boolean selfUpdated = false;
+ try {
+ cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null);
+
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ try {
+ final ParticipantData participantData =
+ ParticipantData.getFromCursor(cursor);
+ if (refreshParticipant(db, participantData)) {
+ if (participantData.isSelf()) {
+ selfUpdated = true;
+ }
+ updateParticipant(db, participantData);
+ final String id = participantData.getId();
+ changedParticipants.add(id);
+ }
+ } catch (final Exception exception) {
+ // Failure to update one participant shouldn't cancel the entire refresh.
+ // Log the failure so we know what's going on and resume the loop.
+ LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "ParticipantRefresh: Failed to " +
+ "update participant", exception);
+ }
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Number of participants refreshed:" + changedParticipants.size());
+ }
+
+ // Refresh conversations for participants that are changed.
+ if (changedParticipants.size() > 0) {
+ BugleDatabaseOperations.refreshConversationsForParticipants(changedParticipants);
+ }
+ if (selfUpdated) {
+ // Boom
+ MessagingContentProvider.notifyAllParticipantsChanged();
+ MessagingContentProvider.notifyAllMessagesChanged();
+ }
+ }
+
+ private static final String SELF_PARTICIPANTS_CLAUSE = ParticipantColumns.SUB_ID
+ + " NOT IN ( "
+ + ParticipantData.OTHER_THAN_SELF_SUB_ID
+ + " )";
+
+ private static final Set<Integer> getExistingSubIds() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final HashSet<Integer> existingSubIds = new HashSet<Integer>();
+
+ Cursor cursor = null;
+ try {
+ cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantsQuery.PROJECTION,
+ SELF_PARTICIPANTS_CLAUSE, null, null, null, null);
+
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ final int subId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
+ existingSubIds.add(subId);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return existingSubIds;
+ }
+
+ private static final String UPDATE_SELF_PARTICIPANT_SUBSCRIPTION_SQL =
+ "UPDATE " + DatabaseHelper.PARTICIPANTS_TABLE + " SET "
+ + ParticipantColumns.SIM_SLOT_ID + " = %d, "
+ + ParticipantColumns.SUBSCRIPTION_COLOR + " = %d, "
+ + ParticipantColumns.SUBSCRIPTION_NAME + " = %s "
+ + " WHERE %s";
+
+ static String getUpdateSelfParticipantSubscriptionInfoSql(final int slotId,
+ final int subscriptionColor, final String subscriptionName, final String where) {
+ return String.format((Locale) null /* construct SQL string without localization */,
+ UPDATE_SELF_PARTICIPANT_SUBSCRIPTION_SQL,
+ slotId, subscriptionColor, subscriptionName, where);
+ }
+
+ /**
+ * Ensure that there is a self participant corresponding to every active SIM. Also, ensure
+ * that any other older SIM self participants are marked as inactive.
+ */
+ private static void refreshSelfParticipantList() {
+ if (!OsUtil.isAtLeastL_MR1()) {
+ return;
+ }
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final List<SubscriptionInfo> subInfoRecords =
+ PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
+ final ArrayMap<Integer, SubscriptionInfo> activeSubscriptionIdToRecordMap =
+ new ArrayMap<Integer, SubscriptionInfo>();
+ db.beginTransaction();
+ final Set<Integer> existingSubIds = getExistingSubIds();
+
+ try {
+ if (subInfoRecords != null) {
+ for (final SubscriptionInfo subInfoRecord : subInfoRecords) {
+ final int subId = subInfoRecord.getSubscriptionId();
+ // If its a new subscription, add it to the database.
+ if (!existingSubIds.contains(subId)) {
+ db.execSQL(DatabaseHelper.getCreateSelfParticipantSql(subId));
+ // Add it to the local set to guard against duplicated entries returned
+ // by subscription manager.
+ existingSubIds.add(subId);
+ }
+ activeSubscriptionIdToRecordMap.put(subId, subInfoRecord);
+
+ if (subId == PhoneUtils.getDefault().getDefaultSmsSubscriptionId()) {
+ // This is the system default subscription, so update the default self.
+ activeSubscriptionIdToRecordMap.put(ParticipantData.DEFAULT_SELF_SUB_ID,
+ subInfoRecord);
+ }
+ }
+ }
+
+ // For subscriptions already in the database, refresh ParticipantColumns.SIM_SLOT_ID.
+ for (final Integer subId : activeSubscriptionIdToRecordMap.keySet()) {
+ final SubscriptionInfo record = activeSubscriptionIdToRecordMap.get(subId);
+ final String displayName =
+ DatabaseUtils.sqlEscapeString(record.getDisplayName().toString());
+ db.execSQL(getUpdateSelfParticipantSubscriptionInfoSql(record.getSimSlotIndex(),
+ record.getIconTint(), displayName,
+ ParticipantColumns.SUB_ID + " = " + subId));
+ }
+ db.execSQL(getUpdateSelfParticipantSubscriptionInfoSql(
+ ParticipantData.INVALID_SLOT_ID, Color.TRANSPARENT, "''",
+ ParticipantColumns.SUB_ID + " NOT IN (" +
+ Joiner.on(", ").join(activeSubscriptionIdToRecordMap.keySet()) + ")"));
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ // Fix up conversation self ids by reverting to default self for conversations whose self
+ // ids are no longer active.
+ refreshConversationSelfIds();
+ }
+
+ /**
+ * Refresh one participant.
+ * @return true if the ParticipantData was changed
+ */
+ public static boolean refreshParticipant(final DatabaseWrapper db,
+ final ParticipantData participantData) {
+ boolean updated = false;
+
+ if (participantData.isSelf()) {
+ final int selfChange = refreshFromSelfProfile(db, participantData);
+
+ if (selfChange == SELF_PROFILE_EXISTS) {
+ // If a self-profile exists, it takes precedence over Contacts data. So we are done.
+ return true;
+ }
+
+ updated = (selfChange == SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED);
+
+ // Fall-through and try to update based on Contacts data
+ }
+
+ updated |= refreshFromContacts(db, participantData);
+ return updated;
+ }
+
+ private static final int SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED = 1;
+ private static final int SELF_PROFILE_EXISTS = 2;
+
+ private static int refreshFromSelfProfile(final DatabaseWrapper db,
+ final ParticipantData participantData) {
+ int changed = 0;
+ // Refresh the phone number based on information from telephony
+ if (participantData.updatePhoneNumberForSelfIfChanged()) {
+ changed = SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED;
+ }
+
+ if (OsUtil.isAtLeastL_MR1()) {
+ // Refresh the subscription info based on information from SubscriptionManager.
+ final SubscriptionInfo subscriptionInfo =
+ PhoneUtils.get(participantData.getSubId()).toLMr1().getActiveSubscriptionInfo();
+ if (participantData.updateSubscriptionInfoForSelfIfChanged(subscriptionInfo)) {
+ changed = SELF_PHONE_NUMBER_OR_SUBSCRIPTION_CHANGED;
+ }
+ }
+
+ // For self participant, try getting name/avatar from self profile in CP2 first.
+ // TODO: in case of multi-sim, profile would not be able to be used for
+ // different numbers. Need to figure out that.
+ Cursor selfCursor = null;
+ try {
+ selfCursor = ContactUtil.getSelf(db.getContext()).performSynchronousQuery();
+ if (selfCursor != null && selfCursor.getCount() > 0) {
+ selfCursor.moveToNext();
+ final long selfContactId = selfCursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ participantData.setContactId(selfContactId);
+ participantData.setFullName(selfCursor.getString(
+ ContactUtil.INDEX_DISPLAY_NAME));
+ participantData.setFirstName(
+ ContactUtil.lookupFirstName(db.getContext(), selfContactId));
+ participantData.setProfilePhotoUri(selfCursor.getString(
+ ContactUtil.INDEX_PHOTO_URI));
+ participantData.setLookupKey(selfCursor.getString(
+ ContactUtil.INDEX_SELF_QUERY_LOOKUP_KEY));
+ return SELF_PROFILE_EXISTS;
+ }
+ } catch (final Exception exception) {
+ // It's possible for contact query to fail and we don't want that to crash our app.
+ // However, we need to at least log the exception so we know something was wrong.
+ LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Participant refresh: failed to refresh " +
+ "participant. exception=" + exception);
+ } finally {
+ if (selfCursor != null) {
+ selfCursor.close();
+ }
+ }
+ return changed;
+ }
+
+ private static boolean refreshFromContacts(final DatabaseWrapper db,
+ final ParticipantData participantData) {
+ final String normalizedDestination = participantData.getNormalizedDestination();
+ final long currentContactId = participantData.getContactId();
+ final String currentDisplayName = participantData.getFullName();
+ final String currentFirstName = participantData.getFirstName();
+ final String currentPhotoUri = participantData.getProfilePhotoUri();
+ final String currentContactDestination = participantData.getContactDestination();
+
+ Cursor matchingContactCursor = null;
+ long matchingContactId = -1;
+ String matchingDisplayName = null;
+ String matchingFirstName = null;
+ String matchingPhotoUri = null;
+ String matchingLookupKey = null;
+ String matchingDestination = null;
+ boolean updated = false;
+
+ if (TextUtils.isEmpty(normalizedDestination)) {
+ // The normalized destination can be "" for the self id if we can't get it from the
+ // SIM. Some contact providers throw an IllegalArgumentException if you lookup "",
+ // so we early out.
+ return false;
+ }
+
+ try {
+ matchingContactCursor = ContactUtil.lookupDestination(db.getContext(),
+ normalizedDestination).performSynchronousQuery();
+ if (matchingContactCursor == null || matchingContactCursor.getCount() == 0) {
+ // If there is no match, mark the participant as contact not found.
+ if (currentContactId != ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND) {
+ participantData.setContactId(ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND);
+ participantData.setFullName(null);
+ participantData.setFirstName(null);
+ participantData.setProfilePhotoUri(null);
+ participantData.setLookupKey(null);
+ updated = true;
+ }
+ return updated;
+ }
+
+ while (matchingContactCursor.moveToNext()) {
+ final long contactId = matchingContactCursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ // Pick either the first contact or the contact with same id as previous matched
+ // contact id.
+ if (matchingContactId == -1 || currentContactId == contactId) {
+ matchingContactId = contactId;
+ matchingDisplayName = matchingContactCursor.getString(
+ ContactUtil.INDEX_DISPLAY_NAME);
+ matchingFirstName = ContactUtil.lookupFirstName(db.getContext(), contactId);
+ matchingPhotoUri = matchingContactCursor.getString(
+ ContactUtil.INDEX_PHOTO_URI);
+ matchingLookupKey = matchingContactCursor.getString(
+ ContactUtil.INDEX_LOOKUP_KEY);
+ matchingDestination = matchingContactCursor.getString(
+ ContactUtil.INDEX_PHONE_EMAIL);
+ }
+
+ // There is no need to try other contacts if the current contactId was not filled...
+ if (currentContactId < 0
+ // or we found the matching contact id
+ || currentContactId == contactId) {
+ break;
+ }
+ }
+ } catch (final Exception exception) {
+ // It's possible for contact query to fail and we don't want that to crash our app.
+ // However, we need to at least log the exception so we know something was wrong.
+ LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Participant refresh: failed to refresh " +
+ "participant. exception=" + exception);
+ return false;
+ } finally {
+ if (matchingContactCursor != null) {
+ matchingContactCursor.close();
+ }
+ }
+
+ // Update participant only if something changed.
+ final boolean isContactIdChanged = (matchingContactId != currentContactId);
+ final boolean isDisplayNameChanged =
+ !TextUtils.equals(matchingDisplayName, currentDisplayName);
+ final boolean isFirstNameChanged = !TextUtils.equals(matchingFirstName, currentFirstName);
+ final boolean isPhotoUrlChanged = !TextUtils.equals(matchingPhotoUri, currentPhotoUri);
+ final boolean isDestinationChanged = !TextUtils.equals(matchingDestination,
+ currentContactDestination);
+
+ if (isContactIdChanged || isDisplayNameChanged || isFirstNameChanged || isPhotoUrlChanged
+ || isDestinationChanged) {
+ participantData.setContactId(matchingContactId);
+ participantData.setFullName(matchingDisplayName);
+ participantData.setFirstName(matchingFirstName);
+ participantData.setProfilePhotoUri(matchingPhotoUri);
+ participantData.setLookupKey(matchingLookupKey);
+ participantData.setContactDestination(matchingDestination);
+ if (isDestinationChanged) {
+ // Update the send destination to the new one entered by user in Contacts.
+ participantData.setSendDestination(matchingDestination);
+ }
+ updated = true;
+ }
+
+ return updated;
+ }
+
+ /**
+ * Update participant with matching contact's contactId, displayName and photoUri.
+ */
+ private static void updateParticipant(final DatabaseWrapper db,
+ final ParticipantData participantData) {
+ final ContentValues values = new ContentValues();
+ if (participantData.isSelf()) {
+ // Self participants can refresh their normalized phone numbers
+ values.put(ParticipantColumns.NORMALIZED_DESTINATION,
+ participantData.getNormalizedDestination());
+ values.put(ParticipantColumns.DISPLAY_DESTINATION,
+ participantData.getDisplayDestination());
+ }
+ values.put(ParticipantColumns.CONTACT_ID, participantData.getContactId());
+ values.put(ParticipantColumns.LOOKUP_KEY, participantData.getLookupKey());
+ values.put(ParticipantColumns.FULL_NAME, participantData.getFullName());
+ values.put(ParticipantColumns.FIRST_NAME, participantData.getFirstName());
+ values.put(ParticipantColumns.PROFILE_PHOTO_URI, participantData.getProfilePhotoUri());
+ values.put(ParticipantColumns.CONTACT_DESTINATION, participantData.getContactDestination());
+ values.put(ParticipantColumns.SEND_DESTINATION, participantData.getSendDestination());
+
+ db.beginTransaction();
+ try {
+ db.update(DatabaseHelper.PARTICIPANTS_TABLE, values, ParticipantColumns._ID + "=?",
+ new String[] { participantData.getId() });
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Get a list of inactive self ids in the participants table.
+ */
+ private static List<String> getInactiveSelfParticipantIds() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final List<String> inactiveSelf = new ArrayList<String>();
+
+ final String selection = ParticipantColumns.SIM_SLOT_ID + "=? AND " +
+ SELF_PARTICIPANTS_CLAUSE;
+ Cursor cursor = null;
+ try {
+ cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ new String[] { ParticipantColumns._ID },
+ selection, new String[] { String.valueOf(ParticipantData.INVALID_SLOT_ID) },
+ null, null, null);
+
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ final String participantId = cursor.getString(0);
+ inactiveSelf.add(participantId);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return inactiveSelf;
+ }
+
+ /**
+ * Gets a list of conversations with the given self ids.
+ */
+ private static List<String> getConversationsWithSelfParticipantIds(final List<String> selfIds) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final List<String> conversationIds = new ArrayList<String>();
+
+ Cursor cursor = null;
+ try {
+ final StringBuilder selectionList = new StringBuilder();
+ for (int i = 0; i < selfIds.size(); i++) {
+ selectionList.append('?');
+ if (i < selfIds.size() - 1) {
+ selectionList.append(',');
+ }
+ }
+ final String selection =
+ ConversationColumns.CURRENT_SELF_ID + " IN (" + selectionList + ")";
+ cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns._ID },
+ selection, selfIds.toArray(new String[0]),
+ null, null, null);
+
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ final String conversationId = cursor.getString(0);
+ conversationIds.add(conversationId);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return conversationIds;
+ }
+
+ /**
+ * Refresh one conversation's self id.
+ */
+ private static void updateConversationSelfId(final String conversationId,
+ final String selfId) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ db.beginTransaction();
+ try {
+ BugleDatabaseOperations.updateConversationSelfIdInTransaction(db, conversationId,
+ selfId);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
+ UIIntents.get().broadcastConversationSelfIdChange(db.getContext(), conversationId, selfId);
+ }
+
+ /**
+ * After refreshing the self participant list, find all conversations with inactive self ids,
+ * and switch them back to system default.
+ */
+ private static void refreshConversationSelfIds() {
+ final List<String> inactiveSelfs = getInactiveSelfParticipantIds();
+ if (inactiveSelfs.size() == 0) {
+ return;
+ }
+ final List<String> conversationsToRefresh =
+ getConversationsWithSelfParticipantIds(inactiveSelfs);
+ if (conversationsToRefresh.size() == 0) {
+ return;
+ }
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final ParticipantData defaultSelf =
+ BugleDatabaseOperations.getOrCreateSelf(db, ParticipantData.DEFAULT_SELF_SUB_ID);
+
+ if (defaultSelf != null) {
+ for (final String conversationId : conversationsToRefresh) {
+ updateConversationSelfId(conversationId, defaultSelf.getId());
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/SyncManager.java b/src/com/android/messaging/datamodel/SyncManager.java
new file mode 100644
index 0000000..b3571bf
--- /dev/null
+++ b/src/com/android/messaging/datamodel/SyncManager.java
@@ -0,0 +1,478 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.support.v4.util.LongSparseArray;
+
+import com.android.messaging.datamodel.action.SyncMessagesAction;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * This class manages message sync with the Telephony SmsProvider/MmsProvider.
+ */
+public class SyncManager {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ /**
+ * Record of any user customization to conversation settings
+ */
+ public static class ConversationCustomization {
+ private final boolean mArchived;
+ private final boolean mMuted;
+ private final boolean mNoVibrate;
+ private final String mNotificationSoundUri;
+
+ public ConversationCustomization(final boolean archived, final boolean muted,
+ final boolean noVibrate, final String notificationSoundUri) {
+ mArchived = archived;
+ mMuted = muted;
+ mNoVibrate = noVibrate;
+ mNotificationSoundUri = notificationSoundUri;
+ }
+
+ public boolean isArchived() {
+ return mArchived;
+ }
+
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ public boolean noVibrate() {
+ return mNoVibrate;
+ }
+
+ public String getNotificationSoundUri() {
+ return mNotificationSoundUri;
+ }
+ }
+
+ SyncManager() {
+ }
+
+ /**
+ * Timestamp of in progress sync - used to keep track of whether sync is running
+ */
+ private long mSyncInProgressTimestamp = -1;
+
+ /**
+ * Timestamp of current sync batch upper bound - used to determine if message makes batch dirty
+ */
+ private long mCurrentUpperBoundTimestamp = -1;
+
+ /**
+ * Timestamp of messages inserted since sync batch started - used to determine if batch dirty
+ */
+ private long mMaxRecentChangeTimestamp = -1L;
+
+ private final ThreadInfoCache mThreadInfoCache = new ThreadInfoCache();
+
+ /**
+ * User customization to conversations. If this is set, we need to recover them after
+ * a full sync.
+ */
+ private LongSparseArray<ConversationCustomization> mCustomization = null;
+
+ /**
+ * Start an incremental sync (backed off a few seconds)
+ */
+ public static void sync() {
+ SyncMessagesAction.sync();
+ }
+
+ /**
+ * Start an incremental sync (with no backoff)
+ */
+ public static void immediateSync() {
+ SyncMessagesAction.immediateSync();
+ }
+
+ /**
+ * Start a full sync (for debugging)
+ */
+ public static void forceSync() {
+ SyncMessagesAction.fullSync();
+ }
+
+ /**
+ * Called from data model thread when starting a sync batch
+ * @param upperBoundTimestamp upper bound timestamp for sync batch
+ */
+ public synchronized void startSyncBatch(final long upperBoundTimestamp) {
+ Assert.isTrue(mCurrentUpperBoundTimestamp < 0);
+ mCurrentUpperBoundTimestamp = upperBoundTimestamp;
+ mMaxRecentChangeTimestamp = -1L;
+ }
+
+ /**
+ * Called from data model thread at end of batch to determine if any messages added in window
+ * @param lowerBoundTimestamp lower bound timestamp for sync batch
+ * @return true if message added within window from lower to upper bound timestamp of batch
+ */
+ public synchronized boolean isBatchDirty(final long lowerBoundTimestamp) {
+ Assert.isTrue(mCurrentUpperBoundTimestamp >= 0);
+ final long max = mMaxRecentChangeTimestamp;
+
+ final boolean dirty = (max >= 0 && max >= lowerBoundTimestamp);
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: Sync batch of messages from " + lowerBoundTimestamp
+ + " to " + mCurrentUpperBoundTimestamp + " is "
+ + (dirty ? "DIRTY" : "clean") + "; max change timestamp = "
+ + mMaxRecentChangeTimestamp);
+ }
+
+ mCurrentUpperBoundTimestamp = -1L;
+ mMaxRecentChangeTimestamp = -1L;
+
+ return dirty;
+ }
+
+ /**
+ * Called from data model or background worker thread to indicate start of message add process
+ * (add must complete on that thread before action transitions to new thread/stage)
+ * @param timestamp timestamp of message being added
+ */
+ public synchronized void onNewMessageInserted(final long timestamp) {
+ if (mCurrentUpperBoundTimestamp >= 0 && timestamp <= mCurrentUpperBoundTimestamp) {
+ // Message insert in current sync window
+ mMaxRecentChangeTimestamp = Math.max(mCurrentUpperBoundTimestamp, timestamp);
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " before upper bound of "
+ + "current sync batch " + mCurrentUpperBoundTimestamp);
+ }
+ } else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " after upper bound of "
+ + "current sync batch " + mCurrentUpperBoundTimestamp);
+ }
+ }
+
+ /**
+ * Synchronously checks whether sync is allowed and starts sync if allowed
+ * @param full - true indicates a full (not incremental) sync operation
+ * @param startTimestamp - starttimestamp for this sync (if allowed)
+ * @return - true if sync should start
+ */
+ public synchronized boolean shouldSync(final boolean full, final long startTimestamp) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncManager: Checking shouldSync " + (full ? "full " : "")
+ + "at " + startTimestamp);
+ }
+
+ if (full) {
+ final long delayUntilFullSync = delayUntilFullSync(startTimestamp);
+ if (delayUntilFullSync > 0) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: Full sync requested for " + startTimestamp
+ + " delayed for " + delayUntilFullSync + " ms");
+ }
+ return false;
+ }
+ }
+
+ if (isSyncing()) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: Not allowed to " + (full ? "full " : "")
+ + "sync yet; still running sync started at " + mSyncInProgressTimestamp);
+ }
+ return false;
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: Starting " + (full ? "full " : "") + "sync at "
+ + startTimestamp);
+ }
+
+ mSyncInProgressTimestamp = startTimestamp;
+
+ return true;
+ }
+
+ /**
+ * Return delay (in ms) until allowed to run a full sync (0 meaning can run immediately)
+ * @param startTimestamp Timestamp used to start the sync
+ * @return 0 if allowed to run now, else delay in ms
+ */
+ public long delayUntilFullSync(final long startTimestamp) {
+ final BugleGservices bugleGservices = BugleGservices.get();
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+
+ final long lastFullSyncTime = prefs.getLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1L);
+ final long smsFullSyncBackoffTimeMillis = bugleGservices.getLong(
+ BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS,
+ BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
+ final long noFullSyncBefore = (lastFullSyncTime < 0 ? startTimestamp :
+ lastFullSyncTime + smsFullSyncBackoffTimeMillis);
+
+ final long delayUntilFullSync = noFullSyncBefore - startTimestamp;
+ if (delayUntilFullSync > 0) {
+ return delayUntilFullSync;
+ }
+ return 0;
+ }
+
+ /**
+ * Check if sync currently in progress (public for asserts/logging).
+ */
+ public synchronized boolean isSyncing() {
+ return (mSyncInProgressTimestamp >= 0);
+ }
+
+ /**
+ * Check if sync batch should be in progress - compares upperBound with in memory value
+ * @param upperBoundTimestamp - upperbound timestamp for sync batch
+ * @return - true if timestamps match (otherwise batch is orphan from older process)
+ */
+ public synchronized boolean isSyncing(final long upperBoundTimestamp) {
+ Assert.isTrue(upperBoundTimestamp >= 0);
+ return (upperBoundTimestamp == mCurrentUpperBoundTimestamp);
+ }
+
+ /**
+ * Check if sync has completed for the first time.
+ */
+ public boolean getHasFirstSyncCompleted() {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ return prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME,
+ BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT) !=
+ BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT;
+ }
+
+ /**
+ * Called once sync is complete
+ */
+ public synchronized void complete() {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: Sync started at " + mSyncInProgressTimestamp
+ + " marked as complete");
+ }
+ mSyncInProgressTimestamp = -1L;
+ // Conversation customization only used once
+ mCustomization = null;
+ }
+
+ private final ContentObserver mMmsSmsObserver = new TelephonyMessagesObserver();
+ private boolean mSyncOnChanges = false;
+ private boolean mNotifyOnChanges = false;
+
+ /**
+ * Register content observer when necessary and kick off a catch up sync
+ */
+ public void updateSyncObserver(final Context context) {
+ registerObserver(context);
+ // Trigger an sms sync in case we missed and messages before registering this observer or
+ // becoming the SMS provider.
+ immediateSync();
+ }
+
+ private void registerObserver(final Context context) {
+ if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
+ // Not default SMS app - need to actively monitor telephony but not notify
+ mNotifyOnChanges = false;
+ mSyncOnChanges = true;
+ } else if (OsUtil.isSecondaryUser()){
+ // Secondary users default SMS app - need to actively monitor telephony and notify
+ mNotifyOnChanges = true;
+ mSyncOnChanges = true;
+ } else {
+ // Primary users default SMS app - don't monitor telephony (most changes from this app)
+ mNotifyOnChanges = false;
+ mSyncOnChanges = false;
+ }
+ if (mNotifyOnChanges || mSyncOnChanges) {
+ context.getContentResolver().registerContentObserver(Telephony.MmsSms.CONTENT_URI,
+ true, mMmsSmsObserver);
+ } else {
+ context.getContentResolver().unregisterContentObserver(mMmsSmsObserver);
+ }
+ }
+
+ public synchronized void setCustomization(
+ final LongSparseArray<ConversationCustomization> customization) {
+ this.mCustomization = customization;
+ }
+
+ public synchronized ConversationCustomization getCustomizationForThread(final long threadId) {
+ if (mCustomization != null) {
+ return mCustomization.get(threadId);
+ }
+ return null;
+ }
+
+ public static void resetLastSyncTimestamps() {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME,
+ BuglePrefsKeys.LAST_FULL_SYNC_TIME_DEFAULT);
+ prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT);
+ }
+
+ private class TelephonyMessagesObserver extends ContentObserver {
+ public TelephonyMessagesObserver() {
+ // Just run on default thread
+ super(null);
+ }
+
+ // Implement the onChange(boolean) method to delegate the change notification to
+ // the onChange(boolean, Uri) method to ensure correct operation on older versions
+ // of the framework that did not have the onChange(boolean, Uri) method.
+ @Override
+ public void onChange(final boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
+ @Override
+ public void onChange(final boolean selfChange, final Uri uri) {
+ // Handle change.
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncManager: Sms/Mms DB changed @" + System.currentTimeMillis()
+ + " for " + (uri == null ? "<unk>" : uri.toString()) + " "
+ + mSyncOnChanges + "/" + mNotifyOnChanges);
+ }
+
+ if (mSyncOnChanges) {
+ // If sync is already running this will do nothing - but at end of each sync
+ // action there is a check for recent messages that should catch new changes.
+ SyncManager.immediateSync();
+ }
+ if (mNotifyOnChanges) {
+ // TODO: Secondary users are not going to get notifications
+ }
+ }
+ }
+
+ public ThreadInfoCache getThreadInfoCache() {
+ return mThreadInfoCache;
+ }
+
+ public static class ThreadInfoCache {
+ // Cache of thread->conversationId map
+ private final LongSparseArray<String> mThreadToConversationId =
+ new LongSparseArray<String>();
+
+ // Cache of thread->recipients map
+ private final LongSparseArray<List<String>> mThreadToRecipients =
+ new LongSparseArray<List<String>>();
+
+ // Remember the conversation ids that need to be archived
+ private final HashSet<String> mArchivedConversations = new HashSet<>();
+
+ public synchronized void clear() {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncManager: Cleared ThreadInfoCache");
+ }
+ mThreadToConversationId.clear();
+ mThreadToRecipients.clear();
+ mArchivedConversations.clear();
+ }
+
+ public synchronized boolean isArchived(final String conversationId) {
+ return mArchivedConversations.contains(conversationId);
+ }
+
+ /**
+ * Get or create a conversation based on the message's thread id
+ *
+ * @param threadId The message's thread
+ * @param refSubId The subId used for normalizing phone numbers in the thread
+ * @param customization The user setting customization to the conversation if any
+ * @return The existing conversation id or new conversation id
+ */
+ public synchronized String getOrCreateConversation(final DatabaseWrapper db,
+ final long threadId, int refSubId, final ConversationCustomization customization) {
+ // This function has several components which need to be atomic.
+ Assert.isTrue(db.getDatabase().inTransaction());
+
+ // If we already have this conversation ID in our local map, just return it
+ String conversationId = mThreadToConversationId.get(threadId);
+ if (conversationId != null) {
+ return conversationId;
+ }
+
+ final List<String> recipients = getThreadRecipients(threadId);
+ final ArrayList<ParticipantData> participants =
+ BugleDatabaseOperations.getConversationParticipantsFromRecipients(recipients,
+ refSubId);
+
+ if (customization != null) {
+ // There is user customization we need to recover
+ conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
+ customization.isArchived(), participants, customization.isMuted(),
+ customization.noVibrate(), customization.getNotificationSoundUri());
+ if (customization.isArchived()) {
+ mArchivedConversations.add(conversationId);
+ }
+ } else {
+ conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
+ false/*archived*/, participants, false/*noNotification*/,
+ false/*noVibrate*/, null/*soundUri*/);
+ }
+
+ if (conversationId != null) {
+ mThreadToConversationId.put(threadId, conversationId);
+ return conversationId;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Load the recipients of a thread from telephony provider. If we fail, use
+ * a predefined unknown recipient. This should not return null.
+ *
+ * @param threadId
+ */
+ public synchronized List<String> getThreadRecipients(final long threadId) {
+ List<String> recipients = mThreadToRecipients.get(threadId);
+ if (recipients == null) {
+ recipients = MmsUtils.getRecipientsByThread(threadId);
+ if (recipients != null && recipients.size() > 0) {
+ mThreadToRecipients.put(threadId, recipients);
+ }
+ }
+
+ if (recipients == null || recipients.isEmpty()) {
+ LogUtil.w(TAG, "SyncManager : using unknown sender since thread " + threadId +
+ " couldn't find any recipients.");
+
+ // We want to try our best to load the messages,
+ // so if recipient info is broken, try to fix it with unknown recipient
+ recipients = Lists.newArrayList();
+ recipients.add(ParticipantData.getUnknownSenderDestination());
+ }
+
+ return recipients;
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/Action.java b/src/com/android/messaging/datamodel/action/Action.java
new file mode 100644
index 0000000..e4c332e
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/Action.java
@@ -0,0 +1,295 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DataModelException;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener;
+import com.android.messaging.util.LogUtil;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Base class for operations that perform application business logic off the main UI thread while
+ * holding a wake lock.
+ * .
+ * Note all derived classes need to provide real implementation of Parcelable (this is abstract)
+ */
+public abstract class Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ // Members holding the parameters common to all actions - no action state
+ public final String actionKey;
+
+ // If derived classes keep their data in actionParameters then parcelable is trivial
+ protected Bundle actionParameters;
+
+ // This does not get written to the parcel
+ private final List<Action> mBackgroundActions = new LinkedList<Action>();
+
+ /**
+ * Process the action locally - runs on action service thread.
+ * TODO: Currently, there is no way for this method to indicate failure
+ * @return result to be passed in to {@link ActionExecutedListener#onActionExecuted}. It is
+ * also the result passed in to {@link ActionCompletedListener#onActionSucceeded} if
+ * there is no background work.
+ */
+ protected Object executeAction() {
+ return null;
+ }
+
+ /**
+ * Queues up background work ie. {@link #doBackgroundWork} will be called on the
+ * background worker thread.
+ */
+ protected void requestBackgroundWork() {
+ mBackgroundActions.add(this);
+ }
+
+ /**
+ * Queues up background actions for background processing after the current action has
+ * completed its processing ({@link #executeAction}, {@link processBackgroundCompletion}
+ * or {@link #processBackgroundFailure}) on the Action thread.
+ * @param backgroundAction
+ */
+ protected void requestBackgroundWork(final Action backgroundAction) {
+ mBackgroundActions.add(backgroundAction);
+ }
+
+ /**
+ * Return flag indicating if any actions have been queued
+ */
+ public boolean hasBackgroundActions() {
+ return !mBackgroundActions.isEmpty();
+ }
+
+ /**
+ * Send queued actions to the background worker provided
+ */
+ public void sendBackgroundActions(final BackgroundWorker worker) {
+ worker.queueBackgroundWork(mBackgroundActions);
+ mBackgroundActions.clear();
+ }
+
+ /**
+ * Do work in a long running background worker thread.
+ * {@link #requestBackgroundWork} needs to be called for this method to
+ * be called. {@link #processBackgroundFailure} will be called on the Action service thread
+ * if this method throws {@link DataModelException}.
+ * @return response that is to be passed to {@link #processBackgroundResponse}
+ */
+ protected Bundle doBackgroundWork() throws DataModelException {
+ return null;
+ }
+
+ /**
+ * Process the success response from the background worker. Runs on action service thread.
+ * @param response the response returned by {@link #doBackgroundWork}
+ * @return result to be passed in to {@link ActionCompletedListener#onActionSucceeded}
+ */
+ protected Object processBackgroundResponse(final Bundle response) {
+ return null;
+ }
+
+ /**
+ * Called in case of failures when sending background actions. Runs on action service thread
+ * @return result to be passed in to {@link ActionCompletedListener#onActionFailed}
+ */
+ protected Object processBackgroundFailure() {
+ return null;
+ }
+
+ /**
+ * Constructor
+ */
+ protected Action(final String key) {
+ this.actionKey = key;
+ this.actionParameters = new Bundle();
+ }
+
+ /**
+ * Constructor
+ */
+ protected Action() {
+ this.actionKey = generateUniqueActionKey(getClass().getSimpleName());
+ this.actionParameters = new Bundle();
+ }
+
+ /**
+ * Queue an action and monitor for processing by the ActionService via the factory helper
+ */
+ protected void start(final ActionMonitor monitor) {
+ ActionMonitor.registerActionMonitor(this.actionKey, monitor);
+ DataModel.startActionService(this);
+ }
+
+ /**
+ * Queue an action for processing by the ActionService via the factory helper
+ */
+ public void start() {
+ DataModel.startActionService(this);
+ }
+
+ /**
+ * Queue an action for delayed processing by the ActionService via the factory helper
+ */
+ public void schedule(final int requestCode, final long delayMs) {
+ DataModel.scheduleAction(this, requestCode, delayMs);
+ }
+
+ /**
+ * Called when action queues ActionService intent
+ */
+ protected final void markStart() {
+ ActionMonitor.setState(this, ActionMonitor.STATE_CREATED,
+ ActionMonitor.STATE_QUEUED);
+ }
+
+ /**
+ * Mark the beginning of local action execution
+ */
+ protected final void markBeginExecute() {
+ ActionMonitor.setState(this, ActionMonitor.STATE_QUEUED,
+ ActionMonitor.STATE_EXECUTING);
+ }
+
+ /**
+ * Mark the end of local action execution - either completes the action or queues
+ * background actions
+ */
+ protected final void markEndExecute(final Object result) {
+ final boolean hasBackgroundActions = hasBackgroundActions();
+ ActionMonitor.setExecutedState(this, ActionMonitor.STATE_EXECUTING,
+ hasBackgroundActions, result);
+ if (!hasBackgroundActions) {
+ ActionMonitor.setCompleteState(this, ActionMonitor.STATE_EXECUTING,
+ result, true);
+ }
+ }
+
+ /**
+ * Update action state to indicate that the background worker is starting
+ */
+ protected final void markBackgroundWorkStarting() {
+ ActionMonitor.setState(this,
+ ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
+ }
+
+ /**
+ * Update action state to indicate that the background worker has posted its response
+ * (or failure) to the Action service
+ */
+ protected final void markBackgroundCompletionQueued() {
+ ActionMonitor.setState(this,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
+ ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED);
+ }
+
+ /**
+ * Update action state to indicate the background action failed but is being re-queued for retry
+ */
+ protected final void markBackgroundWorkQueued() {
+ ActionMonitor.setState(this,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
+ ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
+ }
+
+ /**
+ * Called by ActionService to process a response from the background worker
+ * @param response the response returned by {@link #doBackgroundWork}
+ */
+ protected final void processBackgroundWorkResponse(final Bundle response) {
+ ActionMonitor.setState(this,
+ ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED,
+ ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE);
+ final Object result = processBackgroundResponse(response);
+ ActionMonitor.setCompleteState(this,
+ ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE, result, true);
+ }
+
+ /**
+ * Called by ActionService when a background action fails
+ */
+ protected final void processBackgroundWorkFailure() {
+ final Object result = processBackgroundFailure();
+ ActionMonitor.setCompleteState(this, ActionMonitor.STATE_UNDEFINED,
+ result, false);
+ }
+
+ private static final Object sLock = new Object();
+ private static long sActionIdx = System.currentTimeMillis() * 1000;
+
+ /**
+ * Helper method to generate a unique operation index
+ */
+ protected static long getActionIdx() {
+ long idx = 0;
+ synchronized (sLock) {
+ idx = ++sActionIdx;
+ }
+ return idx;
+ }
+
+ /**
+ * This helper can be used to generate a unique key used to identify an action.
+ * @param baseKey - key generated to identify the action parameters
+ * @return - composite key generated by appending unique index
+ */
+ protected static String generateUniqueActionKey(final String baseKey) {
+ final StringBuilder key = new StringBuilder();
+ if (!TextUtils.isEmpty(baseKey)) {
+ key.append(baseKey);
+ }
+ key.append(":");
+ key.append(getActionIdx());
+ return key.toString();
+ }
+
+ /**
+ * Most derived classes use this base implementation (unless they include files handles)
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Derived classes need to implement writeToParcel (but typically should call this method
+ * to parcel Action member variables before they parcel their member variables).
+ */
+ public void writeActionToParcel(final Parcel parcel, final int flags) {
+ parcel.writeString(this.actionKey);
+ parcel.writeBundle(this.actionParameters);
+ }
+
+ /**
+ * Helper for derived classes to implement parcelable
+ */
+ public Action(final Parcel in) {
+ this.actionKey = in.readString();
+ // Note: Need to set classloader to ensure we can un-parcel classes from this package
+ this.actionParameters = in.readBundle(Action.class.getClassLoader());
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ActionMonitor.java b/src/com/android/messaging/datamodel/action/ActionMonitor.java
new file mode 100644
index 0000000..cb080aa
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ActionMonitor.java
@@ -0,0 +1,477 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Handler;
+import android.support.v4.util.SimpleArrayMap;
+import android.text.TextUtils;
+
+import com.android.messaging.util.Assert.RunsOnAnyThread;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.ThreadUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Base class for action monitors
+ * Actions come in various flavors but
+ * o) Fire and forget - no monitor
+ * o) Immediate local processing only - will trigger ActionCompletedListener when done
+ * o) Background worker processing only - will trigger ActionCompletedListener when done
+ * o) Immediate local processing followed by background work followed by more local processing
+ * - will trigger ActionExecutedListener once local processing complete and
+ * ActionCompletedListener when second set of local process (dealing with background
+ * worker response) is complete
+ */
+public class ActionMonitor {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ /**
+ * Interface used to notify on completion of local execution for an action
+ */
+ public interface ActionExecutedListener {
+ /**
+ * @param result value returned by {@link Action#executeAction}
+ */
+ @RunsOnMainThread
+ abstract void onActionExecuted(ActionMonitor monitor, final Action action,
+ final Object data, final Object result);
+ }
+
+ /**
+ * Interface used to notify action completion
+ */
+ public interface ActionCompletedListener {
+ /**
+ * @param result object returned from processing the action. This is the value returned by
+ * {@link Action#executeAction} if there is no background work, or
+ * else the value returned by
+ * {@link Action#processBackgroundResponse}
+ */
+ @RunsOnMainThread
+ abstract void onActionSucceeded(ActionMonitor monitor,
+ final Action action, final Object data, final Object result);
+ /**
+ * @param result value returned by {@link Action#processBackgroundFailure}
+ */
+ @RunsOnMainThread
+ abstract void onActionFailed(ActionMonitor monitor, final Action action,
+ final Object data, final Object result);
+ }
+
+ /**
+ * Interface for being notified of action state changes - used for profiling, testing only
+ */
+ protected interface ActionStateChangedListener {
+ /**
+ * @param action the action that is changing state
+ * @param state the new state of the action
+ */
+ @RunsOnAnyThread
+ void onActionStateChanged(Action action, int state);
+ }
+
+ /**
+ * Operations always start out as STATE_CREATED and finish as STATE_COMPLETE.
+ * Some common state transition sequences in between include:
+ * <ul>
+ * <li>Local data change only : STATE_QUEUED - STATE_EXECUTING
+ * <li>Background worker request only : STATE_BACKGROUND_ACTIONS_QUEUED
+ * - STATE_EXECUTING_BACKGROUND_ACTION
+ * - STATE_BACKGROUND_COMPLETION_QUEUED
+ * - STATE_PROCESSING_BACKGROUND_RESPONSE
+ * <li>Local plus background worker request : STATE_QUEUED - STATE_EXECUTING
+ * - STATE_BACKGROUND_ACTIONS_QUEUED
+ * - STATE_EXECUTING_BACKGROUND_ACTION
+ * - STATE_BACKGROUND_COMPLETION_QUEUED
+ * - STATE_PROCESSING_BACKGROUND_RESPONSE
+ * </ul>
+ */
+ protected static final int STATE_UNDEFINED = 0;
+ protected static final int STATE_CREATED = 1; // Just created
+ protected static final int STATE_QUEUED = 2; // Action queued for processing
+ protected static final int STATE_EXECUTING = 3; // Action processing on datamodel thread
+ protected static final int STATE_BACKGROUND_ACTIONS_QUEUED = 4;
+ protected static final int STATE_EXECUTING_BACKGROUND_ACTION = 5;
+ // The background work has completed, either returning a success response or resulting in a
+ // failure
+ protected static final int STATE_BACKGROUND_COMPLETION_QUEUED = 6;
+ protected static final int STATE_PROCESSING_BACKGROUND_RESPONSE = 7;
+ protected static final int STATE_COMPLETE = 8; // Action complete
+
+ /**
+ * Lock used to protect access to state and listeners
+ */
+ private final Object mLock = new Object();
+
+ /**
+ * Current state of action
+ */
+ @VisibleForTesting
+ protected int mState;
+
+ /**
+ * Listener which is notified on action completion
+ */
+ private ActionCompletedListener mCompletedListener;
+
+ /**
+ * Listener which is notified on action executed
+ */
+ private ActionExecutedListener mExecutedListener;
+
+ /**
+ * Listener which is notified of state changes
+ */
+ private ActionStateChangedListener mStateChangedListener;
+
+ /**
+ * Handler used to post results back to caller
+ */
+ private final Handler mHandler;
+
+ /**
+ * Data passed back to listeners (associated with the action when it is created)
+ */
+ private final Object mData;
+
+ /**
+ * The action key is used to determine equivalence of operations and their requests
+ */
+ private final String mActionKey;
+
+ /**
+ * Get action key identifying associated action
+ */
+ public String getActionKey() {
+ return mActionKey;
+ }
+
+ /**
+ * Unregister listeners so that they will not be called back - override this method if needed
+ */
+ public void unregister() {
+ clearListeners();
+ }
+
+ /**
+ * Unregister listeners so that they will not be called
+ */
+ protected final void clearListeners() {
+ synchronized (mLock) {
+ mCompletedListener = null;
+ mExecutedListener = null;
+ }
+ }
+
+ /**
+ * Create a monitor associated with a particular action instance
+ */
+ protected ActionMonitor(final int initialState, final String actionKey,
+ final Object data) {
+ mHandler = ThreadUtil.getMainThreadHandler();
+ mActionKey = actionKey;
+ mState = initialState;
+ mData = data;
+ }
+
+ /**
+ * Return flag to indicate if action is complete
+ */
+ public boolean isComplete() {
+ boolean complete = false;
+ synchronized (mLock) {
+ complete = (mState == STATE_COMPLETE);
+ }
+ return complete;
+ }
+
+ /**
+ * Set listener that will be called with action completed result
+ */
+ protected final void setCompletedListener(final ActionCompletedListener listener) {
+ synchronized (mLock) {
+ mCompletedListener = listener;
+ }
+ }
+
+ /**
+ * Set listener that will be called with local execution result
+ */
+ protected final void setExecutedListener(final ActionExecutedListener listener) {
+ synchronized (mLock) {
+ mExecutedListener = listener;
+ }
+ }
+
+ /**
+ * Set listener that will be called with local execution result
+ */
+ protected final void setStateChangedListener(final ActionStateChangedListener listener) {
+ synchronized (mLock) {
+ mStateChangedListener = listener;
+ }
+ }
+
+ /**
+ * Perform a state update transition
+ * @param action - action whose state is updating
+ * @param expectedOldState - expected existing state of action (can be UNKNOWN)
+ * @param newState - new state which will be set
+ */
+ @VisibleForTesting
+ protected void updateState(final Action action, final int expectedOldState,
+ final int newState) {
+ ActionStateChangedListener listener = null;
+ synchronized (mLock) {
+ if (expectedOldState != STATE_UNDEFINED &&
+ mState != expectedOldState) {
+ throw new IllegalStateException("On updateState to " + newState + " was " + mState
+ + " expecting " + expectedOldState);
+ }
+ if (newState != mState) {
+ mState = newState;
+ listener = mStateChangedListener;
+ }
+ }
+ if (listener != null) {
+ listener.onActionStateChanged(action, newState);
+ }
+ }
+
+ /**
+ * Perform a state update transition
+ * @param action - action whose state is updating
+ * @param expectedOldState - expected existing state of action (can be UNKNOWN)
+ * @param newState - new state which will be set
+ */
+ static void setState(final Action action, final int expectedOldState,
+ final int newState) {
+ int oldMonitorState = expectedOldState;
+ int newMonitorState = newState;
+ final ActionMonitor monitor
+ = ActionMonitor.lookupActionMonitor(action.actionKey);
+ if (monitor != null) {
+ oldMonitorState = monitor.mState;
+ monitor.updateState(action, expectedOldState, newState);
+ newMonitorState = monitor.mState;
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
+ + "UTC State = " + oldMonitorState + " - " + newMonitorState);
+ }
+ }
+
+ /**
+ * Mark action complete
+ * @param action - action whose state is updating
+ * @param expectedOldState - expected existing state of action (can be UNKNOWN)
+ * @param result - object returned from processing the action. This is the value returned by
+ * {@link Action#executeAction} if there is no background work, or
+ * else the value returned by {@link Action#processBackgroundResponse}
+ * or {@link Action#processBackgroundFailure}
+ */
+ private final void complete(final Action action,
+ final int expectedOldState, final Object result,
+ final boolean succeeded) {
+ ActionCompletedListener completedListener = null;
+ synchronized (mLock) {
+ setState(action, expectedOldState, STATE_COMPLETE);
+ completedListener = mCompletedListener;
+ mExecutedListener = null;
+ mStateChangedListener = null;
+ }
+ if (completedListener != null) {
+ // Marshal to UI thread
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ActionCompletedListener listener = null;
+ synchronized (mLock) {
+ if (mCompletedListener != null) {
+ listener = mCompletedListener;
+ }
+ mCompletedListener = null;
+ }
+ if (listener != null) {
+ if (succeeded) {
+ listener.onActionSucceeded(ActionMonitor.this,
+ action, mData, result);
+ } else {
+ listener.onActionFailed(ActionMonitor.this,
+ action, mData, result);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Mark action complete
+ * @param action - action whose state is updating
+ * @param expectedOldState - expected existing state of action (can be UNKNOWN)
+ * @param result - object returned from processing the action. This is the value returned by
+ * {@link Action#executeAction} if there is no background work, or
+ * else the value returned by {@link Action#processBackgroundResponse}
+ * or {@link Action#processBackgroundFailure}
+ */
+ static void setCompleteState(final Action action, final int expectedOldState,
+ final Object result, final boolean succeeded) {
+ int oldMonitorState = expectedOldState;
+ final ActionMonitor monitor
+ = ActionMonitor.lookupActionMonitor(action.actionKey);
+ if (monitor != null) {
+ oldMonitorState = monitor.mState;
+ monitor.complete(action, expectedOldState, result, succeeded);
+ unregisterActionMonitorIfComplete(action.actionKey, monitor);
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
+ + "UTC State = " + oldMonitorState + " - " + STATE_COMPLETE);
+ }
+ }
+
+ /**
+ * Mark action complete
+ * @param action - action whose state is updating
+ * @param expectedOldState - expected existing state of action (can be UNKNOWN)
+ * @param hasBackgroundActions - has the completing action requested background work
+ * @param result - the return value of {@link Action#executeAction}
+ */
+ final void executed(final Action action,
+ final int expectedOldState, final boolean hasBackgroundActions, final Object result) {
+ ActionExecutedListener executedListener = null;
+ synchronized (mLock) {
+ if (hasBackgroundActions) {
+ setState(action, expectedOldState, STATE_BACKGROUND_ACTIONS_QUEUED);
+ }
+ executedListener = mExecutedListener;
+ }
+ if (executedListener != null) {
+ // Marshal to UI thread
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ActionExecutedListener listener = null;
+ synchronized (mLock) {
+ if (mExecutedListener != null) {
+ listener = mExecutedListener;
+ mExecutedListener = null;
+ }
+ }
+ if (listener != null) {
+ listener.onActionExecuted(ActionMonitor.this,
+ action, mData, result);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Mark action complete
+ * @param action - action whose state is updating
+ * @param expectedOldState - expected existing state of action (can be UNKNOWN)
+ * @param hasBackgroundActions - has the completing action requested background work
+ * @param result - the return value of {@link Action#executeAction}
+ */
+ static void setExecutedState(final Action action,
+ final int expectedOldState, final boolean hasBackgroundActions, final Object result) {
+ int oldMonitorState = expectedOldState;
+ final ActionMonitor monitor
+ = ActionMonitor.lookupActionMonitor(action.actionKey);
+ if (monitor != null) {
+ oldMonitorState = monitor.mState;
+ monitor.executed(action, expectedOldState, hasBackgroundActions, result);
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ LogUtil.v(TAG, "Operation-" + action.actionKey + ": @" + df.format(new Date())
+ + "UTC State = " + oldMonitorState + " - EXECUTED");
+ }
+ }
+
+ /**
+ * Map of action monitors indexed by actionKey
+ */
+ @VisibleForTesting
+ static SimpleArrayMap<String, ActionMonitor> sActionMonitors =
+ new SimpleArrayMap<String, ActionMonitor>();
+
+ /**
+ * Insert new monitor into map
+ */
+ static void registerActionMonitor(final String actionKey,
+ final ActionMonitor monitor) {
+ if (monitor != null
+ && (TextUtils.isEmpty(monitor.getActionKey())
+ || TextUtils.isEmpty(actionKey)
+ || !actionKey.equals(monitor.getActionKey()))) {
+ throw new IllegalArgumentException("Monitor key " + monitor.getActionKey()
+ + " not compatible with action key " + actionKey);
+ }
+ synchronized (sActionMonitors) {
+ sActionMonitors.put(actionKey, monitor);
+ }
+ }
+
+ /**
+ * Find monitor associated with particular action
+ */
+ private static ActionMonitor lookupActionMonitor(final String actionKey) {
+ ActionMonitor monitor = null;
+ synchronized (sActionMonitors) {
+ monitor = sActionMonitors.get(actionKey);
+ }
+ return monitor;
+ }
+
+ /**
+ * Remove monitor from map
+ */
+ @VisibleForTesting
+ static void unregisterActionMonitor(final String actionKey,
+ final ActionMonitor monitor) {
+ if (monitor != null) {
+ synchronized (sActionMonitors) {
+ sActionMonitors.remove(actionKey);
+ }
+ }
+ }
+
+ /**
+ * Remove monitor from map if the action is complete
+ */
+ static void unregisterActionMonitorIfComplete(final String actionKey,
+ final ActionMonitor monitor) {
+ if (monitor != null && monitor.isComplete()) {
+ synchronized (sActionMonitors) {
+ sActionMonitors.remove(actionKey);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ActionService.java b/src/com/android/messaging/datamodel/action/ActionService.java
new file mode 100644
index 0000000..29225fa
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ActionService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.datamodel.action;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Class providing interface for the ActionService - can be stubbed for testing
+ */
+public class ActionService {
+ protected static PendingIntent makeStartActionPendingIntent(final Context context,
+ final Action action, final int requestCode, final boolean launchesAnActivity) {
+ return ActionServiceImpl.makeStartActionPendingIntent(context, action, requestCode,
+ launchesAnActivity);
+ }
+
+ /**
+ * Start an action by posting it over the the ActionService
+ */
+ public void startAction(final Action action) {
+ ActionServiceImpl.startAction(action);
+ }
+
+ /**
+ * Schedule a delayed action by posting it over the the ActionService
+ */
+ public void scheduleAction(final Action action, final int code,
+ final long delayMs) {
+ ActionServiceImpl.scheduleAction(action, code, delayMs);
+ }
+
+ /**
+ * Process a response from the BackgroundWorker in the ActionService
+ */
+ protected void handleResponseFromBackgroundWorker(
+ final Action action, final Bundle response) {
+ ActionServiceImpl.handleResponseFromBackgroundWorker(action, response);
+ }
+
+ /**
+ * Process a failure from the BackgroundWorker in the ActionService
+ */
+ protected void handleFailureFromBackgroundWorker(final Action action,
+ final Exception exception) {
+ ActionServiceImpl.handleFailureFromBackgroundWorker(action, exception);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ActionServiceImpl.java b/src/com/android/messaging/datamodel/action/ActionServiceImpl.java
new file mode 100644
index 0000000..a408dac
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ActionServiceImpl.java
@@ -0,0 +1,341 @@
+/*
+ * 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.datamodel.action;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.LoggingTimer;
+import com.android.messaging.util.WakeLockHelper;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * ActionService used to perform background processing for data model
+ */
+public class ActionServiceImpl extends IntentService {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final boolean VERBOSE = false;
+
+ public ActionServiceImpl() {
+ super("ActionService");
+ }
+
+ /**
+ * Start action by sending intent to the service
+ * @param action - action to start
+ */
+ protected static void startAction(final Action action) {
+ final Intent intent = makeIntent(OP_START_ACTION);
+ final Bundle actionBundle = new Bundle();
+ actionBundle.putParcelable(BUNDLE_ACTION, action);
+ intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
+ action.markStart();
+ startServiceWithIntent(intent);
+ }
+
+ /**
+ * Schedule an action to run after specified delay using alarm manager to send pendingintent
+ * @param action - action to start
+ * @param requestCode - request code used to collapse requests
+ * @param delayMs - delay in ms (from now) before action will start
+ */
+ protected static void scheduleAction(final Action action, final int requestCode,
+ final long delayMs) {
+ final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION);
+ final Bundle actionBundle = new Bundle();
+ actionBundle.putParcelable(BUNDLE_ACTION, action);
+ intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
+
+ PendingActionReceiver.scheduleAlarm(intent, requestCode, delayMs);
+ }
+
+ /**
+ * Handle response returned by BackgroundWorker
+ * @param request - request generating response
+ * @param response - response from service
+ */
+ protected static void handleResponseFromBackgroundWorker(final Action action,
+ final Bundle response) {
+ final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_RESPONSE);
+
+ final Bundle actionBundle = new Bundle();
+ actionBundle.putParcelable(BUNDLE_ACTION, action);
+ intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
+ intent.putExtra(EXTRA_WORKER_RESPONSE, response);
+
+ startServiceWithIntent(intent);
+ }
+
+ /**
+ * Handle response returned by BackgroundWorker
+ * @param request - request generating failure
+ */
+ protected static void handleFailureFromBackgroundWorker(final Action action,
+ final Exception exception) {
+ final Intent intent = makeIntent(OP_RECEIVE_BACKGROUND_FAILURE);
+
+ final Bundle actionBundle = new Bundle();
+ actionBundle.putParcelable(BUNDLE_ACTION, action);
+ intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
+ intent.putExtra(EXTRA_WORKER_EXCEPTION, exception);
+
+ startServiceWithIntent(intent);
+ }
+
+ // ops
+ @VisibleForTesting
+ protected static final int OP_START_ACTION = 200;
+ @VisibleForTesting
+ protected static final int OP_RECEIVE_BACKGROUND_RESPONSE = 201;
+ @VisibleForTesting
+ protected static final int OP_RECEIVE_BACKGROUND_FAILURE = 202;
+
+ // extras
+ @VisibleForTesting
+ protected static final String EXTRA_OP_CODE = "op";
+ @VisibleForTesting
+ protected static final String EXTRA_ACTION_BUNDLE = "datamodel_action_bundle";
+ @VisibleForTesting
+ protected static final String EXTRA_WORKER_EXCEPTION = "worker_exception";
+ @VisibleForTesting
+ protected static final String EXTRA_WORKER_RESPONSE = "worker_response";
+ @VisibleForTesting
+ protected static final String EXTRA_WORKER_UPDATE = "worker_update";
+ @VisibleForTesting
+ protected static final String BUNDLE_ACTION = "bundle_action";
+
+ private BackgroundWorker mBackgroundWorker;
+
+ /**
+ * Allocate an intent with a specific opcode.
+ */
+ private static Intent makeIntent(final int opcode) {
+ final Intent intent = new Intent(Factory.get().getApplicationContext(),
+ ActionServiceImpl.class);
+ intent.putExtra(EXTRA_OP_CODE, opcode);
+ return intent;
+ }
+
+ /**
+ * Broadcast receiver for alarms scheduled through ActionService.
+ */
+ public static class PendingActionReceiver extends BroadcastReceiver {
+ static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION";
+
+ /**
+ * Allocate an intent with a specific opcode and alarm action.
+ */
+ public static Intent makeIntent(final int opcode) {
+ final Intent intent = new Intent(Factory.get().getApplicationContext(),
+ PendingActionReceiver.class);
+ intent.setAction(ACTION);
+ intent.putExtra(EXTRA_OP_CODE, opcode);
+ return intent;
+ }
+
+ public static void scheduleAlarm(final Intent intent, final int requestCode,
+ final long delayMs) {
+ final Context context = Factory.get().getApplicationContext();
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ final AlarmManager mgr =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ if (delayMs < Long.MAX_VALUE) {
+ mgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + delayMs, pendingIntent);
+ } else {
+ mgr.cancel(pendingIntent);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ ActionServiceImpl.startServiceWithIntent(intent);
+ }
+ }
+
+ /**
+ * Creates a pending intent that will trigger a data model action when the intent is
+ * triggered
+ */
+ public static PendingIntent makeStartActionPendingIntent(final Context context,
+ final Action action, final int requestCode, final boolean launchesAnActivity) {
+ final Intent intent = PendingActionReceiver.makeIntent(OP_START_ACTION);
+ final Bundle actionBundle = new Bundle();
+ actionBundle.putParcelable(BUNDLE_ACTION, action);
+ intent.putExtra(EXTRA_ACTION_BUNDLE, actionBundle);
+ if (launchesAnActivity) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ return PendingIntent.getBroadcast(context, requestCode, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mBackgroundWorker = DataModel.get().getBackgroundWorkerForActionService();
+ DataModel.get().getConnectivityUtil().registerForSignalStrength();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ DataModel.get().getConnectivityUtil().unregisterForSignalStrength();
+ }
+
+ private static final String WAKELOCK_ID = "bugle_datamodel_service_wakelock";
+ @VisibleForTesting
+ static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
+
+ /**
+ * Queue intent to the ActionService after acquiring wake lock
+ */
+ private static void startServiceWithIntent(final Intent intent) {
+ final Context context = Factory.get().getApplicationContext();
+ final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
+ // Increase refCount on wake lock - acquiring if necessary
+ if (VERBOSE) {
+ LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode);
+ }
+ sWakeLock.acquire(context, intent, opcode);
+ intent.setClass(context, ActionServiceImpl.class);
+
+ // TODO: Note that intent will be quietly discarded if it exceeds available rpc
+ // memory (in total around 1MB). See this article for background
+ // http://developer.android.com/reference/android/os/TransactionTooLargeException.html
+ // Perhaps we should keep large structures in the action monitor?
+ if (context.startService(intent) == null) {
+ LogUtil.e(TAG,
+ "ActionService.startServiceWithIntent: failed to start service for intent "
+ + intent);
+ sWakeLock.release(intent, opcode);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onHandleIntent(final Intent intent) {
+ if (intent == null) {
+ // Shouldn't happen but sometimes does following another crash.
+ LogUtil.w(TAG, "ActionService.onHandleIntent: Called with null intent");
+ return;
+ }
+ final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
+ sWakeLock.ensure(intent, opcode);
+
+ try {
+ Action action;
+ final Bundle actionBundle = intent.getBundleExtra(EXTRA_ACTION_BUNDLE);
+ actionBundle.setClassLoader(getClassLoader());
+ switch(opcode) {
+ case OP_START_ACTION: {
+ action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
+ executeAction(action);
+ break;
+ }
+
+ case OP_RECEIVE_BACKGROUND_RESPONSE: {
+ action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
+ final Bundle response = intent.getBundleExtra(EXTRA_WORKER_RESPONSE);
+ processBackgroundResponse(action, response);
+ break;
+ }
+
+ case OP_RECEIVE_BACKGROUND_FAILURE: {
+ action = (Action) actionBundle.getParcelable(BUNDLE_ACTION);
+ processBackgroundFailure(action);
+ break;
+ }
+
+ default:
+ throw new RuntimeException("Unrecognized opcode in ActionServiceImpl");
+ }
+
+ action.sendBackgroundActions(mBackgroundWorker);
+ } finally {
+ // Decrease refCount on wake lock - releasing if necessary
+ sWakeLock.release(intent, opcode);
+ }
+ }
+
+ private static final long EXECUTION_TIME_WARN_LIMIT_MS = 1000; // 1 second
+ /**
+ * Local execution of action on ActionService thread
+ */
+ private void executeAction(final Action action) {
+ action.markBeginExecute();
+
+ final LoggingTimer timer = createLoggingTimer(action, "#executeAction");
+ timer.start();
+
+ final Object result = action.executeAction();
+
+ timer.stopAndLog();
+
+ action.markEndExecute(result);
+ }
+
+ /**
+ * Process response on ActionService thread
+ */
+ private void processBackgroundResponse(final Action action, final Bundle response) {
+ final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundResponse");
+ timer.start();
+
+ action.processBackgroundWorkResponse(response);
+
+ timer.stopAndLog();
+ }
+
+ /**
+ * Process failure on ActionService thread
+ */
+ private void processBackgroundFailure(final Action action) {
+ final LoggingTimer timer = createLoggingTimer(action, "#processBackgroundFailure");
+ timer.start();
+
+ action.processBackgroundWorkFailure();
+
+ timer.stopAndLog();
+ }
+
+ private static LoggingTimer createLoggingTimer(
+ final Action action, final String methodName) {
+ return new LoggingTimer(TAG, action.getClass().getSimpleName() + methodName,
+ EXECUTION_TIME_WARN_LIMIT_MS);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/BackgroundWorker.java b/src/com/android/messaging/datamodel/action/BackgroundWorker.java
new file mode 100644
index 0000000..aad3c07
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/BackgroundWorker.java
@@ -0,0 +1,32 @@
+/*
+ * 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.datamodel.action;
+
+import java.util.List;
+
+/**
+ * Interface between action service and its workers
+ */
+public class BackgroundWorker {
+
+ /**
+ * Send list of requests from action service to a worker
+ */
+ public void queueBackgroundWork(final List<Action> backgroundActions) {
+ BackgroundWorkerService.queueBackgroundWork(backgroundActions);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java b/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java
new file mode 100644
index 0000000..4d4b150
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/BackgroundWorkerService.java
@@ -0,0 +1,168 @@
+/*
+ * 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.datamodel.action;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DataModelException;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.LoggingTimer;
+import com.android.messaging.util.WakeLockHelper;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * Background worker service is an initial example of a background work queue handler
+ * Used to actually "send" messages which may take some time and should not block ActionService
+ * or UI
+ */
+public class BackgroundWorkerService extends IntentService {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final boolean VERBOSE = false;
+
+ private static final String WAKELOCK_ID = "bugle_background_worker_wakelock";
+ @VisibleForTesting
+ static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
+
+ private final ActionService mHost;
+
+ public BackgroundWorkerService() {
+ super("BackgroundWorker");
+ mHost = DataModel.get().getActionService();
+ }
+
+ /**
+ * Queue a list of requests from action service to this worker
+ */
+ public static void queueBackgroundWork(final List<Action> actions) {
+ for (final Action action : actions) {
+ startServiceWithAction(action, 0);
+ }
+ }
+
+ // ops
+ @VisibleForTesting
+ protected static final int OP_PROCESS_REQUEST = 400;
+
+ // extras
+ @VisibleForTesting
+ protected static final String EXTRA_OP_CODE = "op";
+ @VisibleForTesting
+ protected static final String EXTRA_ACTION = "action";
+ @VisibleForTesting
+ protected static final String EXTRA_ATTEMPT = "retry_attempt";
+
+ /**
+ * Queue action intent to the BackgroundWorkerService after acquiring wake lock
+ */
+ private static void startServiceWithAction(final Action action,
+ final int retryCount) {
+ final Intent intent = new Intent();
+ intent.putExtra(EXTRA_ACTION, action);
+ intent.putExtra(EXTRA_ATTEMPT, retryCount);
+ startServiceWithIntent(OP_PROCESS_REQUEST, intent);
+ }
+
+ /**
+ * Queue intent to the BackgroundWorkerService after acquiring wake lock
+ */
+ private static void startServiceWithIntent(final int opcode, final Intent intent) {
+ final Context context = Factory.get().getApplicationContext();
+
+ intent.setClass(context, BackgroundWorkerService.class);
+ intent.putExtra(EXTRA_OP_CODE, opcode);
+ sWakeLock.acquire(context, intent, opcode);
+ if (VERBOSE) {
+ LogUtil.v(TAG, "acquiring wakelock for opcode " + opcode);
+ }
+
+ if (context.startService(intent) == null) {
+ LogUtil.e(TAG,
+ "BackgroundWorkerService.startServiceWithAction: failed to start service for "
+ + opcode);
+ sWakeLock.release(intent, opcode);
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(final Intent intent) {
+ if (intent == null) {
+ // Shouldn't happen but sometimes does following another crash.
+ LogUtil.w(TAG, "BackgroundWorkerService.onHandleIntent: Called with null intent");
+ return;
+ }
+ final int opcode = intent.getIntExtra(EXTRA_OP_CODE, 0);
+ sWakeLock.ensure(intent, opcode);
+
+ try {
+ switch(opcode) {
+ case OP_PROCESS_REQUEST: {
+ final Action action = intent.getParcelableExtra(EXTRA_ACTION);
+ final int attempt = intent.getIntExtra(EXTRA_ATTEMPT, -1);
+ doBackgroundWork(action, attempt);
+ break;
+ }
+
+ default:
+ throw new RuntimeException("Unrecognized opcode in BackgroundWorkerService");
+ }
+ } finally {
+ sWakeLock.release(intent, opcode);
+ }
+ }
+
+ /**
+ * Local execution of background work for action on ActionService thread
+ */
+ private void doBackgroundWork(final Action action, final int attempt) {
+ action.markBackgroundWorkStarting();
+ Bundle response = null;
+ try {
+ final LoggingTimer timer = new LoggingTimer(
+ TAG, action.getClass().getSimpleName() + "#doBackgroundWork");
+ timer.start();
+
+ response = action.doBackgroundWork();
+
+ timer.stopAndLog();
+ action.markBackgroundCompletionQueued();
+ mHost.handleResponseFromBackgroundWorker(action, response);
+ } catch (final Exception exception) {
+ final boolean retry = false;
+ LogUtil.e(TAG, "Error in background worker", exception);
+ if (!(exception instanceof DataModelException)) {
+ // DataModelException is expected (sort-of) and handled in handleFailureFromWorker
+ // below, but other exceptions should crash ENG builds
+ Assert.fail("Unexpected error in background worker - abort");
+ }
+ if (retry) {
+ action.markBackgroundWorkQueued();
+ startServiceWithAction(action, attempt + 1);
+ } else {
+ action.markBackgroundCompletionQueued();
+ mHost.handleFailureFromBackgroundWorker(action, exception);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/BugleActionToasts.java b/src/com/android/messaging/datamodel/action/BugleActionToasts.java
new file mode 100644
index 0000000..f60facd
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/BugleActionToasts.java
@@ -0,0 +1,172 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.widget.Toast;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.ThreadUtil;
+
+import javax.annotation.Nullable;
+
+/**
+ * Shows one-time, transient notifications in response to action failures (i.e. permanent failures
+ * when sending a message) by showing toasts.
+ */
+public class BugleActionToasts {
+ /**
+ * Called when SendMessageAction or DownloadMmsAction finishes
+ * @param conversationId the conversation of the sent or downloaded message
+ * @param success did the action succeed
+ * @param status the message sending status
+ * @param isSms whether the message is sent using SMS
+ * @param subId the subId of the SIM related to this send
+ * @param isSend whether it is a send (false for download)
+ */
+ static void onSendMessageOrManualDownloadActionCompleted(
+ final String conversationId,
+ final boolean success,
+ final int status,
+ final boolean isSms,
+ final int subId,
+ final boolean isSend) {
+ // We only show notifications for two cases, i.e. when mobile data is off or when we are
+ // in airplane mode, both of which fail fast with permanent failures.
+ if (!success && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
+ final PhoneUtils phoneUtils = PhoneUtils.get(subId);
+ if (phoneUtils.isAirplaneModeOn()) {
+ if (isSend) {
+ showToast(R.string.send_message_failure_airplane_mode);
+ } else {
+ showToast(R.string.download_message_failure_airplane_mode);
+ }
+ return;
+ } else if (!isSms && !phoneUtils.isMobileDataEnabled()) {
+ if (isSend) {
+ showToast(R.string.send_message_failure_no_data);
+ } else {
+ showToast(R.string.download_message_failure_no_data);
+ }
+ return;
+ }
+ }
+
+ if (AccessibilityUtil.isTouchExplorationEnabled(Factory.get().getApplicationContext())) {
+ final boolean isFocusedConversation = DataModel.get().isFocusedConversation(conversationId);
+ if (isFocusedConversation && success) {
+ // Using View.announceForAccessibility may be preferable, but we do not have a
+ // View, and so we use a toast instead.
+ showToast(isSend ? R.string.send_message_success
+ : R.string.download_message_success);
+ return;
+ }
+
+ // {@link MessageNotificationState#checkFailedMessages} does not post a notification for
+ // failures in observable conversations. For accessibility, we provide an indication
+ // here.
+ final boolean isObservableConversation = DataModel.get().isNewMessageObservable(
+ conversationId);
+ if (isObservableConversation && !success) {
+ showToast(isSend ? R.string.send_message_failure
+ : R.string.download_message_failure);
+ }
+ }
+ }
+
+ public static void onMessageReceived(final String conversationId,
+ @Nullable final ParticipantData sender, @Nullable final MessageData message) {
+ final Context context = Factory.get().getApplicationContext();
+ if (AccessibilityUtil.isTouchExplorationEnabled(context)) {
+ final boolean isFocusedConversation = DataModel.get().isFocusedConversation(
+ conversationId);
+ if (isFocusedConversation) {
+ final Resources res = context.getResources();
+ final String senderDisplayName = (sender == null)
+ ? res.getString(R.string.unknown_sender) : sender.getDisplayName(false);
+ final String announcement = res.getString(
+ R.string.incoming_message_announcement, senderDisplayName,
+ (message == null) ? "" : message.getMessageText());
+ showToast(announcement);
+ }
+ }
+ }
+
+ public static void onConversationDeleted() {
+ showToast(R.string.conversation_deleted);
+ }
+
+ private static void showToast(final int messageResId) {
+ ThreadUtil.getMainThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(),
+ getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private static void showToast(final String message) {
+ ThreadUtil.getMainThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private static Context getApplicationContext() {
+ return Factory.get().getApplicationContext();
+ }
+
+ private static class UpdateDestinationBlockedActionToast
+ implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
+ private final Context mContext;
+
+ UpdateDestinationBlockedActionToast(final Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void onUpdateDestinationBlockedAction(
+ final UpdateDestinationBlockedAction action,
+ final boolean success,
+ final boolean block,
+ final String destination) {
+ if (success) {
+ Toast.makeText(mContext,
+ block
+ ? R.string.update_destination_blocked
+ : R.string.update_destination_unblocked,
+ Toast.LENGTH_LONG
+ ).show();
+ }
+ }
+ }
+
+ public static UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
+ makeUpdateDestinationBlockedActionListener(final Context context) {
+ return new UpdateDestinationBlockedActionToast(context);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/DeleteConversationAction.java b/src/com/android/messaging/datamodel/action/DeleteConversationAction.java
new file mode 100644
index 0000000..a00f6d6
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/DeleteConversationAction.java
@@ -0,0 +1,205 @@
+/*
+ * 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.datamodel.action;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DataModelException;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.widget.WidgetConversationProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Action used to delete a conversation.
+ */
+public class DeleteConversationAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ public static void deleteConversation(final String conversationId, final long cutoffTimestamp) {
+ final DeleteConversationAction action = new DeleteConversationAction(conversationId,
+ cutoffTimestamp);
+ action.start();
+ }
+
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+ private static final String KEY_CUTOFF_TIMESTAMP = "cutoff_timestamp";
+
+ private DeleteConversationAction(final String conversationId, final long cutoffTimestamp) {
+ super();
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ // TODO: Should we set cuttoff timestamp to prevent us deleting new messages?
+ actionParameters.putLong(KEY_CUTOFF_TIMESTAMP, cutoffTimestamp);
+ }
+
+ // Delete conversation from both the local DB and telephony in the background so sync cannot
+ // run concurrently and incorrectly try to recreate the conversation's messages locally. The
+ // telephony database can sometimes be quite slow to delete conversations, so we delete from
+ // the local DB first, notify the UI, and then delete from telephony.
+ @Override
+ protected Bundle doBackgroundWork() throws DataModelException {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final long cutoffTimestamp = actionParameters.getLong(KEY_CUTOFF_TIMESTAMP);
+
+ if (!TextUtils.isEmpty(conversationId)) {
+ // First find the thread id for this conversation.
+ final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId);
+
+ if (BugleDatabaseOperations.deleteConversation(db, conversationId, cutoffTimestamp)) {
+ LogUtil.i(TAG, "DeleteConversationAction: Deleted local conversation "
+ + conversationId);
+
+ BugleActionToasts.onConversationDeleted();
+
+ // Remove notifications if necessary
+ BugleNotifications.update(true /* silent */, null /* conversationId */,
+ BugleNotifications.UPDATE_MESSAGES);
+
+ // We have changed the conversation list
+ MessagingContentProvider.notifyConversationListChanged();
+
+ // Notify the widget the conversation is deleted so it can go into its configure state.
+ WidgetConversationProvider.notifyConversationDeleted(
+ Factory.get().getApplicationContext(),
+ conversationId);
+ } else {
+ LogUtil.w(TAG, "DeleteConversationAction: Could not delete local conversation "
+ + conversationId);
+ return null;
+ }
+
+ // Now delete from telephony DB. MmsSmsProvider throws an exception if the thread id is
+ // less than 0. If it's greater than zero, it will delete all messages with that thread
+ // id, even if there's no corresponding row in the threads table.
+ if (threadId >= 0) {
+ final int count = MmsUtils.deleteThread(threadId, cutoffTimestamp);
+ if (count > 0) {
+ LogUtil.i(TAG, "DeleteConversationAction: Deleted telephony thread "
+ + threadId + " (cutoffTimestamp = " + cutoffTimestamp + ")");
+ } else {
+ LogUtil.w(TAG, "DeleteConversationAction: Could not delete thread from "
+ + "telephony: conversationId = " + conversationId + ", thread id = "
+ + threadId);
+ }
+ } else {
+ LogUtil.w(TAG, "DeleteConversationAction: Local conversation " + conversationId
+ + " has an invalid telephony thread id; will delete messages individually");
+ deleteConversationMessagesFromTelephony();
+ }
+ } else {
+ LogUtil.e(TAG, "DeleteConversationAction: conversationId is empty");
+ }
+
+ return null;
+ }
+
+ /**
+ * Deletes all the telephony messages for the local conversation being deleted.
+ * <p>
+ * This is a fallback used when the conversation is not associated with any telephony thread,
+ * or its thread id is invalid (e.g. negative). This is not common, but can happen sometimes
+ * (e.g. the Unknown Sender conversation). In the usual case of deleting a conversation, we
+ * don't need this because the telephony provider automatically deletes messages when a thread
+ * is deleted.
+ */
+ private void deleteConversationMessagesFromTelephony() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ Assert.notNull(conversationId);
+
+ final List<Uri> messageUris = new ArrayList<>();
+ Cursor cursor = null;
+ try {
+ cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
+ new String[] { MessageColumns.SMS_MESSAGE_URI },
+ MessageColumns.CONVERSATION_ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ while (cursor.moveToNext()) {
+ String messageUri = cursor.getString(0);
+ try {
+ messageUris.add(Uri.parse(messageUri));
+ } catch (Exception e) {
+ LogUtil.e(TAG, "DeleteConversationAction: Could not parse message uri "
+ + messageUri);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ for (Uri messageUri : messageUris) {
+ int count = MmsUtils.deleteMessage(messageUri);
+ if (count > 0) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "DeleteConversationAction: Deleted telephony message "
+ + messageUri);
+ }
+ } else {
+ LogUtil.w(TAG, "DeleteConversationAction: Could not delete telephony message "
+ + messageUri);
+ }
+ }
+ }
+
+ @Override
+ protected Object executeAction() {
+ requestBackgroundWork();
+ return null;
+ }
+
+ private DeleteConversationAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<DeleteConversationAction> CREATOR
+ = new Parcelable.Creator<DeleteConversationAction>() {
+ @Override
+ public DeleteConversationAction createFromParcel(final Parcel in) {
+ return new DeleteConversationAction(in);
+ }
+
+ @Override
+ public DeleteConversationAction[] newArray(final int size) {
+ return new DeleteConversationAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/DeleteMessageAction.java b/src/com/android/messaging/datamodel/action/DeleteMessageAction.java
new file mode 100644
index 0000000..9ddb2a6
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/DeleteMessageAction.java
@@ -0,0 +1,135 @@
+/*
+ * 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.datamodel.action;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Action used to delete a single message.
+ */
+public class DeleteMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ public static void deleteMessage(final String messageId) {
+ final DeleteMessageAction action = new DeleteMessageAction(messageId);
+ action.start();
+ }
+
+ private static final String KEY_MESSAGE_ID = "message_id";
+
+ private DeleteMessageAction(final String messageId) {
+ super();
+ actionParameters.putString(KEY_MESSAGE_ID, messageId);
+ }
+
+ // Doing this work in the background so that we're not competing with sync
+ // which could bring the deleted message back to life between the time we deleted
+ // it locally and deleted it in telephony (sync is also done on doBackgroundWork).
+ //
+ // Previously this block of code deleted from telephony first but that can be very
+ // slow (on the order of seconds) so this was modified to first delete locally, trigger
+ // the UI update, then delete from telephony.
+ @Override
+ protected Bundle doBackgroundWork() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // First find the thread id for this conversation.
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+
+ if (!TextUtils.isEmpty(messageId)) {
+ // Check message still exists
+ final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
+ if (message != null) {
+ // Delete from local DB
+ int count = BugleDatabaseOperations.deleteMessage(db, messageId);
+ if (count > 0) {
+ LogUtil.i(TAG, "DeleteMessageAction: Deleted local message "
+ + messageId);
+ } else {
+ LogUtil.w(TAG, "DeleteMessageAction: Could not delete local message "
+ + messageId);
+ }
+ MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
+ // We may have changed the conversation list
+ MessagingContentProvider.notifyConversationListChanged();
+
+ final Uri messageUri = message.getSmsMessageUri();
+ if (messageUri != null) {
+ // Delete from telephony DB
+ count = MmsUtils.deleteMessage(messageUri);
+ if (count > 0) {
+ LogUtil.i(TAG, "DeleteMessageAction: Deleted telephony message "
+ + messageUri);
+ } else {
+ LogUtil.w(TAG, "DeleteMessageAction: Could not delete message from "
+ + "telephony: messageId = " + messageId + ", telephony uri = "
+ + messageUri);
+ }
+ } else {
+ LogUtil.i(TAG, "DeleteMessageAction: Local message " + messageId
+ + " has no telephony uri.");
+ }
+ } else {
+ LogUtil.w(TAG, "DeleteMessageAction: Message " + messageId + " no longer exists");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Delete the message.
+ */
+ @Override
+ protected Object executeAction() {
+ requestBackgroundWork();
+ return null;
+ }
+
+ private DeleteMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<DeleteMessageAction> CREATOR
+ = new Parcelable.Creator<DeleteMessageAction>() {
+ @Override
+ public DeleteMessageAction createFromParcel(final Parcel in) {
+ return new DeleteMessageAction(in);
+ }
+
+ @Override
+ public DeleteMessageAction[] newArray(final int size) {
+ return new DeleteMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/DownloadMmsAction.java b/src/com/android/messaging/datamodel/action/DownloadMmsAction.java
new file mode 100644
index 0000000..7a8c907
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/DownloadMmsAction.java
@@ -0,0 +1,340 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Downloads an MMS message.
+ * <p>
+ * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to
+ * access the EXTRA_* fields for setting up the 'downloaded' pending intent.
+ */
+public class DownloadMmsAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ /**
+ * Interface for DownloadMmsAction listeners
+ */
+ public interface DownloadMmsActionListener {
+ @RunsOnMainThread
+ abstract void onDownloadMessageStarting(final ActionMonitor monitor,
+ final Object data, final MessageData message);
+ @RunsOnMainThread
+ abstract void onDownloadMessageSucceeded(final ActionMonitor monitor,
+ final Object data, final MessageData message);
+ @RunsOnMainThread
+ abstract void onDownloadMessageFailed(final ActionMonitor monitor,
+ final Object data, final MessageData message);
+ }
+
+ /**
+ * Queue download of an mms notification message (can only be called during execute of action)
+ */
+ static boolean queueMmsForDownloadInBackground(final String messageId,
+ final Action processingAction) {
+ // When this method is being called, it is always from auto download
+ final DownloadMmsAction action = new DownloadMmsAction();
+ // This could queue nothing
+ return action.queueAction(messageId, processingAction);
+ }
+
+ private static final String KEY_MESSAGE_ID = "message_id";
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+ private static final String KEY_PARTICIPANT_ID = "participant_id";
+ private static final String KEY_CONTENT_LOCATION = "content_location";
+ private static final String KEY_TRANSACTION_ID = "transaction_id";
+ private static final String KEY_NOTIFICATION_URI = "notification_uri";
+ private static final String KEY_SUB_ID = "sub_id";
+ private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
+ private static final String KEY_AUTO_DOWNLOAD = "auto_download";
+ private static final String KEY_FAILURE_STATUS = "failure_status";
+
+ // Values we attach to the pending intent that's fired when the message is downloaded.
+ // Only applicable when downloading via the platform APIs on L+.
+ public static final String EXTRA_MESSAGE_ID = "message_id";
+ public static final String EXTRA_CONTENT_URI = "content_uri";
+ public static final String EXTRA_NOTIFICATION_URI = "notification_uri";
+ public static final String EXTRA_SUB_ID = "sub_id";
+ public static final String EXTRA_SUB_PHONE_NUMBER = "sub_phone_number";
+ public static final String EXTRA_TRANSACTION_ID = "transaction_id";
+ public static final String EXTRA_CONTENT_LOCATION = "content_location";
+ public static final String EXTRA_AUTO_DOWNLOAD = "auto_download";
+ public static final String EXTRA_RECEIVED_TIMESTAMP = "received_timestamp";
+ public static final String EXTRA_CONVERSATION_ID = "conversation_id";
+ public static final String EXTRA_PARTICIPANT_ID = "participant_id";
+ public static final String EXTRA_STATUS_IF_FAILED = "status_if_failed";
+
+ private DownloadMmsAction() {
+ super();
+ }
+
+ @Override
+ protected Object executeAction() {
+ Assert.fail("DownloadMmsAction must be queued rather than started");
+ return null;
+ }
+
+ protected boolean queueAction(final String messageId, final Action processingAction) {
+ actionParameters.putString(KEY_MESSAGE_ID, messageId);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ // Read the message from local db
+ final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
+ if (message != null && message.canDownloadMessage()) {
+ final Uri notificationUri = message.getSmsMessageUri();
+ final String conversationId = message.getConversationId();
+ final int status = message.getStatus();
+
+ final String selfId = message.getSelfId();
+ final ParticipantData self = BugleDatabaseOperations
+ .getExistingParticipant(db, selfId);
+ final int subId = self.getSubId();
+ actionParameters.putInt(KEY_SUB_ID, subId);
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ actionParameters.putString(KEY_PARTICIPANT_ID, message.getParticipantId());
+ actionParameters.putString(KEY_CONTENT_LOCATION, message.getMmsContentLocation());
+ actionParameters.putString(KEY_TRANSACTION_ID, message.getMmsTransactionId());
+ actionParameters.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
+ actionParameters.putBoolean(KEY_AUTO_DOWNLOAD, isAutoDownload(status));
+
+ final long now = System.currentTimeMillis();
+ if (message.getInDownloadWindow(now)) {
+ // We can still retry
+ actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination());
+
+ final int downloadingStatus = getDownloadingStatus(status);
+ // Update message status to indicate downloading.
+ updateMessageStatus(notificationUri, messageId, conversationId,
+ downloadingStatus, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
+ // Pre-compute the next status when failed so we don't have to load from db again
+ actionParameters.putInt(KEY_FAILURE_STATUS, getFailureStatus(downloadingStatus));
+
+ // Actual download happens in background
+ processingAction.requestBackgroundWork(this);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG,
+ "DownloadMmsAction: Queued download of MMS message " + messageId);
+ }
+ return true;
+ } else {
+ LogUtil.w(TAG, "DownloadMmsAction: Download of MMS message " + messageId
+ + " failed (outside download window)");
+
+ // Retries depleted and we failed. Update the message status so we won't retry again
+ updateMessageStatus(notificationUri, messageId, conversationId,
+ MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED,
+ MessageData.RAW_TELEPHONY_STATUS_UNDEFINED);
+ if (status == MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD) {
+ // For auto download failure, we should send a DEFERRED NotifyRespInd
+ // to carrier to indicate we will manual download later
+ ProcessDownloadedMmsAction.sendDeferredRespStatus(
+ messageId, message.getMmsTransactionId(),
+ message.getMmsContentLocation(), subId);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find out the auto download state of this message based on its starting status
+ *
+ * @param status The starting status of the message.
+ * @return True if this is a message doing auto downloading, false otherwise
+ */
+ private static boolean isAutoDownload(final int status) {
+ switch (status) {
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
+ return false;
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
+ return true;
+ default:
+ Assert.fail("isAutoDownload: invalid input status " + status);
+ return false;
+ }
+ }
+
+ /**
+ * Get the corresponding downloading status based on the starting status of the message
+ *
+ * @param status The starting status of the message.
+ * @return The downloading status
+ */
+ private static int getDownloadingStatus(final int status) {
+ switch (status) {
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
+ return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
+ return MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING;
+ default:
+ Assert.fail("isAutoDownload: invalid input status " + status);
+ return MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING;
+ }
+ }
+
+ /**
+ * Get the corresponding failed status based on the current downloading status
+ *
+ * @param status The downloading status
+ * @return The status the message should have if downloading failed
+ */
+ private static int getFailureStatus(final int status) {
+ switch (status) {
+ case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
+ return MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD;
+ case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
+ return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
+ default:
+ Assert.fail("isAutoDownload: invalid input status " + status);
+ return MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD;
+ }
+ }
+
+ @Override
+ protected Bundle doBackgroundWork() {
+ final Context context = Factory.get().getApplicationContext();
+ final int subId = actionParameters.getInt(KEY_SUB_ID);
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ final Uri notificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
+ final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
+ final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
+ final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
+ final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
+ final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
+
+ final long receivedTimestampRoundedToSecond =
+ 1000 * ((System.currentTimeMillis() + 500) / 1000);
+
+ LogUtil.i(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
+ + " (" + (autoDownload ? "auto" : "manual") + ")");
+
+ // Bundle some values we'll need after the message is downloaded (via platform APIs)
+ final Bundle extras = new Bundle();
+ extras.putString(EXTRA_MESSAGE_ID, messageId);
+ extras.putString(EXTRA_CONVERSATION_ID, conversationId);
+ extras.putString(EXTRA_PARTICIPANT_ID, participantId);
+ extras.putInt(EXTRA_STATUS_IF_FAILED, statusIfFailed);
+
+ // Start the download
+ final MmsUtils.StatusPlusUri status = MmsUtils.downloadMmsMessage(context,
+ notificationUri, subId, subPhoneNumber, transactionId, contentLocation,
+ autoDownload, receivedTimestampRoundedToSecond / 1000L, extras);
+ if (status == MmsUtils.STATUS_PENDING) {
+ // Async download; no status yet
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "DownloadMmsAction: Downloading MMS message " + messageId
+ + " asynchronously; waiting for pending intent to signal completion");
+ }
+ } else {
+ // Inform sync that message has been added at local received timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(receivedTimestampRoundedToSecond);
+ // Handle downloaded message
+ ProcessDownloadedMmsAction.processMessageDownloadFastFailed(messageId,
+ notificationUri, conversationId, participantId, contentLocation, subId,
+ subPhoneNumber, statusIfFailed, autoDownload, transactionId,
+ status.resultCode);
+ }
+ return null;
+ }
+
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ // Nothing to do here; post-download actions handled by ProcessDownloadedMmsAction
+ return null;
+ }
+
+ @Override
+ protected Object processBackgroundFailure() {
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final String participantId = actionParameters.getString(KEY_PARTICIPANT_ID);
+ final int statusIfFailed = actionParameters.getInt(KEY_FAILURE_STATUS);
+ final int subId = actionParameters.getInt(KEY_SUB_ID);
+
+ ProcessDownloadedMmsAction.processDownloadActionFailure(messageId,
+ MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
+ conversationId, participantId, statusIfFailed, subId, transactionId);
+
+ return null;
+ }
+
+ static void updateMessageStatus(final Uri messageUri, final String messageId,
+ final String conversationId, final int status, final int rawStatus) {
+ final Context context = Factory.get().getApplicationContext();
+ // Downloading status just kept in local DB but need to fix up telephony DB first
+ if (status == MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING ||
+ status == MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING) {
+ MmsUtils.clearMmsStatus(context, messageUri);
+ }
+ // Then mark downloading status in our local DB
+ final ContentValues values = new ContentValues();
+ values.put(MessageColumns.STATUS, status);
+ values.put(MessageColumns.RAW_TELEPHONY_STATUS, rawStatus);
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ BugleDatabaseOperations.updateMessageRowIfExists(db, messageId, values);
+
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ }
+
+ private DownloadMmsAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<DownloadMmsAction> CREATOR
+ = new Parcelable.Creator<DownloadMmsAction>() {
+ @Override
+ public DownloadMmsAction createFromParcel(final Parcel in) {
+ return new DownloadMmsAction(in);
+ }
+
+ @Override
+ public DownloadMmsAction[] newArray(final int size) {
+ return new DownloadMmsAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java b/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java
new file mode 100644
index 0000000..ab320bf
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/DumpDatabaseAction.java
@@ -0,0 +1,124 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.LogUtil;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class DumpDatabaseAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ public static final String DUMP_NAME = "db_copy.db";
+ private static final int BUFFER_SIZE = 16384;
+
+ /**
+ * Copy the database to external storage
+ */
+ public static void dumpDatabase() {
+ final DumpDatabaseAction action = new DumpDatabaseAction();
+ action.start();
+ }
+
+ private DumpDatabaseAction() {
+ }
+
+ @Override
+ protected Object executeAction() {
+ final Context context = Factory.get().getApplicationContext();
+ final String dbName = DatabaseHelper.DATABASE_NAME;
+ BufferedOutputStream bos = null;
+ BufferedInputStream bis = null;
+
+ long originalSize = 0;
+ final File inFile = context.getDatabasePath(dbName);
+ if (inFile.exists() && inFile.isFile()) {
+ originalSize = inFile.length();
+ }
+ final File outFile = DebugUtils.getDebugFile(DUMP_NAME, true);
+ if (outFile != null) {
+ int totalBytes = 0;
+ try {
+ bos = new BufferedOutputStream(new FileOutputStream(outFile));
+ bis = new BufferedInputStream(new FileInputStream(inFile));
+
+ final byte[] buffer = new byte[BUFFER_SIZE];
+ int bytesRead;
+ while ((bytesRead = bis.read(buffer)) > 0) {
+ bos.write(buffer, 0, bytesRead);
+ totalBytes += bytesRead;
+ }
+ } catch (final IOException e) {
+ LogUtil.w(TAG, "Exception copying the database;"
+ + " destination may not be complete.", e);
+ } finally {
+ if (bos != null) {
+ try {
+ bos.close();
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+ }
+
+ if (bis != null) {
+ try {
+ bis.close();
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+ }
+ DebugUtils.ensureReadable(outFile);
+ LogUtil.i(TAG, "Dump complete; orig size: " + originalSize +
+ ", copy size: " + totalBytes);
+ }
+ }
+ return null;
+ }
+
+ private DumpDatabaseAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<DumpDatabaseAction> CREATOR
+ = new Parcelable.Creator<DumpDatabaseAction>() {
+ @Override
+ public DumpDatabaseAction createFromParcel(final Parcel in) {
+ return new DumpDatabaseAction(in);
+ }
+
+ @Override
+ public DumpDatabaseAction[] newArray(final int size) {
+ return new DumpDatabaseAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java b/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java
new file mode 100644
index 0000000..e3d131d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/FixupMessageStatusOnStartupAction.java
@@ -0,0 +1,114 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Action used to fixup actively downloading or sending status at startup - just in case we
+ * crash - never run this when a message might actually be sending or downloading.
+ */
+public class FixupMessageStatusOnStartupAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ public static void fixupMessageStatus() {
+ final FixupMessageStatusOnStartupAction action = new FixupMessageStatusOnStartupAction();
+ action.start();
+ }
+
+ private FixupMessageStatusOnStartupAction() {
+ }
+
+ @Override
+ protected Object executeAction() {
+ // Now mark any messages in active sending or downloading state as inactive
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ int downloadFailedCnt = 0;
+ int sendFailedCnt = 0;
+ try {
+ // For both sending and downloading messages, let's assume they failed.
+ // For MMS sent/downloaded via platform, the sent/downloaded pending intent
+ // may come back. That will update the message. User may see the message
+ // in wrong status within a short window if that happens. But this should
+ // rarely happen. This is a simple solution to situations like app gets killed
+ // while the pending intent is still in the fly. Alternatively, we could
+ // keep the status for platform sent/downloaded MMS and timeout these messages.
+ // But that is much more complex.
+ final ContentValues values = new ContentValues();
+ values.put(DatabaseHelper.MessageColumns.STATUS,
+ MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED);
+ downloadFailedCnt += db.update(DatabaseHelper.MESSAGES_TABLE, values,
+ DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+ new String[]{
+ Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING),
+ Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING)
+ });
+ values.clear();
+
+ values.clear();
+ values.put(DatabaseHelper.MessageColumns.STATUS,
+ MessageData.BUGLE_STATUS_OUTGOING_FAILED);
+ sendFailedCnt = db.update(DatabaseHelper.MESSAGES_TABLE, values,
+ DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+ new String[]{
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING),
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)
+ });
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ LogUtil.i(TAG, "Fixup: Send failed - " + sendFailedCnt
+ + " Download failed - " + downloadFailedCnt);
+
+ // Don't send contentObserver notifications as displayed text should not change
+ return null;
+ }
+
+ private FixupMessageStatusOnStartupAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<FixupMessageStatusOnStartupAction> CREATOR
+ = new Parcelable.Creator<FixupMessageStatusOnStartupAction>() {
+ @Override
+ public FixupMessageStatusOnStartupAction createFromParcel(final Parcel in) {
+ return new FixupMessageStatusOnStartupAction(in);
+ }
+
+ @Override
+ public FixupMessageStatusOnStartupAction[] newArray(final int size) {
+ return new FixupMessageStatusOnStartupAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java b/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java
new file mode 100644
index 0000000..b262141
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/GetOrCreateConversationAction.java
@@ -0,0 +1,173 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
+import com.android.messaging.datamodel.data.LaunchConversationData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Action used to get or create a conversation for a list of conversation participants.
+ */
+public class GetOrCreateConversationAction extends Action implements Parcelable {
+ /**
+ * Interface for GetOrCreateConversationAction listeners
+ */
+ public interface GetOrCreateConversationActionListener {
+ @RunsOnMainThread
+ abstract void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
+ final Object data, final String conversationId);
+
+ @RunsOnMainThread
+ abstract void onGetOrCreateConversationFailed(final ActionMonitor monitor,
+ final Object data);
+ }
+
+ public static GetOrCreateConversationActionMonitor getOrCreateConversation(
+ final ArrayList<ParticipantData> participants, final Object data,
+ final GetOrCreateConversationActionListener listener) {
+ final GetOrCreateConversationActionMonitor monitor = new
+ GetOrCreateConversationActionMonitor(data, listener);
+ final GetOrCreateConversationAction action = new GetOrCreateConversationAction(participants,
+ monitor.getActionKey());
+ action.start(monitor);
+ return monitor;
+ }
+
+
+ public static GetOrCreateConversationActionMonitor getOrCreateConversation(
+ final String[] recipients, final Object data, final LaunchConversationData listener) {
+ final ArrayList<ParticipantData> participants = new ArrayList<>();
+ for (String recipient : recipients) {
+ recipient = recipient.trim();
+ if (!TextUtils.isEmpty(recipient)) {
+ participants.add(ParticipantData.getFromRawPhoneBySystemLocale(recipient));
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "getOrCreateConversation hit empty recipient");
+ }
+ }
+ return getOrCreateConversation(participants, data, listener);
+ }
+
+ private static final String KEY_PARTICIPANTS_LIST = "participants_list";
+
+ private GetOrCreateConversationAction(final ArrayList<ParticipantData> participants,
+ final String actionKey) {
+ super(actionKey);
+ actionParameters.putParcelableArrayList(KEY_PARTICIPANTS_LIST, participants);
+ }
+
+ /**
+ * Lookup the conversation or create a new one.
+ */
+ @Override
+ protected Object executeAction() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // First find the thread id for this list of participants.
+ final ArrayList<ParticipantData> participants =
+ actionParameters.getParcelableArrayList(KEY_PARTICIPANTS_LIST);
+ BugleDatabaseOperations.sanitizeConversationParticipants(participants);
+ final ArrayList<String> recipients =
+ BugleDatabaseOperations.getRecipientsFromConversationParticipants(participants);
+
+ final long threadId = MmsUtils.getOrCreateThreadId(Factory.get().getApplicationContext(),
+ recipients);
+
+ if (threadId < 0) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't create a threadId in SMS db for numbers : " +
+ LogUtil.sanitizePII(recipients.toString()));
+ // TODO: Add a better way to indicate an error from executeAction.
+ return null;
+ }
+
+ final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
+ false, participants, false, false, null);
+
+ return conversationId;
+ }
+
+ /**
+ * A monitor that notifies a listener upon completion
+ */
+ public static class GetOrCreateConversationActionMonitor extends ActionMonitor
+ implements ActionCompletedListener {
+ private final GetOrCreateConversationActionListener mListener;
+
+ GetOrCreateConversationActionMonitor(final Object data,
+ final GetOrCreateConversationActionListener listener) {
+ super(STATE_CREATED, generateUniqueActionKey("GetOrCreateConversationAction"), data);
+ setCompletedListener(this);
+ mListener = listener;
+ }
+
+ @Override
+ public void onActionSucceeded(final ActionMonitor monitor,
+ final Action action, final Object data, final Object result) {
+ if (result == null) {
+ mListener.onGetOrCreateConversationFailed(monitor, data);
+ } else {
+ mListener.onGetOrCreateConversationSucceeded(monitor, data, (String) result);
+ }
+ }
+
+ @Override
+ public void onActionFailed(final ActionMonitor monitor,
+ final Action action, final Object data, final Object result) {
+ // TODO: Currently onActionFailed is only called if there is an error in
+ // processing requests, not for errors in the local processing.
+ Assert.fail("Unreachable");
+ mListener.onGetOrCreateConversationFailed(monitor, data);
+ }
+ }
+
+ private GetOrCreateConversationAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<GetOrCreateConversationAction> CREATOR
+ = new Parcelable.Creator<GetOrCreateConversationAction>() {
+ @Override
+ public GetOrCreateConversationAction createFromParcel(final Parcel in) {
+ return new GetOrCreateConversationAction(in);
+ }
+
+ @Override
+ public GetOrCreateConversationAction[] newArray(final int size) {
+ return new GetOrCreateConversationAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java b/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java
new file mode 100644
index 0000000..7bfcfe0
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/HandleLowStorageAction.java
@@ -0,0 +1,94 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.sms.SmsReleaseStorage;
+import com.android.messaging.util.Assert;
+
+/**
+ * Action used to handle low storage related issues on the device.
+ */
+public class HandleLowStorageAction extends Action implements Parcelable {
+ private static final int SUB_OP_CODE_CLEAR_MEDIA_MESSAGES = 100;
+ private static final int SUB_OP_CODE_CLEAR_OLD_MESSAGES = 101;
+
+ public static void handleDeleteMediaMessages(final long durationInMillis) {
+ final HandleLowStorageAction action = new HandleLowStorageAction(
+ SUB_OP_CODE_CLEAR_MEDIA_MESSAGES, durationInMillis);
+ action.start();
+ }
+
+ public static void handleDeleteOldMessages(final long durationInMillis) {
+ final HandleLowStorageAction action = new HandleLowStorageAction(
+ SUB_OP_CODE_CLEAR_OLD_MESSAGES, durationInMillis);
+ action.start();
+ }
+
+ private static final String KEY_SUB_OP_CODE = "sub_op_code";
+ private static final String KEY_CUTOFF_DURATION_MILLIS = "cutoff_duration_millis";
+
+ private HandleLowStorageAction(final int subOpcode, final long durationInMillis) {
+ super();
+ actionParameters.putInt(KEY_SUB_OP_CODE, subOpcode);
+ actionParameters.putLong(KEY_CUTOFF_DURATION_MILLIS, durationInMillis);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final int subOpCode = actionParameters.getInt(KEY_SUB_OP_CODE);
+ final long durationInMillis = actionParameters.getLong(KEY_CUTOFF_DURATION_MILLIS);
+ switch (subOpCode) {
+ case SUB_OP_CODE_CLEAR_MEDIA_MESSAGES:
+ SmsReleaseStorage.deleteMessages(0, durationInMillis);
+ break;
+
+ case SUB_OP_CODE_CLEAR_OLD_MESSAGES:
+ SmsReleaseStorage.deleteMessages(1, durationInMillis);
+ break;
+
+ default:
+ Assert.fail("Unsupported action type!");
+ break;
+ }
+ return true;
+ }
+
+ private HandleLowStorageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<HandleLowStorageAction> CREATOR
+ = new Parcelable.Creator<HandleLowStorageAction>() {
+ @Override
+ public HandleLowStorageAction createFromParcel(final Parcel in) {
+ return new HandleLowStorageAction(in);
+ }
+
+ @Override
+ public HandleLowStorageAction[] newArray(final int size) {
+ return new HandleLowStorageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java b/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
new file mode 100644
index 0000000..2567ca9
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
@@ -0,0 +1,480 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Action used to convert a draft message to an outgoing message. Its writes SMS messages to
+ * the telephony db, but {@link SendMessageAction} is responsible for inserting MMS message into
+ * the telephony DB. The latter also does the actual sending of the message in the background.
+ * The latter is also responsible for re-sending a failed message.
+ */
+public class InsertNewMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ private static long sLastSentMessageTimestamp = -1;
+
+ /**
+ * Insert message (no listener)
+ */
+ public static void insertNewMessage(final MessageData message) {
+ final InsertNewMessageAction action = new InsertNewMessageAction(message);
+ action.start();
+ }
+
+ /**
+ * Insert message (no listener) with a given non-default subId.
+ */
+ public static void insertNewMessage(final MessageData message, final int subId) {
+ Assert.isFalse(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
+ final InsertNewMessageAction action = new InsertNewMessageAction(message, subId);
+ action.start();
+ }
+
+ /**
+ * Insert message (no listener)
+ */
+ public static void insertNewMessage(final int subId, final String recipients,
+ final String messageText, final String subject) {
+ final InsertNewMessageAction action = new InsertNewMessageAction(
+ subId, recipients, messageText, subject);
+ action.start();
+ }
+
+ public static long getLastSentMessageTimestamp() {
+ return sLastSentMessageTimestamp;
+ }
+
+ private static final String KEY_SUB_ID = "sub_id";
+ private static final String KEY_MESSAGE = "message";
+ private static final String KEY_RECIPIENTS = "recipients";
+ private static final String KEY_MESSAGE_TEXT = "message_text";
+ private static final String KEY_SUBJECT_TEXT = "subject_text";
+
+ private InsertNewMessageAction(final MessageData message) {
+ this(message, ParticipantData.DEFAULT_SELF_SUB_ID);
+ actionParameters.putParcelable(KEY_MESSAGE, message);
+ }
+
+ private InsertNewMessageAction(final MessageData message, final int subId) {
+ super();
+ actionParameters.putParcelable(KEY_MESSAGE, message);
+ actionParameters.putInt(KEY_SUB_ID, subId);
+ }
+
+ private InsertNewMessageAction(final int subId, final String recipients,
+ final String messageText, final String subject) {
+ super();
+ if (TextUtils.isEmpty(recipients) || TextUtils.isEmpty(messageText)) {
+ Assert.fail("InsertNewMessageAction: Can't have empty recipients or message");
+ }
+ actionParameters.putInt(KEY_SUB_ID, subId);
+ actionParameters.putString(KEY_RECIPIENTS, recipients);
+ actionParameters.putString(KEY_MESSAGE_TEXT, messageText);
+ actionParameters.putString(KEY_SUBJECT_TEXT, subject);
+ }
+
+ /**
+ * Add message to database in pending state and queue actual sending
+ */
+ @Override
+ protected Object executeAction() {
+ LogUtil.i(TAG, "InsertNewMessageAction: inserting new message");
+ MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
+ if (message == null) {
+ LogUtil.i(TAG, "InsertNewMessageAction: Creating MessageData with provided data");
+ message = createMessage();
+ if (message == null) {
+ LogUtil.w(TAG, "InsertNewMessageAction: Could not create MessageData");
+ return null;
+ }
+ }
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final String conversationId = message.getConversationId();
+
+ final ParticipantData self = getSelf(db, conversationId, message);
+ if (self == null) {
+ return null;
+ }
+ message.bindSelfId(self.getId());
+ // If the user taps the Send button before the conversation draft is created/loaded by
+ // ReadDraftDataAction (maybe the action service thread was busy), the MessageData may not
+ // have the participant id set. It should be equal to the self id, so we'll use that.
+ if (message.getParticipantId() == null) {
+ message.bindParticipantId(self.getId());
+ }
+
+ final long timestamp = System.currentTimeMillis();
+ final ArrayList<String> recipients =
+ BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
+ if (recipients.size() < 1) {
+ LogUtil.w(TAG, "InsertNewMessageAction: message recipients is empty");
+ return null;
+ }
+ final int subId = self.getSubId();
+
+ // TODO: Work out whether to send with SMS or MMS (taking into account recipients)?
+ final boolean isSms = (message.getProtocol() == MessageData.PROTOCOL_SMS);
+ if (isSms) {
+ String sendingConversationId = conversationId;
+ if (recipients.size() > 1) {
+ // Broadcast SMS - put message in "fake conversation" before farming out to real 1:1
+ final long laterTimestamp = timestamp + 1;
+ // Send a single message
+ insertBroadcastSmsMessage(conversationId, message, subId,
+ laterTimestamp, recipients);
+
+ sendingConversationId = null;
+ }
+
+ for (final String recipient : recipients) {
+ // Start actual sending
+ insertSendingSmsMessage(message, subId, recipient,
+ timestamp, sendingConversationId);
+ }
+
+ // Can now clear draft from conversation (deleting attachments if necessary)
+ BugleDatabaseOperations.updateDraftMessageData(db, conversationId,
+ null /* message */, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
+ } else {
+ final long timestampRoundedToSecond = 1000 * ((timestamp + 500) / 1000);
+ // Write place holder message directly referencing parts from the draft
+ final MessageData messageToSend = insertSendingMmsMessage(conversationId,
+ message, timestampRoundedToSecond);
+
+ // Can now clear draft from conversation (preserving attachments which are now
+ // referenced by messageToSend)
+ BugleDatabaseOperations.updateDraftMessageData(db, conversationId,
+ messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
+ }
+ MessagingContentProvider.notifyConversationListChanged();
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
+
+ return message;
+ }
+
+ private ParticipantData getSelf(
+ final DatabaseWrapper db, final String conversationId, final MessageData message) {
+ ParticipantData self;
+ // Check if we are asked to bind to a non-default subId. This is directly passed in from
+ // the UI thread so that the sub id may be locked as soon as the user clicks on the Send
+ // button.
+ final int requestedSubId = actionParameters.getInt(
+ KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ if (requestedSubId != ParticipantData.DEFAULT_SELF_SUB_ID) {
+ self = BugleDatabaseOperations.getOrCreateSelf(db, requestedSubId);
+ } else {
+ String selfId = message.getSelfId();
+ if (selfId == null) {
+ // The conversation draft provides no self id hint, meaning that 1) conversation
+ // self id was not loaded AND 2) the user didn't pick a SIM from the SIM selector.
+ // In this case, use the conversation's self id.
+ final ConversationListItemData conversation =
+ ConversationListItemData.getExistingConversation(db, conversationId);
+ if (conversation != null) {
+ selfId = conversation.getSelfId();
+ } else {
+ LogUtil.w(LogUtil.BUGLE_DATAMODEL_TAG, "Conversation " + conversationId +
+ "already deleted before sending draft message " +
+ message.getMessageId() + ". Aborting InsertNewMessageAction.");
+ return null;
+ }
+ }
+
+ // We do not use SubscriptionManager.DEFAULT_SUB_ID for sending a message, so we need
+ // to bind the message to the system default subscription if it's unbound.
+ final ParticipantData unboundSelf = BugleDatabaseOperations.getExistingParticipant(
+ db, selfId);
+ if (unboundSelf.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID
+ && OsUtil.isAtLeastL_MR1()) {
+ final int defaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId();
+ self = BugleDatabaseOperations.getOrCreateSelf(db, defaultSubId);
+ } else {
+ self = unboundSelf;
+ }
+ }
+ return self;
+ }
+
+ /** Create MessageData using KEY_RECIPIENTS, KEY_MESSAGE_TEXT and KEY_SUBJECT */
+ private MessageData createMessage() {
+ // First find the thread id for this list of participants.
+ final String recipientsList = actionParameters.getString(KEY_RECIPIENTS);
+ final String messageText = actionParameters.getString(KEY_MESSAGE_TEXT);
+ final String subjectText = actionParameters.getString(KEY_SUBJECT_TEXT);
+ final int subId = actionParameters.getInt(
+ KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+
+ final ArrayList<ParticipantData> participants = new ArrayList<>();
+ for (final String recipient : recipientsList.split(",")) {
+ participants.add(ParticipantData.getFromRawPhoneBySimLocale(recipient, subId));
+ }
+ if (participants.size() == 0) {
+ Assert.fail("InsertNewMessage: Empty participants");
+ return null;
+ }
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ BugleDatabaseOperations.sanitizeConversationParticipants(participants);
+ final ArrayList<String> recipients =
+ BugleDatabaseOperations.getRecipientsFromConversationParticipants(participants);
+ if (recipients.size() == 0) {
+ Assert.fail("InsertNewMessage: Empty recipients");
+ return null;
+ }
+
+ final long threadId = MmsUtils.getOrCreateThreadId(Factory.get().getApplicationContext(),
+ recipients);
+
+ if (threadId < 0) {
+ Assert.fail("InsertNewMessage: Couldn't get threadId in SMS db for these recipients: "
+ + recipients.toString());
+ // TODO: How do we fail the action?
+ return null;
+ }
+
+ final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
+ false, participants, false, false, null);
+
+ final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId);
+
+ if (TextUtils.isEmpty(subjectText)) {
+ return MessageData.createDraftSmsMessage(conversationId, self.getId(), messageText);
+ } else {
+ return MessageData.createDraftMmsMessage(conversationId, self.getId(), messageText,
+ subjectText);
+ }
+ }
+
+ private void insertBroadcastSmsMessage(final String conversationId,
+ final MessageData message, final int subId, final long laterTimestamp,
+ final ArrayList<String> recipients) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "InsertNewMessageAction: Inserting broadcast SMS message "
+ + message.getMessageId());
+ }
+ final Context context = Factory.get().getApplicationContext();
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // Inform sync that message is being added at timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(laterTimestamp);
+
+ final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId);
+ final String address = TextUtils.join(" ", recipients);
+
+ final String messageText = message.getMessageText();
+ // Insert message into telephony database sms message table
+ final Uri messageUri = MmsUtils.insertSmsMessage(context,
+ Telephony.Sms.CONTENT_URI,
+ subId,
+ address,
+ messageText,
+ laterTimestamp,
+ Telephony.Sms.STATUS_COMPLETE,
+ Telephony.Sms.MESSAGE_TYPE_SENT, threadId);
+ if (messageUri != null && !TextUtils.isEmpty(messageUri.toString())) {
+ db.beginTransaction();
+ try {
+ message.updateSendingMessage(conversationId, messageUri, laterTimestamp);
+ message.markMessageSent(laterTimestamp);
+
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
+ conversationId, message.getMessageId(), laterTimestamp,
+ false /* senderBlocked */, false /* shouldAutoSwitchSelfId */);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "InsertNewMessageAction: Inserted broadcast SMS message "
+ + message.getMessageId() + ", uri = " + message.getSmsMessageUri());
+ }
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyPartsChanged();
+ } else {
+ // Ignore error as we only really care about the individual messages?
+ LogUtil.e(TAG,
+ "InsertNewMessageAction: No uri for broadcast SMS " + message.getMessageId()
+ + " inserted into telephony DB");
+ }
+ }
+
+ /**
+ * Insert SMS messaging into our database and telephony db.
+ */
+ private MessageData insertSendingSmsMessage(final MessageData content, final int subId,
+ final String recipient, final long timestamp, final String sendingConversationId) {
+ sLastSentMessageTimestamp = timestamp;
+
+ final Context context = Factory.get().getApplicationContext();
+
+ // Inform sync that message is being added at timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(timestamp);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // Send a single message
+ long threadId;
+ String conversationId;
+ if (sendingConversationId == null) {
+ // For 1:1 message generated sending broadcast need to look up threadId+conversationId
+ threadId = MmsUtils.getOrCreateSmsThreadId(context, recipient);
+ conversationId = BugleDatabaseOperations.getOrCreateConversationFromRecipient(
+ db, threadId, false /* sender blocked */,
+ ParticipantData.getFromRawPhoneBySimLocale(recipient, subId));
+ } else {
+ // Otherwise just look up threadId
+ threadId = BugleDatabaseOperations.getThreadId(db, sendingConversationId);
+ conversationId = sendingConversationId;
+ }
+
+ final String messageText = content.getMessageText();
+
+ // Insert message into telephony database sms message table
+ final Uri messageUri = MmsUtils.insertSmsMessage(context,
+ Telephony.Sms.CONTENT_URI,
+ subId,
+ recipient,
+ messageText,
+ timestamp,
+ Telephony.Sms.STATUS_NONE,
+ Telephony.Sms.MESSAGE_TYPE_SENT, threadId);
+
+ MessageData message = null;
+ if (messageUri != null && !TextUtils.isEmpty(messageUri.toString())) {
+ db.beginTransaction();
+ try {
+ message = MessageData.createDraftSmsMessage(conversationId,
+ content.getSelfId(), messageText);
+ message.updateSendingMessage(conversationId, messageUri, timestamp);
+
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+
+ // Do not update the conversation summary to reflect autogenerated 1:1 messages
+ if (sendingConversationId != null) {
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
+ conversationId, message.getMessageId(), timestamp,
+ false /* senderBlocked */, false /* shouldAutoSwitchSelfId */);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "InsertNewMessageAction: Inserted SMS message "
+ + message.getMessageId() + " (uri = " + message.getSmsMessageUri()
+ + ", timestamp = " + message.getReceivedTimeStamp() + ")");
+ }
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyPartsChanged();
+ } else {
+ LogUtil.e(TAG, "InsertNewMessageAction: No uri for SMS inserted into telephony DB");
+ }
+
+ return message;
+ }
+
+ /**
+ * Insert MMS messaging into our database.
+ */
+ private MessageData insertSendingMmsMessage(final String conversationId,
+ final MessageData message, final long timestamp) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ final List<MessagePartData> attachmentsUpdated = new ArrayList<>();
+ try {
+ sLastSentMessageTimestamp = timestamp;
+
+ // Insert "draft" message as placeholder until the final message is written to
+ // the telephony db
+ message.updateSendingMessage(conversationId, null/*messageUri*/, timestamp);
+
+ // No need to inform SyncManager as message currently has no Uri...
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
+ conversationId, message.getMessageId(), timestamp,
+ false /* senderBlocked */, false /* shouldAutoSwitchSelfId */);
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "InsertNewMessageAction: Inserted MMS message "
+ + message.getMessageId() + " (timestamp = " + timestamp + ")");
+ }
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyPartsChanged();
+
+ return message;
+ }
+
+ private InsertNewMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<InsertNewMessageAction> CREATOR
+ = new Parcelable.Creator<InsertNewMessageAction>() {
+ @Override
+ public InsertNewMessageAction createFromParcel(final Parcel in) {
+ return new InsertNewMessageAction(in);
+ }
+
+ @Override
+ public InsertNewMessageAction[] newArray(final int size) {
+ return new InsertNewMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java b/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java
new file mode 100644
index 0000000..441a5a2
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/LogTelephonyDatabaseAction.java
@@ -0,0 +1,153 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.Threads;
+import android.provider.Telephony.ThreadsColumns;
+
+import com.android.messaging.Factory;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.LogUtil;
+
+public class LogTelephonyDatabaseAction extends Action implements Parcelable {
+ // Because we use sanitizePII, we should also use BUGLE_TAG
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final String[] ALL_THREADS_PROJECTION = {
+ Threads._ID,
+ Threads.DATE,
+ Threads.MESSAGE_COUNT,
+ Threads.RECIPIENT_IDS,
+ Threads.SNIPPET,
+ Threads.SNIPPET_CHARSET,
+ Threads.READ,
+ Threads.ERROR,
+ Threads.HAS_ATTACHMENT };
+
+ // Constants from the Telephony Database
+ private static final int ID = 0;
+ private static final int DATE = 1;
+ private static final int MESSAGE_COUNT = 2;
+ private static final int RECIPIENT_IDS = 3;
+ private static final int SNIPPET = 4;
+ private static final int SNIPPET_CHAR_SET = 5;
+ private static final int READ = 6;
+ private static final int ERROR = 7;
+ private static final int HAS_ATTACHMENT = 8;
+
+ /**
+ * Log telephony data to logcat
+ */
+ public static void dumpDatabase() {
+ final LogTelephonyDatabaseAction action = new LogTelephonyDatabaseAction();
+ action.start();
+ }
+
+ private LogTelephonyDatabaseAction() {
+ }
+
+ @Override
+ protected Object executeAction() {
+ final Context context = Factory.get().getApplicationContext();
+
+ if (!DebugUtils.isDebugEnabled()) {
+ LogUtil.e(TAG, "Can't log telephony database unless debugging is enabled");
+ return null;
+ }
+
+ if (!LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.w(TAG, "Can't log telephony database unless DEBUG is turned on for TAG: " +
+ TAG);
+ return null;
+ }
+
+ LogUtil.d(TAG, "\n");
+ LogUtil.d(TAG, "Dump of canoncial_addresses table");
+ LogUtil.d(TAG, "*********************************");
+
+ Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+ Uri.parse("content://mms-sms/canonical-addresses"), null, null, null, null);
+
+ if (cursor == null) {
+ LogUtil.w(TAG, "null Cursor in content://mms-sms/canonical-addresses");
+ } else {
+ try {
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(0);
+ String number = cursor.getString(1);
+ LogUtil.d(TAG, LogUtil.sanitizePII("id: " + id + " number: " + number));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ LogUtil.d(TAG, "\n");
+ LogUtil.d(TAG, "Dump of threads table");
+ LogUtil.d(TAG, "*********************");
+
+ cursor = SqliteWrapper.query(context, context.getContentResolver(),
+ Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(),
+ ALL_THREADS_PROJECTION, null, null, "date ASC");
+ try {
+ while (cursor.moveToNext()) {
+ LogUtil.d(TAG, LogUtil.sanitizePII("threadId: " + cursor.getLong(ID) +
+ " " + ThreadsColumns.DATE + " : " + cursor.getLong(DATE) +
+ " " + ThreadsColumns.MESSAGE_COUNT + " : " + cursor.getInt(MESSAGE_COUNT) +
+ " " + ThreadsColumns.SNIPPET + " : " + cursor.getString(SNIPPET) +
+ " " + ThreadsColumns.READ + " : " + cursor.getInt(READ) +
+ " " + ThreadsColumns.ERROR + " : " + cursor.getInt(ERROR) +
+ " " + ThreadsColumns.HAS_ATTACHMENT + " : " +
+ cursor.getInt(HAS_ATTACHMENT) +
+ " " + ThreadsColumns.RECIPIENT_IDS + " : " +
+ cursor.getString(RECIPIENT_IDS)));
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ private LogTelephonyDatabaseAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<LogTelephonyDatabaseAction> CREATOR
+ = new Parcelable.Creator<LogTelephonyDatabaseAction>() {
+ @Override
+ public LogTelephonyDatabaseAction createFromParcel(final Parcel in) {
+ return new LogTelephonyDatabaseAction(in);
+ }
+
+ @Override
+ public LogTelephonyDatabaseAction[] newArray(final int size) {
+ return new LogTelephonyDatabaseAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/MarkAsReadAction.java b/src/com/android/messaging/datamodel/action/MarkAsReadAction.java
new file mode 100644
index 0000000..31bc59d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/MarkAsReadAction.java
@@ -0,0 +1,113 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Action used to mark all the messages in a conversation as read
+ */
+public class MarkAsReadAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+
+ /**
+ * Mark all the messages as read for a particular conversation.
+ */
+ public static void markAsRead(final String conversationId) {
+ final MarkAsReadAction action = new MarkAsReadAction(conversationId);
+ action.start();
+ }
+
+ private MarkAsReadAction(final String conversationId) {
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+
+ // TODO: Consider doing this in background service to avoid delaying other actions
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // Mark all messages in thread as read in telephony
+ final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId);
+ if (threadId != -1) {
+ MmsUtils.updateSmsReadStatus(threadId, Long.MAX_VALUE);
+ }
+
+ // Update local db
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(MessageColumns.CONVERSATION_ID, conversationId);
+ values.put(MessageColumns.READ, 1);
+ values.put(MessageColumns.SEEN, 1); // if they read it, they saw it
+
+ final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values,
+ "(" + MessageColumns.READ + " !=1 OR " +
+ MessageColumns.SEEN + " !=1 ) AND " +
+ MessageColumns.CONVERSATION_ID + "=?",
+ new String[] { conversationId });
+ if (count > 0) {
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ // After marking messages as read, update the notifications. This will
+ // clear the now stale notifications.
+ BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL);
+ return null;
+ }
+
+ private MarkAsReadAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<MarkAsReadAction> CREATOR
+ = new Parcelable.Creator<MarkAsReadAction>() {
+ @Override
+ public MarkAsReadAction createFromParcel(final Parcel in) {
+ return new MarkAsReadAction(in);
+ }
+
+ @Override
+ public MarkAsReadAction[] newArray(final int size) {
+ return new MarkAsReadAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java b/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java
new file mode 100644
index 0000000..28f55fd
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/MarkAsSeenAction.java
@@ -0,0 +1,126 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Action used to mark all messages as seen
+ */
+public class MarkAsSeenAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+
+ /**
+ * Mark all messages as seen.
+ */
+ public static void markAllAsSeen() {
+ final MarkAsSeenAction action = new MarkAsSeenAction((String) null/*conversationId*/);
+ action.start();
+ }
+
+ /**
+ * Mark all messages of a given conversation as seen.
+ */
+ public static void markAsSeen(final String conversationId) {
+ final MarkAsSeenAction action = new MarkAsSeenAction(conversationId);
+ action.start();
+ }
+
+ /**
+ * ctor for MarkAsSeenAction.
+ * @param conversationId the conversation id for which to mark as seen, or null to mark all
+ * messages as seen
+ */
+ public MarkAsSeenAction(final String conversationId) {
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final String conversationId =
+ actionParameters.getString(KEY_CONVERSATION_ID);
+ final boolean hasSpecificConversation = !TextUtils.isEmpty(conversationId);
+
+ // Everything in telephony should already have the seen bit set.
+ // Possible exception are messages which did not have seen set and
+ // were sync'ed into bugle.
+
+ // Now mark the messages as seen in the bugle db
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(MessageColumns.SEEN, 1);
+
+ if (hasSpecificConversation) {
+ final int count = db.update(DatabaseHelper.MESSAGES_TABLE, values,
+ MessageColumns.SEEN + " != 1 AND " +
+ MessageColumns.CONVERSATION_ID + "=?",
+ new String[] { conversationId });
+ if (count > 0) {
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ }
+ } else {
+ db.update(DatabaseHelper.MESSAGES_TABLE, values,
+ MessageColumns.SEEN + " != 1", null/*selectionArgs*/);
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ // After marking messages as seen, update the notifications. This will
+ // clear the now stale notifications.
+ BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL);
+ return null;
+ }
+
+ private MarkAsSeenAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<MarkAsSeenAction> CREATOR
+ = new Parcelable.Creator<MarkAsSeenAction>() {
+ @Override
+ public MarkAsSeenAction createFromParcel(final Parcel in) {
+ return new MarkAsSeenAction(in);
+ }
+
+ @Override
+ public MarkAsSeenAction[] newArray(final int size) {
+ return new MarkAsSeenAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java b/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java
new file mode 100644
index 0000000..fbd4e82
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ProcessDeliveryReportAction.java
@@ -0,0 +1,122 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.util.concurrent.TimeUnit;
+
+public class ProcessDeliveryReportAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ private static final String KEY_URI = "uri";
+ private static final String KEY_STATUS = "status";
+
+ private ProcessDeliveryReportAction(final Uri uri, final int status) {
+ actionParameters.putParcelable(KEY_URI, uri);
+ actionParameters.putInt(KEY_STATUS, status);
+ }
+
+ public static void deliveryReportReceived(final Uri uri, final int status) {
+ final ProcessDeliveryReportAction action = new ProcessDeliveryReportAction(uri, status);
+ action.start();
+ }
+
+ @Override
+ protected Object executeAction() {
+ final Uri smsMessageUri = actionParameters.getParcelable(KEY_URI);
+ final int status = actionParameters.getInt(KEY_STATUS);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final long messageRowId = ContentUris.parseId(smsMessageUri);
+ if (messageRowId < 0) {
+ LogUtil.e(TAG, "ProcessDeliveryReportAction: can't find message");
+ return null;
+ }
+ final long timeSentInMillis = System.currentTimeMillis();
+ // Update telephony provider
+ if (smsMessageUri != null) {
+ MmsUtils.updateSmsStatusAndDateSent(smsMessageUri, status, timeSentInMillis);
+ }
+
+ // Update local message
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ final int bugleStatus = SyncMessageBatch.bugleStatusForSms(true /*outgoing*/,
+ Telephony.Sms.MESSAGE_TYPE_SENT /* type */, status);
+ values.put(DatabaseHelper.MessageColumns.STATUS, bugleStatus);
+ values.put(DatabaseHelper.MessageColumns.SENT_TIMESTAMP,
+ TimeUnit.MILLISECONDS.toMicros(timeSentInMillis));
+
+ final MessageData messageData =
+ BugleDatabaseOperations.readMessageData(db, smsMessageUri);
+
+ // Check the message was not removed before the delivery report comes in
+ if (messageData != null) {
+ Assert.isTrue(smsMessageUri.equals(messageData.getSmsMessageUri()));
+
+ // Row must exist as was just loaded above (on ActionService thread)
+ BugleDatabaseOperations.updateMessageRow(db, messageData.getMessageId(), values);
+
+ MessagingContentProvider.notifyMessagesChanged(messageData.getConversationId());
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ return null;
+ }
+
+ private ProcessDeliveryReportAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ProcessDeliveryReportAction> CREATOR
+ = new Parcelable.Creator<ProcessDeliveryReportAction>() {
+ @Override
+ public ProcessDeliveryReportAction createFromParcel(final Parcel in) {
+ return new ProcessDeliveryReportAction(in);
+ }
+
+ @Override
+ public ProcessDeliveryReportAction[] newArray(final int size) {
+ return new ProcessDeliveryReportAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java b/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java
new file mode 100644
index 0000000..757ea05
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ProcessDownloadedMmsAction.java
@@ -0,0 +1,573 @@
+/*
+ * 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.datamodel.action;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.Mms;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DataModelException;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.MmsFileProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.mmslib.pdu.PduHeaders;
+import com.android.messaging.mmslib.pdu.RetrieveConf;
+import com.android.messaging.sms.DatabaseMessages;
+import com.android.messaging.sms.MmsSender;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Processes an MMS message after it has been downloaded.
+ * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure).
+ */
+public class ProcessDownloadedMmsAction extends Action {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ // Always set when message downloaded
+ private static final String KEY_DOWNLOADED_BY_PLATFORM = "downloaded_by_platform";
+ private static final String KEY_MESSAGE_ID = "message_id";
+ private static final String KEY_NOTIFICATION_URI = "notification_uri";
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+ private static final String KEY_PARTICIPANT_ID = "participant_id";
+ private static final String KEY_STATUS_IF_FAILED = "status_if_failed";
+
+ // Set when message downloaded by platform (L+)
+ private static final String KEY_RESULT_CODE = "result_code";
+ private static final String KEY_HTTP_STATUS_CODE = "http_status_code";
+ private static final String KEY_CONTENT_URI = "content_uri";
+ private static final String KEY_SUB_ID = "sub_id";
+ private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
+ private static final String KEY_TRANSACTION_ID = "transaction_id";
+ private static final String KEY_CONTENT_LOCATION = "content_location";
+ private static final String KEY_AUTO_DOWNLOAD = "auto_download";
+ private static final String KEY_RECEIVED_TIMESTAMP = "received_timestamp";
+
+ // Set when message downloaded by us (legacy)
+ private static final String KEY_STATUS = "status";
+ private static final String KEY_RAW_STATUS = "raw_status";
+ private static final String KEY_MMS_URI = "mms_uri";
+
+ // Used to send a deferred response in response to auto-download failure
+ private static final String KEY_SEND_DEFERRED_RESP_STATUS = "send_deferred_resp_status";
+
+ // Results passed from background worker to processCompletion
+ private static final String BUNDLE_REQUEST_STATUS = "request_status";
+ private static final String BUNDLE_RAW_TELEPHONY_STATUS = "raw_status";
+ private static final String BUNDLE_MMS_URI = "mms_uri";
+
+ // This is called when MMS lib API returns via PendingIntent
+ public static void processMessageDownloaded(final int resultCode, final Bundle extras) {
+ final String messageId = extras.getString(DownloadMmsAction.EXTRA_MESSAGE_ID);
+ final Uri contentUri = extras.getParcelable(DownloadMmsAction.EXTRA_CONTENT_URI);
+ final Uri notificationUri = extras.getParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI);
+ final String conversationId = extras.getString(DownloadMmsAction.EXTRA_CONVERSATION_ID);
+ final String participantId = extras.getString(DownloadMmsAction.EXTRA_PARTICIPANT_ID);
+ Assert.notNull(messageId);
+ Assert.notNull(contentUri);
+ Assert.notNull(notificationUri);
+ Assert.notNull(conversationId);
+ Assert.notNull(participantId);
+
+ final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
+ final Bundle params = action.actionParameters;
+ params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true);
+ params.putString(KEY_MESSAGE_ID, messageId);
+ params.putInt(KEY_RESULT_CODE, resultCode);
+ params.putInt(KEY_HTTP_STATUS_CODE,
+ extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
+ params.putParcelable(KEY_CONTENT_URI, contentUri);
+ params.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
+ params.putInt(KEY_SUB_ID,
+ extras.getInt(DownloadMmsAction.EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
+ params.putString(KEY_SUB_PHONE_NUMBER,
+ extras.getString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER));
+ params.putString(KEY_TRANSACTION_ID,
+ extras.getString(DownloadMmsAction.EXTRA_TRANSACTION_ID));
+ params.putString(KEY_CONTENT_LOCATION,
+ extras.getString(DownloadMmsAction.EXTRA_CONTENT_LOCATION));
+ params.putBoolean(KEY_AUTO_DOWNLOAD,
+ extras.getBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD));
+ params.putLong(KEY_RECEIVED_TIMESTAMP,
+ extras.getLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP));
+ params.putString(KEY_CONVERSATION_ID, conversationId);
+ params.putString(KEY_PARTICIPANT_ID, participantId);
+ params.putInt(KEY_STATUS_IF_FAILED,
+ extras.getInt(DownloadMmsAction.EXTRA_STATUS_IF_FAILED));
+ action.start();
+ }
+
+ // This is called for fast failing downloading (due to airplane mode or mobile data )
+ public static void processMessageDownloadFastFailed(final String messageId,
+ final Uri notificationUri, final String conversationId, final String participantId,
+ final String contentLocation, final int subId, final String subPhoneNumber,
+ final int statusIfFailed, final boolean autoDownload, final String transactionId,
+ final int resultCode) {
+ Assert.notNull(messageId);
+ Assert.notNull(notificationUri);
+ Assert.notNull(conversationId);
+ Assert.notNull(participantId);
+
+ final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
+ final Bundle params = action.actionParameters;
+ params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, true);
+ params.putString(KEY_MESSAGE_ID, messageId);
+ params.putInt(KEY_RESULT_CODE, resultCode);
+ params.putParcelable(KEY_NOTIFICATION_URI, notificationUri);
+ params.putInt(KEY_SUB_ID, subId);
+ params.putString(KEY_SUB_PHONE_NUMBER, subPhoneNumber);
+ params.putString(KEY_CONTENT_LOCATION, contentLocation);
+ params.putBoolean(KEY_AUTO_DOWNLOAD, autoDownload);
+ params.putString(KEY_CONVERSATION_ID, conversationId);
+ params.putString(KEY_PARTICIPANT_ID, participantId);
+ params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed);
+ params.putString(KEY_TRANSACTION_ID, transactionId);
+ action.start();
+ }
+
+ public static void processDownloadActionFailure(final String messageId, final int status,
+ final int rawStatus, final String conversationId, final String participantId,
+ final int statusIfFailed, final int subId, final String transactionId) {
+ Assert.notNull(messageId);
+ Assert.notNull(conversationId);
+ Assert.notNull(participantId);
+
+ final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
+ final Bundle params = action.actionParameters;
+ params.putBoolean(KEY_DOWNLOADED_BY_PLATFORM, false);
+ params.putString(KEY_MESSAGE_ID, messageId);
+ params.putInt(KEY_STATUS, status);
+ params.putInt(KEY_RAW_STATUS, rawStatus);
+ params.putString(KEY_CONVERSATION_ID, conversationId);
+ params.putString(KEY_PARTICIPANT_ID, participantId);
+ params.putInt(KEY_STATUS_IF_FAILED, statusIfFailed);
+ params.putInt(KEY_SUB_ID, subId);
+ params.putString(KEY_TRANSACTION_ID, transactionId);
+ action.start();
+ }
+
+ public static void sendDeferredRespStatus(final String messageId, final String transactionId,
+ final String contentLocation, final int subId) {
+ final ProcessDownloadedMmsAction action = new ProcessDownloadedMmsAction();
+ final Bundle params = action.actionParameters;
+ params.putString(KEY_MESSAGE_ID, messageId);
+ params.putString(KEY_TRANSACTION_ID, transactionId);
+ params.putString(KEY_CONTENT_LOCATION, contentLocation);
+ params.putBoolean(KEY_SEND_DEFERRED_RESP_STATUS, true);
+ params.putInt(KEY_SUB_ID, subId);
+ action.start();
+ }
+
+ private ProcessDownloadedMmsAction() {
+ // Callers must use one of the static methods above
+ }
+
+ @Override
+ protected Object executeAction() {
+ // Fire up the background worker
+ requestBackgroundWork();
+ return null;
+ }
+
+ @Override
+ protected Bundle doBackgroundWork() throws DataModelException {
+ final Context context = Factory.get().getApplicationContext();
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
+ final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
+ final boolean sendDeferredRespStatus =
+ actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS, false);
+
+ // Send a response indicating that auto-download failed
+ if (sendDeferredRespStatus) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "DownloadMmsAction: Auto-download of message " + messageId
+ + " failed; sending DEFERRED NotifyRespInd");
+ }
+ MmsUtils.sendNotifyResponseForMmsDownload(
+ context,
+ subId,
+ MmsUtils.stringToBytes(transactionId, "UTF-8"),
+ contentLocation,
+ PduHeaders.STATUS_DEFERRED);
+ return null;
+ }
+
+ // Processing a real MMS download
+ final boolean downloadedByPlatform = actionParameters.getBoolean(
+ KEY_DOWNLOADED_BY_PLATFORM);
+
+ final int status;
+ int rawStatus = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
+ Uri mmsUri = null;
+
+ if (downloadedByPlatform) {
+ final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
+ if (resultCode == Activity.RESULT_OK) {
+ final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
+ final File downloadedFile = MmsFileProvider.getFile(contentUri);
+ byte[] downloadedData = null;
+ try {
+ downloadedData = Files.toByteArray(downloadedFile);
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "ProcessDownloadedMmsAction: MMS download file not found: "
+ + downloadedFile.getAbsolutePath());
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "ProcessDownloadedMmsAction: Error reading MMS download file: "
+ + downloadedFile.getAbsolutePath(), e);
+ }
+
+ // Can delete the temp file now
+ if (downloadedFile.exists()) {
+ downloadedFile.delete();
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ProcessDownloadedMmsAction: Deleted temp file with "
+ + "downloaded MMS pdu: " + downloadedFile.getAbsolutePath());
+ }
+ }
+
+ if (downloadedData != null) {
+ final RetrieveConf retrieveConf =
+ MmsSender.parseRetrieveConf(downloadedData, subId);
+ if (MmsUtils.isDumpMmsEnabled()) {
+ MmsUtils.dumpPdu(downloadedData, retrieveConf);
+ }
+ if (retrieveConf != null) {
+ // Insert the downloaded MMS into telephony
+ final Uri notificationUri = actionParameters.getParcelable(
+ KEY_NOTIFICATION_URI);
+ final String subPhoneNumber = actionParameters.getString(
+ KEY_SUB_PHONE_NUMBER);
+ final boolean autoDownload = actionParameters.getBoolean(
+ KEY_AUTO_DOWNLOAD);
+ final long receivedTimestampInSeconds =
+ actionParameters.getLong(KEY_RECEIVED_TIMESTAMP);
+
+ // Inform sync we're adding a message to telephony
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(receivedTimestampInSeconds * 1000L);
+
+ final MmsUtils.StatusPlusUri result =
+ MmsUtils.insertDownloadedMessageAndSendResponse(context,
+ notificationUri, subId, subPhoneNumber, transactionId,
+ contentLocation, autoDownload, receivedTimestampInSeconds,
+ retrieveConf);
+ status = result.status;
+ rawStatus = result.rawStatus;
+ mmsUri = result.uri;
+ } else {
+ // Invalid response PDU
+ status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ }
+ } else {
+ // Failed to read download file
+ status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ }
+ } else {
+ LogUtil.w(TAG, "ProcessDownloadedMmsAction: Platform returned error resultCode: "
+ + resultCode);
+ final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
+ status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);
+ }
+ } else {
+ // Message was already processed by the internal API, or the download action failed.
+ // In either case, we just need to copy the status to the response bundle.
+ status = actionParameters.getInt(KEY_STATUS);
+ rawStatus = actionParameters.getInt(KEY_RAW_STATUS);
+ mmsUri = actionParameters.getParcelable(KEY_MMS_URI);
+ }
+
+ final Bundle response = new Bundle();
+ response.putInt(BUNDLE_REQUEST_STATUS, status);
+ response.putInt(BUNDLE_RAW_TELEPHONY_STATUS, rawStatus);
+ response.putParcelable(BUNDLE_MMS_URI, mmsUri);
+ return response;
+ }
+
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ if (response == null) {
+ // No message download to process; doBackgroundWork sent a notify deferred response
+ Assert.isTrue(actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS));
+ return null;
+ }
+
+ final int status = response.getInt(BUNDLE_REQUEST_STATUS);
+ final int rawStatus = response.getInt(BUNDLE_RAW_TELEPHONY_STATUS);
+ final Uri messageUri = response.getParcelable(BUNDLE_MMS_URI);
+ final boolean autoDownload = actionParameters.getBoolean(KEY_AUTO_DOWNLOAD);
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+
+ // Do post-processing on downloaded message
+ final MessageData message = processResult(status, rawStatus, messageUri);
+
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ // If we were trying to auto-download but have failed need to send the deferred response
+ if (autoDownload && message == null && status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
+ final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
+ final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
+ sendDeferredRespStatus(messageId, transactionId, contentLocation, subId);
+ }
+
+ if (autoDownload) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ MessageData toastMessage = message;
+ if (toastMessage == null) {
+ // If the downloaded failed (message is null), then we should announce the
+ // receiving of the wap push message. Load the wap push message here instead.
+ toastMessage = BugleDatabaseOperations.readMessageData(db, messageId);
+ }
+ if (toastMessage != null) {
+ final ParticipantData sender = ParticipantData.getFromId(
+ db, toastMessage.getParticipantId());
+ BugleActionToasts.onMessageReceived(
+ toastMessage.getConversationId(), sender, toastMessage);
+ }
+ } else {
+ final boolean success = message != null && status == MmsUtils.MMS_REQUEST_SUCCEEDED;
+ BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
+ // If download failed, use the wap push message's conversation instead
+ success ? message.getConversationId()
+ : actionParameters.getString(KEY_CONVERSATION_ID),
+ success, status, false/*isSms*/, subId, false /*isSend*/);
+ }
+
+ final boolean failed = (messageUri == null);
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(failed, this);
+ if (failed) {
+ BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
+ }
+
+ return message;
+ }
+
+ @Override
+ protected Object processBackgroundFailure() {
+ if (actionParameters.getBoolean(KEY_SEND_DEFERRED_RESP_STATUS)) {
+ // We can early-out for these failures. processResult is only designed to handle
+ // post-processing of MMS downloads (whether successful or not).
+ LogUtil.w(TAG,
+ "ProcessDownloadedMmsAction: Exception while sending deferred NotifyRespInd");
+ return null;
+ }
+
+ // Background worker threw an exception; require manual retry
+ processResult(MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
+ null /* mmsUri */);
+
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true /* failed */,
+ this);
+
+ return null;
+ }
+
+ private MessageData processResult(final int status, final int rawStatus, final Uri mmsUri) {
+ final Context context = Factory.get().getApplicationContext();
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ final Uri mmsNotificationUri = actionParameters.getParcelable(KEY_NOTIFICATION_URI);
+ final String notificationConversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final String notificationParticipantId = actionParameters.getString(KEY_PARTICIPANT_ID);
+ final int statusIfFailed = actionParameters.getInt(KEY_STATUS_IF_FAILED);
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+
+ Assert.notNull(messageId);
+
+ LogUtil.i(TAG, "ProcessDownloadedMmsAction: Processed MMS download of message " + messageId
+ + "; status is " + MmsUtils.getRequestStatusDescription(status));
+
+ DatabaseMessages.MmsMessage mms = null;
+ if (status == MmsUtils.MMS_REQUEST_SUCCEEDED && mmsUri != null) {
+ // Delete the initial M-Notification.ind from telephony
+ SqliteWrapper.delete(context, context.getContentResolver(),
+ mmsNotificationUri, null, null);
+
+ // Read the sent MMS from the telephony provider
+ mms = MmsUtils.loadMms(mmsUri);
+ }
+
+ boolean messageInFocusedConversation = false;
+ boolean messageInObservableConversation = false;
+ String conversationId = null;
+ MessageData message = null;
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ if (mms != null) {
+ final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId());
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+
+ final List<String> recipients = MmsUtils.getRecipientsByThread(mms.mThreadId);
+ String from = MmsUtils.getMmsSender(recipients, mms.getUri());
+ if (from == null) {
+ LogUtil.w(TAG,
+ "Downloaded an MMS without sender address; using unknown sender.");
+ from = ParticipantData.getUnknownSenderDestination();
+ }
+ final ParticipantData sender = ParticipantData.getFromRawPhoneBySimLocale(from,
+ subId);
+ final String senderParticipantId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender);
+ if (!senderParticipantId.equals(notificationParticipantId)) {
+ LogUtil.e(TAG, "ProcessDownloadedMmsAction: Downloaded MMS message "
+ + messageId + " has different sender (participantId = "
+ + senderParticipantId + ") than notification ("
+ + notificationParticipantId + ")");
+ }
+ final boolean blockedSender = BugleDatabaseOperations.isBlockedDestination(
+ db, sender.getNormalizedDestination());
+ conversationId = BugleDatabaseOperations.getOrCreateConversationFromThreadId(db,
+ mms.mThreadId, blockedSender, subId);
+
+ messageInFocusedConversation =
+ DataModel.get().isFocusedConversation(conversationId);
+ messageInObservableConversation =
+ DataModel.get().isNewMessageObservable(conversationId);
+
+ // TODO: Also write these values to the telephony provider
+ mms.mRead = messageInFocusedConversation;
+ mms.mSeen = messageInObservableConversation;
+
+ // Translate to our format
+ message = MmsUtils.createMmsMessage(mms, conversationId, senderParticipantId,
+ selfId, MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
+ // Update image sizes.
+ message.updateSizesForImageParts();
+ // Inform sync that message has been added at local received timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(message.getReceivedTimeStamp());
+ final MessageData current = BugleDatabaseOperations.readMessageData(db, messageId);
+ if (current == null) {
+ LogUtil.w(TAG, "Message deleted prior to update");
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+ } else {
+ // Overwrite existing notification message
+ message.updateMessageId(messageId);
+ // Write message
+ BugleDatabaseOperations.updateMessageInTransaction(db, message);
+ }
+
+ if (!TextUtils.equals(notificationConversationId, conversationId)) {
+ // If this is a group conversation, the message is moved. So the original
+ // 1v1 conversation (as referenced by notificationConversationId) could
+ // be left with no non-draft message. Delete the conversation if that
+ // happens. See the comment for the method below for why we need to do this.
+ if (!BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(
+ db, notificationConversationId)) {
+ BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(
+ db, notificationConversationId, messageId,
+ true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/);
+ }
+ }
+
+ BugleDatabaseOperations.refreshConversationMetadataInTransaction(db, conversationId,
+ true /*shouldAutoSwitchSelfId*/, blockedSender /*keepArchived*/);
+ } else {
+ messageInFocusedConversation =
+ DataModel.get().isFocusedConversation(notificationConversationId);
+
+ // Default to retry status unless status indicates otherwise
+ int bugleStatus = statusIfFailed;
+ if (status == MmsUtils.MMS_REQUEST_MANUAL_RETRY) {
+ bugleStatus = MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED;
+ } else if (status == MmsUtils.MMS_REQUEST_NO_RETRY) {
+ bugleStatus = MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE;
+ }
+ DownloadMmsAction.updateMessageStatus(mmsNotificationUri, messageId,
+ notificationConversationId, bugleStatus, rawStatus);
+
+ // Log MMS download failed
+ final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
+ final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
+
+ // Just in case this was the latest message update the summary data
+ BugleDatabaseOperations.refreshConversationMetadataInTransaction(db,
+ notificationConversationId, true /*shouldAutoSwitchSelfId*/,
+ false /*keepArchived*/);
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ if (mmsUri != null) {
+ // Update mms table with read status now we know the conversation id
+ final ContentValues values = new ContentValues(1);
+ values.put(Mms.READ, messageInFocusedConversation);
+ SqliteWrapper.update(context, context.getContentResolver(), mmsUri, values,
+ null, null);
+ }
+
+ // Show a notification to let the user know a new message has arrived
+ BugleNotifications.update(false /*silent*/, conversationId, BugleNotifications.UPDATE_ALL);
+
+ // Messages may have changed in two conversations
+ if (conversationId != null) {
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ }
+ MessagingContentProvider.notifyMessagesChanged(notificationConversationId);
+ MessagingContentProvider.notifyPartsChanged();
+
+ return message;
+ }
+
+ private ProcessDownloadedMmsAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ProcessDownloadedMmsAction> CREATOR
+ = new Parcelable.Creator<ProcessDownloadedMmsAction>() {
+ @Override
+ public ProcessDownloadedMmsAction createFromParcel(final Parcel in) {
+ return new ProcessDownloadedMmsAction(in);
+ }
+
+ @Override
+ public ProcessDownloadedMmsAction[] newArray(final int size) {
+ return new ProcessDownloadedMmsAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
new file mode 100644
index 0000000..8a41f4a
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
@@ -0,0 +1,470 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.ServiceState;
+
+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.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.ConnectivityUtil.ConnectivityListener;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Action used to lookup any messages in the pending send/download state and either fail them or
+ * retry their action. This action only initiates one retry at a time - further retries should be
+ * triggered by successful sending of a message, network status change or exponential backoff timer.
+ */
+public class ProcessPendingMessagesAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final int PENDING_INTENT_REQUEST_CODE = 101;
+
+ public static void processFirstPendingMessage() {
+ // Clear any pending alarms or connectivity events
+ unregister();
+ // Clear retry count
+ setRetry(0);
+
+ // Start action
+ final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
+ action.start();
+ }
+
+ public static void scheduleProcessPendingMessagesAction(final boolean failed,
+ final Action processingAction) {
+ LogUtil.i(TAG, "ProcessPendingMessagesAction: Scheduling pending messages"
+ + (failed ? "(message failed)" : ""));
+ // Can safely clear any pending alarms or connectivity events as either an action
+ // is currently running or we will run now or register if pending actions possible.
+ unregister();
+
+ final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
+ boolean scheduleAlarm = false;
+ // If message succeeded and if Bugle is default SMS app just carry on with next message
+ if (!failed && isDefaultSmsApp) {
+ // Clear retry attempt count as something just succeeded
+ setRetry(0);
+
+ // Lookup and queue next message for immediate processing by background worker
+ // iff there are no pending messages this will do nothing and return true.
+ final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
+ if (action.queueActions(processingAction)) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ if (processingAction.hasBackgroundActions()) {
+ LogUtil.v(TAG, "ProcessPendingMessagesAction: Action queued");
+ } else {
+ LogUtil.v(TAG, "ProcessPendingMessagesAction: No actions to queue");
+ }
+ }
+ // Have queued next action if needed, nothing more to do
+ return;
+ }
+ // In case of error queuing schedule a retry
+ scheduleAlarm = true;
+ LogUtil.w(TAG, "ProcessPendingMessagesAction: Action failed to queue; retrying");
+ }
+ if (getHavePendingMessages() || scheduleAlarm) {
+ // Still have a pending message that needs to be queued for processing
+ final ConnectivityListener listener = new ConnectivityListener() {
+ @Override
+ public void onConnectivityStateChanged(final Context context, final Intent intent) {
+ final int networkType =
+ MmsUtils.getConnectivityEventNetworkType(context, intent);
+ if (networkType != ConnectivityManager.TYPE_MOBILE) {
+ return;
+ }
+ final boolean isConnected = !intent.getBooleanExtra(
+ ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ // TODO: Should we check in more detail?
+ if (isConnected) {
+ onConnected();
+ }
+ }
+
+ @Override
+ public void onPhoneStateChanged(final Context context, final int serviceState) {
+ if (serviceState == ServiceState.STATE_IN_SERVICE) {
+ onConnected();
+ }
+ }
+
+ private void onConnected() {
+ LogUtil.i(TAG, "ProcessPendingMessagesAction: Now connected; starting action");
+
+ // Clear any pending alarms or connectivity events but leave attempt count alone
+ unregister();
+
+ // Start action
+ final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
+ action.start();
+ }
+ };
+ // Read and increment attempt number from shared prefs
+ final int retryAttempt = getNextRetry();
+ register(listener, retryAttempt);
+ } else {
+ // No more pending messages (presumably the message that failed has expired) or it
+ // may be possible that a send and a download are already in process.
+ // Clear retry attempt count.
+ // TODO Might be premature if send and download in process...
+ // but worst case means we try to send a bit more often.
+ setRetry(0);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "ProcessPendingMessagesAction: No more pending messages");
+ }
+ }
+ }
+
+ private static void register(final ConnectivityListener listener, final int retryAttempt) {
+ int retryNumber = retryAttempt;
+
+ // Register to be notified about connectivity changes
+ DataModel.get().getConnectivityUtil().register(listener);
+
+ final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
+ final long initialBackoffMs = BugleGservices.get().getLong(
+ BugleGservicesKeys.INITIAL_MESSAGE_RESEND_DELAY_MS,
+ BugleGservicesKeys.INITIAL_MESSAGE_RESEND_DELAY_MS_DEFAULT);
+ final long maxDelayMs = BugleGservices.get().getLong(
+ BugleGservicesKeys.MAX_MESSAGE_RESEND_DELAY_MS,
+ BugleGservicesKeys.MAX_MESSAGE_RESEND_DELAY_MS_DEFAULT);
+ long delayMs;
+ long nextDelayMs = initialBackoffMs;
+ do {
+ delayMs = nextDelayMs;
+ retryNumber--;
+ nextDelayMs = delayMs * 2;
+ }
+ while (retryNumber > 0 && nextDelayMs < maxDelayMs);
+
+ LogUtil.i(TAG, "ProcessPendingMessagesAction: Registering for retry #" + retryAttempt
+ + " in " + delayMs + " ms");
+
+ action.schedule(PENDING_INTENT_REQUEST_CODE, delayMs);
+ }
+
+ private static void unregister() {
+ // Clear any pending alarms or connectivity events
+ DataModel.get().getConnectivityUtil().unregister();
+
+ final ProcessPendingMessagesAction action = new ProcessPendingMessagesAction();
+ action.schedule(PENDING_INTENT_REQUEST_CODE, Long.MAX_VALUE);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "ProcessPendingMessagesAction: Unregistering for connectivity changed "
+ + "events and clearing scheduled alarm");
+ }
+ }
+
+ private static void setRetry(final int retryAttempt) {
+ final BuglePrefs prefs = Factory.get().getApplicationPrefs();
+ prefs.putInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, retryAttempt);
+ }
+
+ private static int getNextRetry() {
+ final BuglePrefs prefs = Factory.get().getApplicationPrefs();
+ final int retryAttempt =
+ prefs.getInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, 0) + 1;
+ prefs.putInt(BuglePrefsKeys.PROCESS_PENDING_MESSAGES_RETRY_COUNT, retryAttempt);
+ return retryAttempt;
+ }
+
+ private ProcessPendingMessagesAction() {
+ }
+
+ /**
+ * Read from the DB and determine if there are any messages we should process
+ * @return true if we have pending messages
+ */
+ private static boolean getHavePendingMessages() {
+ 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) {
+ return true;
+ }
+ }
+ // Messages may be in the process of sending/downloading even when there are no pending
+ // messages...
+ return false;
+ }
+
+ /**
+ * Queue any pending actions
+ * @param actionState
+ * @return true if action queued (or no actions to queue) else false
+ */
+ private boolean queueActions(final Action processingAction) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final long now = System.currentTimeMillis();
+ boolean succeeded = true;
+
+ // Will queue no more than one message 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;
+ }
+ }
+ 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 "
+ + toDownloadMessageId + " for download");
+ succeeded = false;
+ }
+ }
+ if (toSendMessageId == null && toDownloadMessageId == null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download");
+ }
+ }
+ return succeeded;
+ }
+
+ @Override
+ protected Object executeAction() {
+ // If triggered by alarm will not have unregistered yet
+ unregister();
+
+ if (PhoneUtils.getDefault().isDefaultSmsApp()) {
+ queueActions(this);
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "ProcessPendingMessagesAction: Not default SMS app; rescheduling");
+ }
+ scheduleProcessPendingMessagesAction(true, this);
+ }
+
+ return null;
+ }
+
+ private static String findNextMessageToSend(final DatabaseWrapper db, final long now) {
+ String toSendMessageId = null;
+ db.beginTransaction();
+ Cursor sending = null;
+ Cursor cursor = null;
+ int sendingCnt = 0;
+ int pendingCnt = 0;
+ int failedCnt = 0;
+ try {
+ // First check to see if we have any messages already sending
+ sending = db.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(),
+ DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+ new String[]{Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING),
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)},
+ null,
+ null,
+ DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
+ final boolean messageCurrentlySending = sending.moveToNext();
+ sendingCnt = sending.getCount();
+ // Look for messages we could send
+ final ContentValues values = new ContentValues();
+ values.put(DatabaseHelper.MessageColumns.STATUS,
+ MessageData.BUGLE_STATUS_OUTGOING_FAILED);
+ cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(),
+ DatabaseHelper.MessageColumns.STATUS + " IN ("
+ + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ","
+ + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")",
+ null,
+ null,
+ null,
+ DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
+ pendingCnt = cursor.getCount();
+
+ while (cursor.moveToNext()) {
+ final MessageData message = new MessageData();
+ message.bind(cursor);
+ if (message.getInResendWindow(now)) {
+ // If no messages currently sending
+ if (!messageCurrentlySending) {
+ // Resend this message
+ toSendMessageId = message.getMessageId();
+ // Before queuing the message for resending, check if the message's self is
+ // active. If not, switch back to the system's default subscription.
+ if (OsUtil.isAtLeastL_MR1()) {
+ final ParticipantData messageSelf = BugleDatabaseOperations
+ .getExistingParticipant(db, message.getSelfId());
+ if (messageSelf == null || !messageSelf.isActiveSubscription()) {
+ final ParticipantData defaultSelf = BugleDatabaseOperations
+ .getOrCreateSelf(db, PhoneUtils.getDefault()
+ .getDefaultSmsSubscriptionId());
+ if (defaultSelf != null) {
+ message.bindSelfId(defaultSelf.getId());
+ final ContentValues selfValues = new ContentValues();
+ selfValues.put(MessageColumns.SELF_PARTICIPANT_ID,
+ defaultSelf.getId());
+ BugleDatabaseOperations.updateMessageRow(db,
+ message.getMessageId(), selfValues);
+ MessagingContentProvider.notifyMessagesChanged(
+ message.getConversationId());
+ }
+ }
+ }
+ }
+ break;
+ } else {
+ failedCnt++;
+
+ // Mark message as failed
+ BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values);
+ MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ if (cursor != null) {
+ cursor.close();
+ }
+ if (sending != null) {
+ sending.close();
+ }
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ProcessPendingMessagesAction: "
+ + sendingCnt + " messages already sending, "
+ + pendingCnt + " messages to send, "
+ + failedCnt + " failed messages");
+ }
+
+ return toSendMessageId;
+ }
+
+ private static String findNextMessageToDownload(final DatabaseWrapper db, final long now) {
+ String toDownloadMessageId = null;
+ db.beginTransaction();
+ Cursor cursor = null;
+ int downloadingCnt = 0;
+ int pendingCnt = 0;
+ try {
+ // First check if we have any messages already downloading
+ downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
+ DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+ new String[] {
+ Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING),
+ Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING)
+ });
+
+ // TODO: This query is not actually needed if downloadingCnt == 0.
+ cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection(),
+ DatabaseHelper.MessageColumns.STATUS + " =? OR "
+ + DatabaseHelper.MessageColumns.STATUS + " =?",
+ new String[]{
+ Integer.toString(
+ MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD),
+ Integer.toString(
+ MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD)
+ },
+ null,
+ null,
+ DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
+
+ pendingCnt = cursor.getCount();
+
+ // If no messages are currently downloading and there is a download pending,
+ // queue the download of the oldest pending message.
+ if (downloadingCnt == 0 && cursor.moveToNext()) {
+ // Always start the next pending message. We will check if a download has
+ // expired in DownloadMmsAction and mark message failed there.
+ final MessageData message = new MessageData();
+ message.bind(cursor);
+ toDownloadMessageId = message.getMessageId();
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ProcessPendingMessagesAction: "
+ + downloadingCnt + " messages already downloading, "
+ + pendingCnt + " messages to download");
+ }
+
+ return toDownloadMessageId;
+ }
+
+ private ProcessPendingMessagesAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ProcessPendingMessagesAction> CREATOR
+ = new Parcelable.Creator<ProcessPendingMessagesAction>() {
+ @Override
+ public ProcessPendingMessagesAction createFromParcel(final Parcel in) {
+ return new ProcessPendingMessagesAction(in);
+ }
+
+ @Override
+ public ProcessPendingMessagesAction[] newArray(final int size) {
+ return new ProcessPendingMessagesAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
new file mode 100644
index 0000000..f408e47
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
@@ -0,0 +1,310 @@
+/*
+ * 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.datamodel.action;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MmsFileProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.pdu.SendConf;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.sms.MmsSender;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+* Update message status to reflect success or failure
+* Can also update the message itself if a "final" message is now available from telephony db
+*/
+public class ProcessSentMessageAction extends Action {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ // These are always set
+ private static final String KEY_SMS = "is_sms";
+ private static final String KEY_SENT_BY_PLATFORM = "sent_by_platform";
+
+ // These are set when we're processing a message sent by the user. They are null for messages
+ // sent automatically (e.g. a NotifyRespInd/AcknowledgeInd sent in response to a download).
+ private static final String KEY_MESSAGE_ID = "message_id";
+ private static final String KEY_MESSAGE_URI = "message_uri";
+ private static final String KEY_UPDATED_MESSAGE_URI = "updated_message_uri";
+ private static final String KEY_SUB_ID = "sub_id";
+
+ // These are set for messages sent by the platform (L+)
+ public static final String KEY_RESULT_CODE = "result_code";
+ public static final String KEY_HTTP_STATUS_CODE = "http_status_code";
+ private static final String KEY_CONTENT_URI = "content_uri";
+ private static final String KEY_RESPONSE = "response";
+ private static final String KEY_RESPONSE_IMPORTANT = "response_important";
+
+ // These are set for messages we sent ourself (legacy), or which we fast-failed before sending.
+ private static final String KEY_STATUS = "status";
+ private static final String KEY_RAW_STATUS = "raw_status";
+
+ // This is called when MMS lib API returns via PendingIntent
+ public static void processMmsSent(final int resultCode, final Uri messageUri,
+ final Bundle extras) {
+ final ProcessSentMessageAction action = new ProcessSentMessageAction();
+ final Bundle params = action.actionParameters;
+ params.putBoolean(KEY_SMS, false);
+ params.putBoolean(KEY_SENT_BY_PLATFORM, true);
+ params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID));
+ params.putParcelable(KEY_MESSAGE_URI, messageUri);
+ params.putParcelable(KEY_UPDATED_MESSAGE_URI,
+ extras.getParcelable(SendMessageAction.EXTRA_UPDATED_MESSAGE_URI));
+ params.putInt(KEY_SUB_ID,
+ extras.getInt(SendMessageAction.KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
+ params.putInt(KEY_RESULT_CODE, resultCode);
+ params.putInt(KEY_HTTP_STATUS_CODE, extras.getInt(SmsManager.EXTRA_MMS_HTTP_STATUS, 0));
+ params.putParcelable(KEY_CONTENT_URI,
+ extras.getParcelable(SendMessageAction.EXTRA_CONTENT_URI));
+ params.putByteArray(KEY_RESPONSE, extras.getByteArray(SmsManager.EXTRA_MMS_DATA));
+ params.putBoolean(KEY_RESPONSE_IMPORTANT,
+ extras.getBoolean(SendMessageAction.EXTRA_RESPONSE_IMPORTANT));
+ action.start();
+ }
+
+ public static void processMessageSentFastFailed(final String messageId,
+ final Uri messageUri, final Uri updatedMessageUri, final int subId, final boolean isSms,
+ final int status, final int rawStatus, final int resultCode) {
+ final ProcessSentMessageAction action = new ProcessSentMessageAction();
+ final Bundle params = action.actionParameters;
+ params.putBoolean(KEY_SMS, isSms);
+ params.putBoolean(KEY_SENT_BY_PLATFORM, false);
+ params.putString(KEY_MESSAGE_ID, messageId);
+ params.putParcelable(KEY_MESSAGE_URI, messageUri);
+ params.putParcelable(KEY_UPDATED_MESSAGE_URI, updatedMessageUri);
+ params.putInt(KEY_SUB_ID, subId);
+ params.putInt(KEY_STATUS, status);
+ params.putInt(KEY_RAW_STATUS, rawStatus);
+ params.putInt(KEY_RESULT_CODE, resultCode);
+ action.start();
+ }
+
+ private ProcessSentMessageAction() {
+ // Callers must use one of the static methods above
+ }
+
+ /**
+ * Update message status to reflect success or failure
+ * Can also update the message itself if a "final" message is now available from telephony db
+ */
+ @Override
+ protected Object executeAction() {
+ final Context context = Factory.get().getApplicationContext();
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ final Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
+ final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI);
+ final boolean isSms = actionParameters.getBoolean(KEY_SMS);
+ final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM);
+
+ int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY);
+ int rawStatus = actionParameters.getInt(KEY_RAW_STATUS,
+ MmsUtils.PDU_HEADER_VALUE_UNDEFINED);
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+
+ if (sentByPlatform) {
+ // Delete temporary file backing the contentUri passed to MMS service
+ final Uri contentUri = actionParameters.getParcelable(KEY_CONTENT_URI);
+ Assert.isTrue(contentUri != null);
+ final File tempFile = MmsFileProvider.getFile(contentUri);
+ long messageSize = 0;
+ if (tempFile.exists()) {
+ messageSize = tempFile.length();
+ tempFile.delete();
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "ProcessSentMessageAction: Deleted temp file with outgoing "
+ + "MMS pdu: " + contentUri);
+ }
+ }
+
+ final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
+ final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT);
+ if (resultCode == Activity.RESULT_OK) {
+ if (responseImportant) {
+ // Get the status from the response PDU and update telephony
+ final byte[] response = actionParameters.getByteArray(KEY_RESPONSE);
+ final SendConf sendConf = MmsSender.parseSendConf(response, subId);
+ if (sendConf != null) {
+ final MmsUtils.StatusPlusUri result =
+ MmsUtils.updateSentMmsMessageStatus(context, messageUri, sendConf);
+ status = result.status;
+ rawStatus = result.rawStatus;
+ }
+ }
+ } else {
+ String errorMsg = "ProcessSentMessageAction: Platform returned error resultCode: "
+ + resultCode;
+ final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
+ if (httpStatusCode != 0) {
+ errorMsg += (", HTTP status code: " + httpStatusCode);
+ }
+ LogUtil.w(TAG, errorMsg);
+ status = MmsSender.getErrorResultStatus(resultCode, httpStatusCode);
+
+ // Check for MMS messages that failed because they exceeded the maximum size,
+ // indicated by an I/O error from the platform.
+ if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) {
+ if (messageSize > MmsConfig.get(subId).getMaxMessageSize()) {
+ rawStatus = MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG;
+ }
+ }
+ }
+ }
+ if (messageId != null) {
+ final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
+ final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
+ processResult(
+ messageId, updatedMessageUri, status, rawStatus, isSms, this, subId,
+ resultCode, httpStatusCode);
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "ProcessSentMessageAction: No sent message to process (it was "
+ + "probably a notify response for an MMS download)");
+ }
+ }
+ return null;
+ }
+
+ static void processResult(final String messageId, Uri updatedMessageUri, int status,
+ final int rawStatus, final boolean isSms, final Action processingAction,
+ final int subId, final int resultCode, final int httpStatusCode) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
+ final MessageData originalMessage = message;
+ if (message == null) {
+ LogUtil.w(TAG, "ProcessSentMessageAction: Sent message " + messageId
+ + " missing from local database");
+ return;
+ }
+ final String conversationId = message.getConversationId();
+ if (updatedMessageUri != null) {
+ // Update message if we have newly written final message in the telephony db
+ final MessageData update = MmsUtils.readSendingMmsMessage(updatedMessageUri,
+ conversationId, message.getParticipantId(), message.getSelfId());
+ if (update != null) {
+ // Set message Id of final message to that of the existing place holder.
+ update.updateMessageId(message.getMessageId());
+ // Update image sizes.
+ update.updateSizesForImageParts();
+ // Temp attachments are no longer needed
+ for (final MessagePartData part : message.getParts()) {
+ part.destroySync();
+ }
+ message = update;
+ // processResult will rewrite the complete message as part of update
+ } else {
+ updatedMessageUri = null;
+ status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ LogUtil.e(TAG, "ProcessSentMessageAction: Unable to read sending message");
+ }
+ }
+
+ final long timestamp = System.currentTimeMillis();
+ boolean failed;
+ if (status == MmsUtils.MMS_REQUEST_SUCCEEDED) {
+ message.markMessageSent(timestamp);
+ failed = false;
+ } else if (status == MmsUtils.MMS_REQUEST_AUTO_RETRY
+ && message.getInResendWindow(timestamp)) {
+ message.markMessageNotSent(timestamp);
+ message.setRawTelephonyStatus(rawStatus);
+ failed = false;
+ } else {
+ message.markMessageFailed(timestamp);
+ message.setRawTelephonyStatus(rawStatus);
+ message.setMessageSeen(false);
+ failed = true;
+ }
+
+ // We have special handling for when a message to an emergency number fails. In this case,
+ // we notify immediately of any failure (even if we auto-retry), and instruct the user to
+ // try calling the emergency number instead.
+ if (status != MmsUtils.MMS_REQUEST_SUCCEEDED) {
+ final ArrayList<String> recipients =
+ BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
+ for (final String recipient : recipients) {
+ if (PhoneNumberUtils.isEmergencyNumber(recipient)) {
+ BugleNotifications.notifyEmergencySmsFailed(recipient, conversationId);
+ message.markMessageFailedEmergencyNumber(timestamp);
+ failed = true;
+ break;
+ }
+ }
+ }
+
+ // Update the message status and optionally refresh the message with final parts/values.
+ if (SendMessageAction.updateMessageAndStatus(isSms, message, updatedMessageUri, failed)) {
+ // We shouldn't show any notifications if we're not allowed to modify Telephony for
+ // this message.
+ if (failed) {
+ BugleNotifications.update(false, BugleNotifications.UPDATE_ERRORS);
+ }
+ BugleActionToasts.onSendMessageOrManualDownloadActionCompleted(
+ conversationId, !failed, status, isSms, subId, true/*isSend*/);
+ }
+
+ LogUtil.i(TAG, "ProcessSentMessageAction: Done sending " + (isSms ? "SMS" : "MMS")
+ + " message " + message.getMessageId()
+ + " in conversation " + conversationId
+ + "; status is " + MmsUtils.getRequestStatusDescription(status));
+
+ // Whether we succeeded or failed we will check and maybe schedule some more work
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(
+ status != MmsUtils.MMS_REQUEST_SUCCEEDED, processingAction);
+ }
+
+ private ProcessSentMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ProcessSentMessageAction> CREATOR
+ = new Parcelable.Creator<ProcessSentMessageAction>() {
+ @Override
+ public ProcessSentMessageAction createFromParcel(final Parcel in) {
+ return new ProcessSentMessageAction(in);
+ }
+
+ @Override
+ public ProcessSentMessageAction[] newArray(final int size) {
+ return new ProcessSentMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java b/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java
new file mode 100644
index 0000000..7ac646b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ReadDraftDataAction.java
@@ -0,0 +1,166 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+public class ReadDraftDataAction extends Action implements Parcelable {
+
+ /**
+ * Interface for ReadDraftDataAction listeners
+ */
+ public interface ReadDraftDataActionListener {
+ @RunsOnMainThread
+ abstract void onReadDraftDataSucceeded(final ReadDraftDataAction action,
+ final Object data, final MessageData message,
+ final ConversationListItemData conversation);
+ @RunsOnMainThread
+ abstract void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data);
+ }
+
+ /**
+ * Read draft message and associated data (with listener)
+ */
+ public static ReadDraftDataActionMonitor readDraftData(final String conversationId,
+ final MessageData incomingDraft, final Object data,
+ final ReadDraftDataActionListener listener) {
+ final ReadDraftDataActionMonitor monitor = new ReadDraftDataActionMonitor(data,
+ listener);
+ final ReadDraftDataAction action = new ReadDraftDataAction(conversationId,
+ incomingDraft, monitor.getActionKey());
+ action.start(monitor);
+ return monitor;
+ }
+
+ private static final String KEY_CONVERSATION_ID = "conversationId";
+ private static final String KEY_INCOMING_DRAFT = "draftMessage";
+
+ private ReadDraftDataAction(final String conversationId, final MessageData incomingDraft,
+ final String actionKey) {
+ super(actionKey);
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ actionParameters.putParcelable(KEY_INCOMING_DRAFT, incomingDraft);
+ }
+
+ @VisibleForTesting
+ class DraftData {
+ public final MessageData message;
+ public final ConversationListItemData conversation;
+
+ DraftData(final MessageData message, final ConversationListItemData conversation) {
+ this.message = message;
+ this.conversation = conversation;
+ }
+ }
+
+ @Override
+ protected Object executeAction() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final MessageData incomingDraft = actionParameters.getParcelable(KEY_INCOMING_DRAFT);
+ final ConversationListItemData conversation =
+ ConversationListItemData.getExistingConversation(db, conversationId);
+ MessageData message = null;
+ if (conversation != null) {
+ if (incomingDraft == null) {
+ message = BugleDatabaseOperations.readDraftMessageData(db, conversationId,
+ conversation.getSelfId());
+ }
+ if (message == null) {
+ message = MessageData.createDraftMessage(conversationId, conversation.getSelfId(),
+ incomingDraft);
+ LogUtil.d(LogUtil.BUGLE_TAG, "ReadDraftMessage: created draft. "
+ + "conversationId=" + conversationId
+ + " selfId=" + conversation.getSelfId());
+ } else {
+ LogUtil.d(LogUtil.BUGLE_TAG, "ReadDraftMessage: read draft. "
+ + "conversationId=" + conversationId
+ + " selfId=" + conversation.getSelfId());
+ }
+ return new DraftData(message, conversation);
+ }
+ return null;
+ }
+
+ /**
+ * An operation that notifies a listener upon completion
+ */
+ public static class ReadDraftDataActionMonitor extends ActionMonitor
+ implements ActionCompletedListener {
+
+ private final ReadDraftDataActionListener mListener;
+
+ ReadDraftDataActionMonitor(final Object data,
+ final ReadDraftDataActionListener completed) {
+ super(STATE_CREATED, generateUniqueActionKey("ReadDraftDataAction"), data);
+ setCompletedListener(this);
+ mListener = completed;
+ }
+
+ @Override
+ public void onActionSucceeded(final ActionMonitor monitor,
+ final Action action, final Object data, final Object result) {
+ final DraftData draft = (DraftData) result;
+ if (draft == null) {
+ mListener.onReadDraftDataFailed((ReadDraftDataAction) action, data);
+ } else {
+ mListener.onReadDraftDataSucceeded((ReadDraftDataAction) action, data,
+ draft.message, draft.conversation);
+ }
+ }
+
+ @Override
+ public void onActionFailed(final ActionMonitor monitor,
+ final Action action, final Object data, final Object result) {
+ Assert.fail("Reading draft should not fail");
+ }
+ }
+
+ private ReadDraftDataAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ReadDraftDataAction> CREATOR
+ = new Parcelable.Creator<ReadDraftDataAction>() {
+ @Override
+ public ReadDraftDataAction createFromParcel(final Parcel in) {
+ return new ReadDraftDataAction(in);
+ }
+
+ @Override
+ public ReadDraftDataAction[] newArray(final int size) {
+ return new ReadDraftDataAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java b/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java
new file mode 100644
index 0000000..6794b17
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ReceiveMmsMessageAction.java
@@ -0,0 +1,197 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DataModelException;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.pdu.PduHeaders;
+import com.android.messaging.sms.DatabaseMessages;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.LogUtil;
+
+import java.util.List;
+
+/**
+ * Action used to "receive" an incoming message
+ */
+public class ReceiveMmsMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ private static final String KEY_SUB_ID = "sub_id";
+ private static final String KEY_PUSH_DATA = "push_data";
+ private static final String KEY_TRANSACTION_ID = "transaction_id";
+ private static final String KEY_CONTENT_LOCATION = "content_location";
+
+ /**
+ * Create a message received from a particular number in a particular conversation
+ */
+ public ReceiveMmsMessageAction(final int subId, final byte[] pushData) {
+ actionParameters.putInt(KEY_SUB_ID, subId);
+ actionParameters.putByteArray(KEY_PUSH_DATA, pushData);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final Context context = Factory.get().getApplicationContext();
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ final byte[] pushData = actionParameters.getByteArray(KEY_PUSH_DATA);
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // Write received message to telephony DB
+ MessageData message = null;
+ final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId);
+
+ final long received = System.currentTimeMillis();
+ // Inform sync that message has been added at local received timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(received);
+
+ // TODO: Should use local time to set received time in MMS message
+ final DatabaseMessages.MmsMessage mms = MmsUtils.processReceivedPdu(
+ context, pushData, self.getSubId(), self.getNormalizedDestination());
+
+ if (mms != null) {
+ final List<String> recipients = MmsUtils.getRecipientsByThread(mms.mThreadId);
+ String from = MmsUtils.getMmsSender(recipients, mms.getUri());
+ if (from == null) {
+ LogUtil.w(TAG, "Received an MMS without sender address; using unknown sender.");
+ from = ParticipantData.getUnknownSenderDestination();
+ }
+ final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
+ from, subId);
+ final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
+ db, rawSender.getNormalizedDestination());
+ final boolean autoDownload = (!blocked && MmsUtils.allowMmsAutoRetrieve(subId));
+ final String conversationId =
+ BugleDatabaseOperations.getOrCreateConversationFromThreadId(db, mms.mThreadId,
+ blocked, subId);
+
+ final boolean messageInFocusedConversation =
+ DataModel.get().isFocusedConversation(conversationId);
+ final boolean messageInObservableConversation =
+ DataModel.get().isNewMessageObservable(conversationId);
+
+ // TODO: Also write these values to the telephony provider
+ mms.mRead = messageInFocusedConversation;
+ mms.mSeen = messageInObservableConversation || blocked;
+
+ // Write received placeholder message to our DB
+ db.beginTransaction();
+ try {
+ final String participantId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+
+ message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId,
+ (autoDownload ? MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD :
+ MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD));
+ // Write the message
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+
+ if (!autoDownload) {
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
+ conversationId, message.getMessageId(), message.getReceivedTimeStamp(),
+ blocked, true /* shouldAutoSwitchSelfId */);
+ final ParticipantData sender = ParticipantData .getFromId(
+ db, participantId);
+ BugleActionToasts.onMessageReceived(conversationId, sender, message);
+ }
+ // else update the conversation once we have downloaded final message (or failed)
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ // Update conversation if not immediately initiating a download
+ if (!autoDownload) {
+ MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
+ MessagingContentProvider.notifyPartsChanged();
+
+ // Show a notification to let the user know a new message has arrived
+ BugleNotifications.update(false/*silent*/, conversationId,
+ BugleNotifications.UPDATE_ALL);
+
+ // Send the NotifyRespInd with DEFERRED status since no auto download
+ actionParameters.putString(KEY_TRANSACTION_ID, mms.mTransactionId);
+ actionParameters.putString(KEY_CONTENT_LOCATION, mms.mContentLocation);
+ requestBackgroundWork();
+ }
+
+ LogUtil.i(TAG, "ReceiveMmsMessageAction: Received MMS message " + message.getMessageId()
+ + " in conversation " + message.getConversationId()
+ + ", uri = " + message.getSmsMessageUri());
+ } else {
+ LogUtil.e(TAG, "ReceiveMmsMessageAction: Skipping processing of incoming PDU");
+ }
+
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
+
+ return message;
+ }
+
+ @Override
+ protected Bundle doBackgroundWork() throws DataModelException {
+ final Context context = Factory.get().getApplicationContext();
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ final String transactionId = actionParameters.getString(KEY_TRANSACTION_ID);
+ final String contentLocation = actionParameters.getString(KEY_CONTENT_LOCATION);
+ MmsUtils.sendNotifyResponseForMmsDownload(
+ context,
+ subId,
+ MmsUtils.stringToBytes(transactionId, "UTF-8"),
+ contentLocation,
+ PduHeaders.STATUS_DEFERRED);
+ // We don't need to return anything.
+ return null;
+ }
+
+ private ReceiveMmsMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ReceiveMmsMessageAction> CREATOR
+ = new Parcelable.Creator<ReceiveMmsMessageAction>() {
+ @Override
+ public ReceiveMmsMessageAction createFromParcel(final Parcel in) {
+ return new ReceiveMmsMessageAction(in);
+ }
+
+ @Override
+ public ReceiveMmsMessageAction[] newArray(final int size) {
+ return new ReceiveMmsMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java b/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java
new file mode 100644
index 0000000..5ffb35d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ReceiveSmsMessageAction.java
@@ -0,0 +1,198 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.Sms;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Action used to "receive" an incoming message
+ */
+public class ReceiveSmsMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ private static final String KEY_MESSAGE_VALUES = "message_values";
+
+ /**
+ * Create a message received from a particular number in a particular conversation
+ */
+ public ReceiveSmsMessageAction(final ContentValues messageValues) {
+ actionParameters.putParcelable(KEY_MESSAGE_VALUES, messageValues);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final Context context = Factory.get().getApplicationContext();
+ final ContentValues messageValues = actionParameters.getParcelable(KEY_MESSAGE_VALUES);
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // Get the SIM subscription ID
+ Integer subId = messageValues.getAsInteger(Sms.SUBSCRIPTION_ID);
+ if (subId == null) {
+ subId = ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+ // Make sure we have a sender address
+ String address = messageValues.getAsString(Sms.ADDRESS);
+ if (TextUtils.isEmpty(address)) {
+ LogUtil.w(TAG, "Received an SMS without an address; using unknown sender.");
+ address = ParticipantData.getUnknownSenderDestination();
+ messageValues.put(Sms.ADDRESS, address);
+ }
+ final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
+ address, subId);
+
+ // TODO: Should use local timestamp for this?
+ final long received = messageValues.getAsLong(Sms.DATE);
+ // Inform sync that message has been added at local received timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(received);
+
+ // Make sure we've got a thread id
+ final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(context, address);
+ messageValues.put(Sms.THREAD_ID, threadId);
+ final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
+ db, rawSender.getNormalizedDestination());
+ final String conversationId = BugleDatabaseOperations.
+ getOrCreateConversationFromRecipient(db, threadId, blocked, rawSender);
+
+ final boolean messageInFocusedConversation =
+ DataModel.get().isFocusedConversation(conversationId);
+ final boolean messageInObservableConversation =
+ DataModel.get().isNewMessageObservable(conversationId);
+
+ MessageData message = null;
+ // Only the primary user gets to insert the message into the telephony db and into bugle's
+ // db. The secondary user goes through this path, but skips doing the actual insert. It
+ // goes through this path because it needs to compute messageInFocusedConversation in order
+ // to calculate whether to skip the notification and play a soft sound if the user is
+ // already in the conversation.
+ if (!OsUtil.isSecondaryUser()) {
+ final boolean read = messageValues.getAsBoolean(Sms.Inbox.READ)
+ || messageInFocusedConversation;
+ // If you have read it you have seen it
+ final boolean seen = read || messageInObservableConversation || blocked;
+ messageValues.put(Sms.Inbox.READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+
+ // incoming messages are marked as seen in the telephony db
+ messageValues.put(Sms.Inbox.SEEN, 1);
+
+ // Insert into telephony
+ final Uri messageUri = context.getContentResolver().insert(Sms.Inbox.CONTENT_URI,
+ messageValues);
+
+ if (messageUri != null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ReceiveSmsMessageAction: Inserted SMS message into telephony, "
+ + "uri = " + messageUri);
+ }
+ } else {
+ LogUtil.e(TAG, "ReceiveSmsMessageAction: Failed to insert SMS into telephony!");
+ }
+
+ final String text = messageValues.getAsString(Sms.BODY);
+ final String subject = messageValues.getAsString(Sms.SUBJECT);
+ final long sent = messageValues.getAsLong(Sms.DATE_SENT);
+ final ParticipantData self = ParticipantData.getSelfParticipant(subId);
+ final Integer pathPresent = messageValues.getAsInteger(Sms.REPLY_PATH_PRESENT);
+ final String smsServiceCenter = messageValues.getAsString(Sms.SERVICE_CENTER);
+ String conversationServiceCenter = null;
+ // Only set service center if message REPLY_PATH_PRESENT = 1
+ if (pathPresent != null && pathPresent == 1 && !TextUtils.isEmpty(smsServiceCenter)) {
+ conversationServiceCenter = smsServiceCenter;
+ }
+ db.beginTransaction();
+ try {
+ final String participantId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+
+ message = MessageData.createReceivedSmsMessage(messageUri, conversationId,
+ participantId, selfId, text, subject, sent, received, seen, read);
+
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db, conversationId,
+ message.getMessageId(), message.getReceivedTimeStamp(), blocked,
+ conversationServiceCenter, true /* shouldAutoSwitchSelfId */);
+
+ final ParticipantData sender = ParticipantData.getFromId(db, participantId);
+ BugleActionToasts.onMessageReceived(conversationId, sender, message);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ LogUtil.i(TAG, "ReceiveSmsMessageAction: Received SMS message " + message.getMessageId()
+ + " in conversation " + message.getConversationId()
+ + ", uri = " + messageUri);
+
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ReceiveSmsMessageAction: Not inserting received SMS message for "
+ + "secondary user.");
+ }
+ }
+ // Show a notification to let the user know a new message has arrived
+ BugleNotifications.update(false/*silent*/, conversationId, BugleNotifications.UPDATE_ALL);
+
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyPartsChanged();
+
+ return message;
+ }
+
+ private ReceiveSmsMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ReceiveSmsMessageAction> CREATOR
+ = new Parcelable.Creator<ReceiveSmsMessageAction>() {
+ @Override
+ public ReceiveSmsMessageAction createFromParcel(final Parcel in) {
+ return new ReceiveSmsMessageAction(in);
+ }
+
+ @Override
+ public ReceiveSmsMessageAction[] newArray(final int size) {
+ return new ReceiveSmsMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java b/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java
new file mode 100644
index 0000000..e899b0c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/RedownloadMmsAction.java
@@ -0,0 +1,128 @@
+/*
+ * 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.datamodel.action;
+
+import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Action to manually start an MMS download (after failed or manual mms download)
+ */
+public class RedownloadMmsAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final int REQUEST_CODE_PENDING_INTENT = 102;
+
+ /**
+ * Download an MMS message
+ */
+ public static void redownloadMessage(final String messageId) {
+ final RedownloadMmsAction action = new RedownloadMmsAction(messageId);
+ action.start();
+ }
+
+ /**
+ * Get a pending intent of for downloading an MMS
+ */
+ public static PendingIntent getPendingIntentForRedownloadMms(
+ final Context context, final String messageId) {
+ final Action action = new RedownloadMmsAction(messageId);
+ return ActionService.makeStartActionPendingIntent(context,
+ action, REQUEST_CODE_PENDING_INTENT, false /*launchesAnActivity*/);
+ }
+
+ // Core parameters needed for all types of message
+ private static final String KEY_MESSAGE_ID = "message_id";
+
+ /**
+ * Constructor used for retrying sending in the background (only message id available)
+ */
+ RedownloadMmsAction(final String messageId) {
+ super();
+ actionParameters.putString(KEY_MESSAGE_ID, messageId);
+ }
+
+ /**
+ * Read message from database and change status to allow downloading
+ */
+ @Override
+ protected Object executeAction() {
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
+ // Check message can be redownloaded
+ if (message != null && message.canRedownloadMessage()) {
+ final long timestamp = System.currentTimeMillis();
+
+ final ContentValues values = new ContentValues(2);
+ values.put(DatabaseHelper.MessageColumns.STATUS,
+ MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD);
+ values.put(DatabaseHelper.MessageColumns.RETRY_START_TIMESTAMP, timestamp);
+
+ // Row must exist as was just loaded above (on ActionService thread)
+ BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values);
+
+ MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
+
+ // Whether we succeeded or failed we will check and maybe schedule some more work
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
+ } else {
+ message = null;
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "Attempt to download a missing or un-redownloadable message");
+ }
+ // Immediately update the notifications in case we came from the download action from a
+ // heads-up notification. This will dismiss the heads-up notification.
+ BugleNotifications.update(false/*silent*/, BugleNotifications.UPDATE_ALL);
+ return message;
+ }
+
+ private RedownloadMmsAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<RedownloadMmsAction> CREATOR
+ = new Parcelable.Creator<RedownloadMmsAction>() {
+ @Override
+ public RedownloadMmsAction createFromParcel(final Parcel in) {
+ return new RedownloadMmsAction(in);
+ }
+
+ @Override
+ public RedownloadMmsAction[] newArray(final int size) {
+ return new RedownloadMmsAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/ResendMessageAction.java b/src/com/android/messaging/datamodel/action/ResendMessageAction.java
new file mode 100644
index 0000000..2201965
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/ResendMessageAction.java
@@ -0,0 +1,128 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Action used to manually resend an outgoing message
+ */
+public class ResendMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ /**
+ * Manual send of existing message (no listener)
+ */
+ public static void resendMessage(final String messageId) {
+ final ResendMessageAction action = new ResendMessageAction(messageId);
+ action.start();
+ }
+
+ // Core parameters needed for all types of message
+ private static final String KEY_MESSAGE_ID = "message_id";
+
+ /**
+ * Constructor used for retrying sending in the background (only message id available)
+ */
+ ResendMessageAction(final String messageId) {
+ super();
+ actionParameters.putString(KEY_MESSAGE_ID, messageId);
+ }
+
+ /**
+ * Read message from database and change status to allow sending
+ */
+ @Override
+ protected Object executeAction() {
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
+ // Check message can be resent
+ if (message != null && message.canResendMessage()) {
+ final boolean isMms = message.getIsMms();
+ long timestamp = System.currentTimeMillis();
+ if (isMms) {
+ // MMS expects timestamp rounded to nearest second
+ timestamp = 1000 * ((timestamp + 500) / 1000);
+ }
+
+ LogUtil.i(TAG, "ResendMessageAction: Resending message " + messageId
+ + "; changed timestamp from " + message.getReceivedTimeStamp() + " to "
+ + timestamp);
+
+ final ContentValues values = new ContentValues();
+ values.put(MessageColumns.STATUS, MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND);
+ values.put(MessageColumns.RECEIVED_TIMESTAMP, timestamp);
+ values.put(MessageColumns.SENT_TIMESTAMP, timestamp);
+ values.put(MessageColumns.RETRY_START_TIMESTAMP, timestamp);
+
+ // Row must exist as was just loaded above (on ActionService thread)
+ BugleDatabaseOperations.updateMessageRow(db, message.getMessageId(), values);
+
+ MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
+
+ // Whether we succeeded or failed we will check and maybe schedule some more work
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
+
+ return message;
+ } else {
+ String error = "ResendMessageAction: Cannot resend message " + messageId + "; ";
+ if (message != null) {
+ error += ("status = " + MessageData.getStatusDescription(message.getStatus()));
+ } else {
+ error += "not found in database";
+ }
+ LogUtil.e(TAG, error);
+ }
+
+ return null;
+ }
+
+ private ResendMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<ResendMessageAction> CREATOR
+ = new Parcelable.Creator<ResendMessageAction>() {
+ @Override
+ public ResendMessageAction createFromParcel(final Parcel in) {
+ return new ResendMessageAction(in);
+ }
+
+ @Override
+ public ResendMessageAction[] newArray(final int size) {
+ return new ResendMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/SendMessageAction.java b/src/com/android/messaging/datamodel/action/SendMessageAction.java
new file mode 100644
index 0000000..d7ebe8f
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/SendMessageAction.java
@@ -0,0 +1,447 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Action used to send an outgoing message. It writes MMS messages to the telephony db
+ * ({@link InsertNewMessageAction}) writes SMS messages to the telephony db). It also
+ * initiates the actual sending. It will all be used for re-sending a failed message.
+ * NOTE: This action must queue a ProcessPendingMessagesAction when it is done (success or failure).
+ * <p>
+ * This class is public (not package-private) because the SMS/MMS (e.g. MmsUtils) classes need to
+ * access the EXTRA_* fields for setting up the 'sent' pending intent.
+ */
+public class SendMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ /**
+ * Queue sending of existing message (can only be called during execute of action)
+ */
+ static boolean queueForSendInBackground(final String messageId,
+ final Action processingAction) {
+ final SendMessageAction action = new SendMessageAction();
+ return action.queueAction(messageId, processingAction);
+ }
+
+ public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;
+ public static final int MAX_SMS_RETRY = 3;
+
+ // Core parameters needed for all types of message
+ private static final String KEY_MESSAGE_ID = "message_id";
+ private static final String KEY_MESSAGE = "message";
+ private static final String KEY_MESSAGE_URI = "message_uri";
+ private static final String KEY_SUB_PHONE_NUMBER = "sub_phone_number";
+
+ // For sms messages a few extra values are included in the bundle
+ private static final String KEY_RECIPIENT = "recipient";
+ private static final String KEY_RECIPIENTS = "recipients";
+ private static final String KEY_SMS_SERVICE_CENTER = "sms_service_center";
+
+ // Values we attach to the pending intent that's fired when the message is sent.
+ // Only applicable when sending via the platform APIs on L+.
+ public static final String KEY_SUB_ID = "sub_id";
+ public static final String EXTRA_MESSAGE_ID = "message_id";
+ public static final String EXTRA_UPDATED_MESSAGE_URI = "updated_message_uri";
+ public static final String EXTRA_CONTENT_URI = "content_uri";
+ public static final String EXTRA_RESPONSE_IMPORTANT = "response_important";
+
+ /**
+ * Constructor used for retrying sending in the background (only message id available)
+ */
+ private SendMessageAction() {
+ super();
+ }
+
+ /**
+ * Read message from database and queue actual sending
+ */
+ private boolean queueAction(final String messageId, final Action processingAction) {
+ actionParameters.putString(KEY_MESSAGE_ID, messageId);
+
+ final long timestamp = System.currentTimeMillis();
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
+ // Check message can be resent
+ if (message != null && message.canSendMessage()) {
+ final boolean isSms = (message.getProtocol() == MessageData.PROTOCOL_SMS);
+
+ final ParticipantData self = BugleDatabaseOperations.getExistingParticipant(
+ db, message.getSelfId());
+ final Uri messageUri = message.getSmsMessageUri();
+ final String conversationId = message.getConversationId();
+
+ // Update message status
+ if (message.getYetToSend()) {
+ // Initial sending of message
+ message.markMessageSending(timestamp);
+ } else {
+ // Automatic resend of message
+ message.markMessageResending(timestamp);
+ }
+ if (!updateMessageAndStatus(isSms, message, null /* messageUri */, false /*notify*/)) {
+ // If message is missing in the telephony database we don't need to send it
+ return false;
+ }
+
+ final ArrayList<String> recipients =
+ BugleDatabaseOperations.getRecipientsForConversation(db, conversationId);
+
+ // Update action state with parameters needed for background sending
+ actionParameters.putParcelable(KEY_MESSAGE_URI, messageUri);
+ actionParameters.putParcelable(KEY_MESSAGE, message);
+ actionParameters.putStringArrayList(KEY_RECIPIENTS, recipients);
+ actionParameters.putInt(KEY_SUB_ID, self.getSubId());
+ actionParameters.putString(KEY_SUB_PHONE_NUMBER, self.getNormalizedDestination());
+
+ if (isSms) {
+ final String smsc = BugleDatabaseOperations.getSmsServiceCenterForConversation(
+ db, conversationId);
+ actionParameters.putString(KEY_SMS_SERVICE_CENTER, smsc);
+
+ if (recipients.size() == 1) {
+ final String recipient = recipients.get(0);
+
+ actionParameters.putString(KEY_RECIPIENT, recipient);
+ // Queue actual sending for SMS
+ processingAction.requestBackgroundWork(this);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SendMessageAction: Queued SMS message " + messageId
+ + " for sending");
+ }
+ return true;
+ } else {
+ LogUtil.wtf(TAG, "Trying to resend a broadcast SMS - not allowed");
+ }
+ } else {
+ // Queue actual sending for MMS
+ processingAction.requestBackgroundWork(this);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SendMessageAction: Queued MMS message " + messageId
+ + " for sending");
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Never called
+ */
+ @Override
+ protected Object executeAction() {
+ Assert.fail("SendMessageAction must be queued rather than started");
+ return null;
+ }
+
+ /**
+ * Send message on background worker thread
+ */
+ @Override
+ protected Bundle doBackgroundWork() {
+ final MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
+ Uri updatedMessageUri = null;
+ final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS;
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
+
+ LogUtil.i(TAG, "SendMessageAction: Sending " + (isSms ? "SMS" : "MMS") + " message "
+ + messageId + " in conversation " + message.getConversationId());
+
+ int status;
+ int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
+ int resultCode = MessageData.UNKNOWN_RESULT_CODE;
+ if (isSms) {
+ Assert.notNull(messageUri);
+ final String recipient = actionParameters.getString(KEY_RECIPIENT);
+ final String messageText = message.getMessageText();
+ final String smsServiceCenter = actionParameters.getString(KEY_SMS_SERVICE_CENTER);
+ final boolean deliveryReportRequired = MmsUtils.isDeliveryReportRequired(subId);
+
+ status = MmsUtils.sendSmsMessage(recipient, messageText, messageUri, subId,
+ smsServiceCenter, deliveryReportRequired);
+ } else {
+ final Context context = Factory.get().getApplicationContext();
+ final ArrayList<String> recipients =
+ actionParameters.getStringArrayList(KEY_RECIPIENTS);
+ if (messageUri == null) {
+ final long timestamp = message.getReceivedTimeStamp();
+
+ // Inform sync that message has been added at local received timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(timestamp);
+
+ // For MMS messages first need to write to telephony (resizing images if needed)
+ updatedMessageUri = MmsUtils.insertSendingMmsMessage(context, recipients,
+ message, subId, subPhoneNumber, timestamp);
+ if (updatedMessageUri != null) {
+ messageUri = updatedMessageUri;
+ // To prevent Sync seeing inconsistent state must write to DB on this thread
+ updateMessageUri(messageId, updatedMessageUri);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SendMessageAction: Updated message " + messageId
+ + " with new uri " + messageUri);
+ }
+ }
+ }
+ if (messageUri != null) {
+ // Actually send the MMS
+ final Bundle extras = new Bundle();
+ extras.putString(EXTRA_MESSAGE_ID, messageId);
+ extras.putParcelable(EXTRA_UPDATED_MESSAGE_URI, updatedMessageUri);
+ final MmsUtils.StatusPlusUri result = MmsUtils.sendMmsMessage(context, subId,
+ messageUri, extras);
+ if (result == MmsUtils.STATUS_PENDING) {
+ // Async send, so no status yet
+ LogUtil.d(TAG, "SendMessageAction: Sending MMS message " + messageId
+ + " asynchronously; waiting for callback to finish processing");
+ return null;
+ }
+ status = result.status;
+ rawStatus = result.rawStatus;
+ resultCode = result.resultCode;
+ } else {
+ status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ }
+ }
+
+ // When we fast-fail before calling the MMS lib APIs (e.g. airplane mode,
+ // sending message is deleted).
+ ProcessSentMessageAction.processMessageSentFastFailed(messageId, messageUri,
+ updatedMessageUri, subId, isSms, status, rawStatus, resultCode);
+ return null;
+ }
+
+ private void updateMessageUri(final String messageId, final Uri updatedMessageUri) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(MessageColumns.SMS_MESSAGE_URI, updatedMessageUri.toString());
+ BugleDatabaseOperations.updateMessageRow(db, messageId, values);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ // Nothing to do here, post-send tasks handled by ProcessSentMessageAction
+ return null;
+ }
+
+ /**
+ * Update message status to reflect success or failure
+ */
+ @Override
+ protected Object processBackgroundFailure() {
+ final String messageId = actionParameters.getString(KEY_MESSAGE_ID);
+ final MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
+ final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS;
+ final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ final int resultCode = actionParameters.getInt(ProcessSentMessageAction.KEY_RESULT_CODE);
+ final int httpStatusCode =
+ actionParameters.getInt(ProcessSentMessageAction.KEY_HTTP_STATUS_CODE);
+
+ ProcessSentMessageAction.processResult(messageId, null /* updatedMessageUri */,
+ MmsUtils.MMS_REQUEST_MANUAL_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
+ isSms, this, subId, resultCode, httpStatusCode);
+
+ // Whether we succeeded or failed we will check and maybe schedule some more work
+ ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(true, this);
+
+ return null;
+ }
+
+ /**
+ * Update the message status (and message itself if necessary)
+ * @param isSms whether this is an SMS or MMS
+ * @param message message to update
+ * @param updatedMessageUri message uri for newly-inserted messages; null otherwise
+ * @param clearSeen whether the message 'seen' status should be reset if error occurs
+ */
+ public static boolean updateMessageAndStatus(final boolean isSms, final MessageData message,
+ final Uri updatedMessageUri, final boolean clearSeen) {
+ final Context context = Factory.get().getApplicationContext();
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ // TODO: We're optimistically setting the type/box of outgoing messages to
+ // 'SENT' even before they actually are. We should technically be using QUEUED or OUTBOX
+ // instead, but if we do that, it's possible that the Messaging app will try to send them
+ // as part of its clean-up logic that runs when it starts (http://b/18155366).
+ //
+ // We also use the wrong status when inserting queued SMS messages in
+ // InsertNewMessageAction.insertBroadcastSmsMessage and insertSendingSmsMessage (should be
+ // QUEUED or OUTBOX), and in MmsUtils.insertSendReq (should be OUTBOX).
+
+ boolean updatedTelephony = true;
+ int messageBox;
+ int type;
+ switch(message.getStatus()) {
+ case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
+ case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED:
+ type = Sms.MESSAGE_TYPE_SENT;
+ messageBox = Mms.MESSAGE_BOX_SENT;
+ break;
+ case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
+ case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
+ type = Sms.MESSAGE_TYPE_SENT;
+ messageBox = Mms.MESSAGE_BOX_SENT;
+ break;
+ case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
+ case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
+ type = Sms.MESSAGE_TYPE_SENT;
+ messageBox = Mms.MESSAGE_BOX_SENT;
+ break;
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
+ type = Sms.MESSAGE_TYPE_FAILED;
+ messageBox = Mms.MESSAGE_BOX_FAILED;
+ break;
+ default:
+ type = Sms.MESSAGE_TYPE_ALL;
+ messageBox = Mms.MESSAGE_BOX_ALL;
+ break;
+ }
+ // First in the telephony DB
+ if (isSms) {
+ // Ignore update message Uri
+ if (type != Sms.MESSAGE_TYPE_ALL) {
+ if (!MmsUtils.updateSmsMessageSendingStatus(context, message.getSmsMessageUri(),
+ type, message.getReceivedTimeStamp())) {
+ message.markMessageFailed(message.getSentTimeStamp());
+ updatedTelephony = false;
+ }
+ }
+ } else if (message.getSmsMessageUri() != null) {
+ if (messageBox != Mms.MESSAGE_BOX_ALL) {
+ if (!MmsUtils.updateMmsMessageSendingStatus(context, message.getSmsMessageUri(),
+ messageBox, message.getReceivedTimeStamp())) {
+ message.markMessageFailed(message.getSentTimeStamp());
+ updatedTelephony = false;
+ }
+ }
+ }
+ if (updatedTelephony) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SendMessageAction: Updated " + (isSms ? "SMS" : "MMS")
+ + " message " + message.getMessageId()
+ + " in telephony (" + message.getSmsMessageUri() + ")");
+ }
+ } else {
+ LogUtil.w(TAG, "SendMessageAction: Failed to update " + (isSms ? "SMS" : "MMS")
+ + " message " + message.getMessageId()
+ + " in telephony (" + message.getSmsMessageUri() + "); marking message failed");
+ }
+
+ // Update the local DB
+ db.beginTransaction();
+ try {
+ if (updatedMessageUri != null) {
+ // Update all message and part fields
+ BugleDatabaseOperations.updateMessageInTransaction(db, message);
+ BugleDatabaseOperations.refreshConversationMetadataInTransaction(
+ db, message.getConversationId(), false/* shouldAutoSwitchSelfId */,
+ false/*archived*/);
+ } else {
+ final ContentValues values = new ContentValues();
+ values.put(MessageColumns.STATUS, message.getStatus());
+
+ if (clearSeen) {
+ // When a message fails to send, the message needs to
+ // be unseen to be selected as an error notification.
+ values.put(MessageColumns.SEEN, 0);
+ }
+ values.put(MessageColumns.RECEIVED_TIMESTAMP, message.getReceivedTimeStamp());
+ values.put(MessageColumns.RAW_TELEPHONY_STATUS, message.getRawTelephonyStatus());
+
+ BugleDatabaseOperations.updateMessageRowIfExists(db, message.getMessageId(),
+ values);
+ }
+ db.setTransactionSuccessful();
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SendMessageAction: Updated " + (isSms ? "SMS" : "MMS")
+ + " message " + message.getMessageId() + " in local db. Timestamp = "
+ + message.getReceivedTimeStamp());
+ }
+ } finally {
+ db.endTransaction();
+ }
+
+ MessagingContentProvider.notifyMessagesChanged(message.getConversationId());
+ if (updatedMessageUri != null) {
+ MessagingContentProvider.notifyPartsChanged();
+ }
+
+ return updatedTelephony;
+ }
+
+ private SendMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<SendMessageAction> CREATOR
+ = new Parcelable.Creator<SendMessageAction>() {
+ @Override
+ public SendMessageAction createFromParcel(final Parcel in) {
+ return new SendMessageAction(in);
+ }
+
+ @Override
+ public SendMessageAction[] newArray(final int size) {
+ return new SendMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/SyncCursorPair.java b/src/com/android/messaging/datamodel/action/SyncCursorPair.java
new file mode 100644
index 0000000..b3a2676
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/SyncCursorPair.java
@@ -0,0 +1,712 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+import android.support.v4.util.LongSparseArray;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.sms.DatabaseMessages;
+import com.android.messaging.sms.DatabaseMessages.DatabaseMessage;
+import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
+import com.android.messaging.sms.DatabaseMessages.MmsMessage;
+import com.android.messaging.sms.DatabaseMessages.SmsMessage;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.google.common.collect.Sets;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Class holding a pair of cursors - one for local db and one for telephony provider - allowing
+ * synchronous stepping through messages as part of sync.
+ */
+class SyncCursorPair {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ static final long SYNC_COMPLETE = -1L;
+ static final long SYNC_STARTING = Long.MAX_VALUE;
+
+ private CursorIterator mLocalCursorIterator;
+ private CursorIterator mRemoteCursorsIterator;
+
+ private final String mLocalSelection;
+ private final String mRemoteSmsSelection;
+ private final String mRemoteMmsSelection;
+
+ /**
+ * Check if SMS has been synchronized. We compare the counts of messages on both
+ * sides and return true if they are equal.
+ *
+ * Note that this may not be the most reliable way to tell if messages are in sync.
+ * For example, the local misses one message and has one obsolete message.
+ * However, we have background sms sync once a while, also some other events might
+ * trigger a full sync. So we will eventually catch up. And this should be rare to
+ * happen.
+ *
+ * @return If sms is in sync with telephony sms/mms providers
+ */
+ static boolean allSynchronized(final DatabaseWrapper db) {
+ return isSynchronized(db, LOCAL_MESSAGES_SELECTION, null,
+ getSmsTypeSelectionSql(), null, getMmsTypeSelectionSql(), null);
+ }
+
+ SyncCursorPair(final long lowerBound, final long upperBound) {
+ mLocalSelection = getTimeConstrainedQuery(
+ LOCAL_MESSAGES_SELECTION,
+ MessageColumns.RECEIVED_TIMESTAMP,
+ lowerBound,
+ upperBound,
+ null /* threadColumn */, null /* threadId */);
+ mRemoteSmsSelection = getTimeConstrainedQuery(
+ getSmsTypeSelectionSql(),
+ "date",
+ lowerBound,
+ upperBound,
+ null /* threadColumn */, null /* threadId */);
+ mRemoteMmsSelection = getTimeConstrainedQuery(
+ getMmsTypeSelectionSql(),
+ "date",
+ ((lowerBound < 0) ? lowerBound : (lowerBound + 999) / 1000), /*seconds*/
+ ((upperBound < 0) ? upperBound : (upperBound + 999) / 1000), /*seconds*/
+ null /* threadColumn */, null /* threadId */);
+ }
+
+ SyncCursorPair(final long threadId, final String conversationId) {
+ mLocalSelection = getTimeConstrainedQuery(
+ LOCAL_MESSAGES_SELECTION,
+ MessageColumns.RECEIVED_TIMESTAMP,
+ -1L,
+ -1L,
+ MessageColumns.CONVERSATION_ID, conversationId);
+ // Find all SMS messages (excluding drafts) within the sync window
+ mRemoteSmsSelection = getTimeConstrainedQuery(
+ getSmsTypeSelectionSql(),
+ "date",
+ -1L,
+ -1L,
+ Sms.THREAD_ID, Long.toString(threadId));
+ mRemoteMmsSelection = getTimeConstrainedQuery(
+ getMmsTypeSelectionSql(),
+ "date",
+ -1L, /*seconds*/
+ -1L, /*seconds*/
+ Mms.THREAD_ID, Long.toString(threadId));
+ }
+
+ void query(final DatabaseWrapper db) {
+ // Load local messages in the sync window
+ mLocalCursorIterator = new LocalCursorIterator(db, mLocalSelection);
+ // Load remote messages in the sync window
+ mRemoteCursorsIterator = new RemoteCursorsIterator(mRemoteSmsSelection,
+ mRemoteMmsSelection);
+ }
+
+ boolean isSynchronized(final DatabaseWrapper db) {
+ return isSynchronized(db, mLocalSelection, null, mRemoteSmsSelection,
+ null, mRemoteMmsSelection, null);
+ }
+
+ void close() {
+ if (mLocalCursorIterator != null) {
+ mLocalCursorIterator.close();
+ }
+ if (mRemoteCursorsIterator != null) {
+ mRemoteCursorsIterator.close();
+ }
+ }
+
+ long scan(final int maxMessagesToScan,
+ final int maxMessagesToUpdate, final ArrayList<SmsMessage> smsToAdd,
+ final LongSparseArray<MmsMessage> mmsToAdd,
+ final ArrayList<LocalDatabaseMessage> messagesToDelete,
+ final SyncManager.ThreadInfoCache threadInfoCache) {
+ // Set of local messages matched with the timestamp of a remote message
+ final Set<DatabaseMessage> matchedLocalMessages = Sets.newHashSet();
+ // Set of remote messages matched with the timestamp of a local message
+ final Set<DatabaseMessage> matchedRemoteMessages = Sets.newHashSet();
+ long lastTimestampMillis = SYNC_STARTING;
+ // Number of messages scanned local and remote
+ int localCount = 0;
+ int remoteCount = 0;
+ // Seed the initial values of remote and local messages for comparison
+ DatabaseMessage remoteMessage = mRemoteCursorsIterator.next();
+ DatabaseMessage localMessage = mLocalCursorIterator.next();
+ // Iterate through messages on both sides in reverse time order
+ // Import messages in remote not in local, delete messages in local not in remote
+ while (localCount + remoteCount < maxMessagesToScan && smsToAdd.size()
+ + mmsToAdd.size() + messagesToDelete.size() < maxMessagesToUpdate) {
+ if (remoteMessage == null && localMessage == null) {
+ // No more message on both sides - scan complete
+ lastTimestampMillis = SYNC_COMPLETE;
+ break;
+ } else if ((remoteMessage == null && localMessage != null) ||
+ (localMessage != null && remoteMessage != null &&
+ localMessage.getTimestampInMillis()
+ > remoteMessage.getTimestampInMillis())) {
+ // Found a local message that is not in remote db
+ // Delete the local message
+ messagesToDelete.add((LocalDatabaseMessage) localMessage);
+ lastTimestampMillis = Math.min(lastTimestampMillis,
+ localMessage.getTimestampInMillis());
+ // Advance to next local message
+ localMessage = mLocalCursorIterator.next();
+ localCount += 1;
+ } else if ((localMessage == null && remoteMessage != null) ||
+ (localMessage != null && remoteMessage != null &&
+ localMessage.getTimestampInMillis()
+ < remoteMessage.getTimestampInMillis())) {
+ // Found a remote message that is not in local db
+ // Add the remote message
+ saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache);
+ lastTimestampMillis = Math.min(lastTimestampMillis,
+ remoteMessage.getTimestampInMillis());
+ // Advance to next remote message
+ remoteMessage = mRemoteCursorsIterator.next();
+ remoteCount += 1;
+ } else {
+ // Found remote and local messages at the same timestamp
+ final long matchedTimestamp = localMessage.getTimestampInMillis();
+ lastTimestampMillis = Math.min(lastTimestampMillis, matchedTimestamp);
+ // Get the next local and remote messages
+ final DatabaseMessage remoteMessagePeek = mRemoteCursorsIterator.next();
+ final DatabaseMessage localMessagePeek = mLocalCursorIterator.next();
+ // Check if only one message on each side matches the current timestamp
+ // by looking at the next messages on both sides. If they are either null
+ // (meaning no more messages) or having a different timestamp. We want
+ // to optimize for this since this is the most common case when majority
+ // of the messages are in sync (so they one-to-one pair up at each timestamp),
+ // by not allocating the data structures required to compare a set of
+ // messages from both sides.
+ if ((remoteMessagePeek == null ||
+ remoteMessagePeek.getTimestampInMillis() != matchedTimestamp) &&
+ (localMessagePeek == null ||
+ localMessagePeek.getTimestampInMillis() != matchedTimestamp)) {
+ // Optimize the common case where only one message on each side
+ // that matches the same timestamp
+ if (!remoteMessage.equals(localMessage)) {
+ // local != remote
+ // Delete local message
+ messagesToDelete.add((LocalDatabaseMessage) localMessage);
+ // Add remote message
+ saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache);
+ }
+ // Get next local and remote messages
+ localMessage = localMessagePeek;
+ remoteMessage = remoteMessagePeek;
+ localCount += 1;
+ remoteCount += 1;
+ } else {
+ // Rare case in which multiple messages are in the same timestamp
+ // on either or both sides
+ // Gather all the matched remote messages
+ matchedRemoteMessages.clear();
+ matchedRemoteMessages.add(remoteMessage);
+ remoteCount += 1;
+ remoteMessage = remoteMessagePeek;
+ while (remoteMessage != null &&
+ remoteMessage.getTimestampInMillis() == matchedTimestamp) {
+ Assert.isTrue(!matchedRemoteMessages.contains(remoteMessage));
+ matchedRemoteMessages.add(remoteMessage);
+ remoteCount += 1;
+ remoteMessage = mRemoteCursorsIterator.next();
+ }
+ // Gather all the matched local messages
+ matchedLocalMessages.clear();
+ matchedLocalMessages.add(localMessage);
+ localCount += 1;
+ localMessage = localMessagePeek;
+ while (localMessage != null &&
+ localMessage.getTimestampInMillis() == matchedTimestamp) {
+ if (matchedLocalMessages.contains(localMessage)) {
+ // Duplicate message is local database is deleted
+ messagesToDelete.add((LocalDatabaseMessage) localMessage);
+ } else {
+ matchedLocalMessages.add(localMessage);
+ }
+ localCount += 1;
+ localMessage = mLocalCursorIterator.next();
+ }
+ // Delete messages local only
+ for (final DatabaseMessage msg : Sets.difference(
+ matchedLocalMessages, matchedRemoteMessages)) {
+ messagesToDelete.add((LocalDatabaseMessage) msg);
+ }
+ // Add messages remote only
+ for (final DatabaseMessage msg : Sets.difference(
+ matchedRemoteMessages, matchedLocalMessages)) {
+ saveMessageToAdd(smsToAdd, mmsToAdd, msg, threadInfoCache);
+ }
+ }
+ }
+ }
+ return lastTimestampMillis;
+ }
+
+ DatabaseMessage getLocalMessage() {
+ return mLocalCursorIterator.next();
+ }
+
+ DatabaseMessage getRemoteMessage() {
+ return mRemoteCursorsIterator.next();
+ }
+
+ int getLocalPosition() {
+ return mLocalCursorIterator.getPosition();
+ }
+
+ int getRemotePosition() {
+ return mRemoteCursorsIterator.getPosition();
+ }
+
+ int getLocalCount() {
+ return mLocalCursorIterator.getCount();
+ }
+
+ int getRemoteCount() {
+ return mRemoteCursorsIterator.getCount();
+ }
+
+ /**
+ * An iterator for a database cursor
+ */
+ interface CursorIterator {
+ /**
+ * Move to next element in the cursor
+ *
+ * @return The next element (which becomes the current)
+ */
+ public DatabaseMessage next();
+ /**
+ * Close the cursor
+ */
+ public void close();
+ /**
+ * Get the position
+ */
+ public int getPosition();
+ /**
+ * Get the count
+ */
+ public int getCount();
+ }
+
+ private static final String ORDER_BY_DATE_DESC = "date DESC";
+
+ // A subquery that selects SMS/MMS messages in Bugle which are also in telephony
+ private static final String LOCAL_MESSAGES_SELECTION = String.format(
+ Locale.US,
+ "(%s NOTNULL)",
+ MessageColumns.SMS_MESSAGE_URI);
+
+ private static final String ORDER_BY_TIMESTAMP_DESC =
+ MessageColumns.RECEIVED_TIMESTAMP + " DESC";
+
+ // TODO : This should move into the provider
+ private static class LocalMessageQuery {
+ private static final String[] PROJECTION = new String[] {
+ MessageColumns._ID,
+ MessageColumns.RECEIVED_TIMESTAMP,
+ MessageColumns.SMS_MESSAGE_URI,
+ MessageColumns.PROTOCOL,
+ MessageColumns.CONVERSATION_ID,
+ };
+ private static final int INDEX_MESSAGE_ID = 0;
+ private static final int INDEX_MESSAGE_TIMESTAMP = 1;
+ private static final int INDEX_SMS_MESSAGE_URI = 2;
+ private static final int INDEX_MESSAGE_SMS_TYPE = 3;
+ private static final int INDEX_CONVERSATION_ID = 4;
+ }
+
+ /**
+ * This class provides the same DatabaseMessage interface over a local SMS db message
+ */
+ private static LocalDatabaseMessage getLocalDatabaseMessage(final Cursor cursor) {
+ if (cursor == null) {
+ return null;
+ }
+ return new LocalDatabaseMessage(
+ cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_ID),
+ cursor.getInt(LocalMessageQuery.INDEX_MESSAGE_SMS_TYPE),
+ cursor.getString(LocalMessageQuery.INDEX_SMS_MESSAGE_URI),
+ cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_TIMESTAMP),
+ cursor.getString(LocalMessageQuery.INDEX_CONVERSATION_ID));
+ }
+
+ /**
+ * The buffered cursor iterator for local SMS
+ */
+ private static class LocalCursorIterator implements CursorIterator {
+ private Cursor mCursor;
+ private final DatabaseWrapper mDatabase;
+
+ LocalCursorIterator(final DatabaseWrapper database, final String selection)
+ throws SQLiteException {
+ mDatabase = database;
+ try {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncCursorPair: Querying for local messages; selection = "
+ + selection);
+ }
+ mCursor = mDatabase.query(
+ DatabaseHelper.MESSAGES_TABLE,
+ LocalMessageQuery.PROJECTION,
+ selection,
+ null /*selectionArgs*/,
+ null/*groupBy*/,
+ null/*having*/,
+ ORDER_BY_TIMESTAMP_DESC);
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "SyncCursorPair: failed to query local sms/mms", e);
+ // Can't query local database. So let's throw up the exception and abort sync
+ // because we may end up import duplicate messages.
+ throw e;
+ }
+ }
+
+ @Override
+ public DatabaseMessage next() {
+ if (mCursor != null && mCursor.moveToNext()) {
+ return getLocalDatabaseMessage(mCursor);
+ }
+ return null;
+ }
+
+ @Override
+ public int getCount() {
+ return (mCursor == null ? 0 : mCursor.getCount());
+ }
+
+ @Override
+ public int getPosition() {
+ return (mCursor == null ? 0 : mCursor.getPosition());
+ }
+
+ @Override
+ public void close() {
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ }
+ }
+
+ /**
+ * The cursor iterator for remote sms.
+ * Since SMS and MMS are stored in different tables in telephony provider,
+ * this class merges the two cursors and provides a unified view of messages
+ * from both cursors. Note that the order is DESC.
+ */
+ private static class RemoteCursorsIterator implements CursorIterator {
+ private Cursor mSmsCursor;
+ private Cursor mMmsCursor;
+ private DatabaseMessage mNextSms;
+ private DatabaseMessage mNextMms;
+
+ RemoteCursorsIterator(final String smsSelection, final String mmsSelection)
+ throws SQLiteException {
+ mSmsCursor = null;
+ mMmsCursor = null;
+ try {
+ final Context context = Factory.get().getApplicationContext();
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncCursorPair: Querying for remote SMS; selection = "
+ + smsSelection);
+ }
+ mSmsCursor = SqliteWrapper.query(
+ context,
+ context.getContentResolver(),
+ Sms.CONTENT_URI,
+ SmsMessage.getProjection(),
+ smsSelection,
+ null /* selectionArgs */,
+ ORDER_BY_DATE_DESC);
+ if (mSmsCursor == null) {
+ LogUtil.w(TAG, "SyncCursorPair: Remote SMS query returned null cursor; "
+ + "need to cancel sync");
+ throw new RuntimeException("Null cursor from remote SMS query");
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncCursorPair: Querying for remote MMS; selection = "
+ + mmsSelection);
+ }
+ mMmsCursor = SqliteWrapper.query(
+ context,
+ context.getContentResolver(),
+ Mms.CONTENT_URI,
+ DatabaseMessages.MmsMessage.getProjection(),
+ mmsSelection,
+ null /* selectionArgs */,
+ ORDER_BY_DATE_DESC);
+ if (mMmsCursor == null) {
+ LogUtil.w(TAG, "SyncCursorPair: Remote MMS query returned null cursor; "
+ + "need to cancel sync");
+ throw new RuntimeException("Null cursor from remote MMS query");
+ }
+ // Move to the first element in the combined stream from both cursors
+ mNextSms = getSmsCursorNext();
+ mNextMms = getMmsCursorNext();
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "SyncCursorPair: failed to query remote messages", e);
+ // If we ignore this, the following code would think there is no remote message
+ // and will delete all the local sms. We should be cautious here. So instead,
+ // let's throw the exception to the caller and abort sms sync. We do the same
+ // thing if either of the remote cursors is null.
+ throw e;
+ }
+ }
+
+ @Override
+ public DatabaseMessage next() {
+ DatabaseMessage result = null;
+ if (mNextSms != null && mNextMms != null) {
+ if (mNextSms.getTimestampInMillis() >= mNextMms.getTimestampInMillis()) {
+ result = mNextSms;
+ mNextSms = getSmsCursorNext();
+ } else {
+ result = mNextMms;
+ mNextMms = getMmsCursorNext();
+ }
+ } else {
+ if (mNextSms != null) {
+ result = mNextSms;
+ mNextSms = getSmsCursorNext();
+ } else {
+ result = mNextMms;
+ mNextMms = getMmsCursorNext();
+ }
+ }
+ return result;
+ }
+
+ private DatabaseMessage getSmsCursorNext() {
+ if (mSmsCursor != null && mSmsCursor.moveToNext()) {
+ return SmsMessage.get(mSmsCursor);
+ }
+ return null;
+ }
+
+ private DatabaseMessage getMmsCursorNext() {
+ if (mMmsCursor != null && mMmsCursor.moveToNext()) {
+ return MmsMessage.get(mMmsCursor);
+ }
+ return null;
+ }
+
+ @Override
+ // Return approximate cursor position allowing for read ahead on two cursors (hence -1)
+ public int getPosition() {
+ return (mSmsCursor == null ? 0 : mSmsCursor.getPosition()) +
+ (mMmsCursor == null ? 0 : mMmsCursor.getPosition()) - 1;
+ }
+
+ @Override
+ public int getCount() {
+ return (mSmsCursor == null ? 0 : mSmsCursor.getCount()) +
+ (mMmsCursor == null ? 0 : mMmsCursor.getCount());
+ }
+
+ @Override
+ public void close() {
+ if (mSmsCursor != null) {
+ mSmsCursor.close();
+ mSmsCursor = null;
+ }
+ if (mMmsCursor != null) {
+ mMmsCursor.close();
+ mMmsCursor = null;
+ }
+ }
+ }
+
+ /**
+ * Type selection for importing sms messages. Only SENT and INBOX messages are imported.
+ *
+ * @return The SQL selection for importing sms messages
+ */
+ public static String getSmsTypeSelectionSql() {
+ return MmsUtils.getSmsTypeSelectionSql();
+ }
+
+ /**
+ * Type selection for importing mms messages.
+ *
+ * Criteria:
+ * MESSAGE_BOX is INBOX, SENT or OUTBOX
+ * MESSAGE_TYPE is SEND_REQ (sent), RETRIEVE_CONF (received) or NOTIFICATION_IND (download)
+ *
+ * @return The SQL selection for importing mms messages. This selects the message type,
+ * not including the selection on timestamp.
+ */
+ public static String getMmsTypeSelectionSql() {
+ return MmsUtils.getMmsTypeSelectionSql();
+ }
+
+ /**
+ * Get a SQL selection string using an existing selection and time window limits
+ * The limits are not applied if the value is < 0
+ *
+ * @param typeSelection The existing selection
+ * @param from The inclusive lower bound
+ * @param to The exclusive upper bound
+ * @return The created SQL selection
+ */
+ private static String getTimeConstrainedQuery(final String typeSelection,
+ final String timeColumn, final long from, final long to,
+ final String threadColumn, final String threadId) {
+ final StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append(typeSelection);
+ if (from > 0) {
+ queryBuilder.append(" AND ").append(timeColumn).append(">=").append(from);
+ }
+ if (to > 0) {
+ queryBuilder.append(" AND ").append(timeColumn).append("<").append(to);
+ }
+ if (!TextUtils.isEmpty(threadColumn) && !TextUtils.isEmpty(threadId)) {
+ queryBuilder.append(" AND ").append(threadColumn).append("=").append(threadId);
+ }
+ return queryBuilder.toString();
+ }
+
+ private static final String[] COUNT_PROJECTION = new String[] { "count()" };
+
+ private static int getCountFromCursor(final Cursor cursor) {
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getInt(0);
+ }
+ // We should only return a number if we were able to read it from the cursor.
+ // Otherwise, we throw an exception to cancel the sync.
+ String cursorDesc = "";
+ if (cursor == null) {
+ cursorDesc = "null";
+ } else if (cursor.getCount() == 0) {
+ cursorDesc = "empty";
+ }
+ throw new IllegalArgumentException("Cannot get count from " + cursorDesc + " cursor");
+ }
+
+ private void saveMessageToAdd(final List<SmsMessage> smsToAdd,
+ final LongSparseArray<MmsMessage> mmsToAdd, final DatabaseMessage message,
+ final ThreadInfoCache threadInfoCache) {
+ long threadId;
+ if (message.getProtocol() == MessageData.PROTOCOL_MMS) {
+ final MmsMessage mms = (MmsMessage) message;
+ mmsToAdd.append(mms.getId(), mms);
+ threadId = mms.mThreadId;
+ } else {
+ final SmsMessage sms = (SmsMessage) message;
+ smsToAdd.add(sms);
+ threadId = sms.mThreadId;
+ }
+ // Cache the lookup and canonicalization of the phone number outside of the transaction...
+ threadInfoCache.getThreadRecipients(threadId);
+ }
+
+ /**
+ * Check if SMS has been synchronized. We compare the counts of messages on both
+ * sides and return true if they are equal.
+ *
+ * Note that this may not be the most reliable way to tell if messages are in sync.
+ * For example, the local misses one message and has one obsolete message.
+ * However, we have background sms sync once a while, also some other events might
+ * trigger a full sync. So we will eventually catch up. And this should be rare to
+ * happen.
+ *
+ * @return If sms is in sync with telephony sms/mms providers
+ */
+ private static boolean isSynchronized(final DatabaseWrapper db, final String localSelection,
+ final String[] localSelectionArgs, final String smsSelection,
+ final String[] smsSelectionArgs, final String mmsSelection,
+ final String[] mmsSelectionArgs) {
+ final Context context = Factory.get().getApplicationContext();
+ Cursor localCursor = null;
+ Cursor remoteSmsCursor = null;
+ Cursor remoteMmsCursor = null;
+ try {
+ localCursor = db.query(
+ DatabaseHelper.MESSAGES_TABLE,
+ COUNT_PROJECTION,
+ localSelection,
+ localSelectionArgs,
+ null/*groupBy*/,
+ null/*having*/,
+ null/*orderBy*/);
+ final int localCount = getCountFromCursor(localCursor);
+ remoteSmsCursor = SqliteWrapper.query(
+ context,
+ context.getContentResolver(),
+ Sms.CONTENT_URI,
+ COUNT_PROJECTION,
+ smsSelection,
+ smsSelectionArgs,
+ null/*orderBy*/);
+ final int smsCount = getCountFromCursor(remoteSmsCursor);
+ remoteMmsCursor = SqliteWrapper.query(
+ context,
+ context.getContentResolver(),
+ Mms.CONTENT_URI,
+ COUNT_PROJECTION,
+ mmsSelection,
+ mmsSelectionArgs,
+ null/*orderBy*/);
+ final int mmsCount = getCountFromCursor(remoteMmsCursor);
+ final int remoteCount = smsCount + mmsCount;
+ final boolean isInSync = (localCount == remoteCount);
+ if (isInSync) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncCursorPair: Same # of local and remote messages = "
+ + localCount);
+ }
+ } else {
+ LogUtil.i(TAG, "SyncCursorPair: Not in sync; # local messages = " + localCount
+ + ", # remote message = " + remoteCount);
+ }
+ return isInSync;
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "SyncCursorPair: failed to query local or remote message counts", e);
+ // If something is wrong in querying database, assume we are synced so
+ // we don't retry indefinitely
+ } finally {
+ if (localCursor != null) {
+ localCursor.close();
+ }
+ if (remoteSmsCursor != null) {
+ remoteSmsCursor.close();
+ }
+ if (remoteMmsCursor != null) {
+ remoteMmsCursor.close();
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/SyncMessageBatch.java b/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
new file mode 100644
index 0000000..972d691
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/SyncMessageBatch.java
@@ -0,0 +1,383 @@
+/*
+ * 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.datamodel.action;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.pdu.PduHeaders;
+import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
+import com.android.messaging.sms.DatabaseMessages.MmsMessage;
+import com.android.messaging.sms.DatabaseMessages.SmsMessage;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Update local database with a batch of messages to add/delete in one transaction
+ */
+class SyncMessageBatch {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ // Variables used during executeAction
+ private final HashSet<String> mConversationsToUpdate;
+ // Cache of thread->conversationId map
+ private final ThreadInfoCache mCache;
+
+ // Set of SMS messages to add
+ private final ArrayList<SmsMessage> mSmsToAdd;
+ // Set of MMS messages to add
+ private final ArrayList<MmsMessage> mMmsToAdd;
+ // Set of local messages to delete
+ private final ArrayList<LocalDatabaseMessage> mMessagesToDelete;
+
+ SyncMessageBatch(final ArrayList<SmsMessage> smsToAdd,
+ final ArrayList<MmsMessage> mmsToAdd,
+ final ArrayList<LocalDatabaseMessage> messagesToDelete,
+ final ThreadInfoCache cache) {
+ mSmsToAdd = smsToAdd;
+ mMmsToAdd = mmsToAdd;
+ mMessagesToDelete = messagesToDelete;
+ mCache = cache;
+ mConversationsToUpdate = new HashSet<String>();
+ }
+
+ void updateLocalDatabase() {
+ // Perform local database changes in one transaction
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ // Store all the SMS messages
+ for (final SmsMessage sms : mSmsToAdd) {
+ storeSms(db, sms);
+ }
+ // Store all the MMS messages
+ for (final MmsMessage mms : mMmsToAdd) {
+ storeMms(db, mms);
+ }
+ // Keep track of conversations with messages deleted
+ for (final LocalDatabaseMessage message : mMessagesToDelete) {
+ mConversationsToUpdate.add(message.getConversationId());
+ }
+ // Batch delete local messages
+ batchDelete(db, DatabaseHelper.MESSAGES_TABLE, MessageColumns._ID,
+ messageListToIds(mMessagesToDelete));
+
+ for (final LocalDatabaseMessage message : mMessagesToDelete) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncMessageBatch: Deleted message " + message.getLocalId()
+ + " for SMS/MMS " + message.getUri() + " with timestamp "
+ + message.getTimestampInMillis());
+ }
+ }
+
+ // Update conversation state for imported messages, like snippet,
+ updateConversations(db);
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private static String[] messageListToIds(final List<LocalDatabaseMessage> messagesToDelete) {
+ final String[] ids = new String[messagesToDelete.size()];
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = Long.toString(messagesToDelete.get(i).getLocalId());
+ }
+ return ids;
+ }
+
+ /**
+ * Store the SMS message into local database.
+ *
+ * @param sms
+ */
+ private void storeSms(final DatabaseWrapper db, final SmsMessage sms) {
+ if (sms.mBody == null) {
+ LogUtil.w(TAG, "SyncMessageBatch: SMS " + sms.mUri + " has no body; adding empty one");
+ // try to fix it
+ sms.mBody = "";
+ }
+
+ if (TextUtils.isEmpty(sms.mAddress)) {
+ LogUtil.e(TAG, "SyncMessageBatch: SMS has no address; using unknown sender");
+ // try to fix it
+ sms.mAddress = ParticipantData.getUnknownSenderDestination();
+ }
+
+ // TODO : We need to also deal with messages in a failed/retry state
+ final boolean isOutgoing = sms.mType != Sms.MESSAGE_TYPE_INBOX;
+
+ final String otherPhoneNumber = sms.mAddress;
+
+ // A forced resync of all messages should still keep the archived states.
+ // The database upgrade code notifies sync manager of this. We need to
+ // honor the original customization to this conversation if created.
+ final String conversationId = mCache.getOrCreateConversation(db, sms.mThreadId, sms.mSubId,
+ DataModel.get().getSyncManager().getCustomizationForThread(sms.mThreadId));
+ if (conversationId == null) {
+ // Cannot create conversation for this message? This should not happen.
+ LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for SMS thread "
+ + sms.mThreadId);
+ return;
+ }
+ final ParticipantData self = ParticipantData.getSelfParticipant(sms.getSubId());
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+ final ParticipantData sender = isOutgoing ?
+ self :
+ ParticipantData.getFromRawPhoneBySimLocale(otherPhoneNumber, sms.getSubId());
+ final String participantId = (isOutgoing ? selfId :
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender));
+
+ final int bugleStatus = bugleStatusForSms(isOutgoing, sms.mType, sms.mStatus);
+
+ final MessageData message = MessageData.createSmsMessage(
+ sms.mUri,
+ participantId,
+ selfId,
+ conversationId,
+ bugleStatus,
+ sms.mSeen,
+ sms.mRead,
+ sms.mTimestampSentInMillis,
+ sms.mTimestampInMillis,
+ sms.mBody);
+
+ // Inserting sms content into messages table
+ try {
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+ } catch (SQLiteConstraintException e) {
+ rethrowSQLiteConstraintExceptionWithDetails(e, db, sms.mUri, sms.mThreadId,
+ conversationId, selfId, participantId);
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId()
+ + " for SMS " + message.getSmsMessageUri() + " received at "
+ + message.getReceivedTimeStamp());
+ }
+
+ // Keep track of updated conversation for later updating the conversation snippet, etc.
+ mConversationsToUpdate.add(conversationId);
+ }
+
+ public static int bugleStatusForSms(final boolean isOutgoing, final int type,
+ final int status) {
+ int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN;
+ // For a message we sync either
+ if (isOutgoing) {
+ // Outgoing message not yet been sent
+ if (type == Telephony.Sms.MESSAGE_TYPE_FAILED ||
+ type == Telephony.Sms.MESSAGE_TYPE_OUTBOX ||
+ type == Telephony.Sms.MESSAGE_TYPE_QUEUED ||
+ (type == Telephony.Sms.MESSAGE_TYPE_SENT &&
+ status == Telephony.Sms.STATUS_FAILED)) {
+ // Not sent counts as failed and available for manual resend
+ bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED;
+ } else if (status == Sms.STATUS_COMPLETE) {
+ bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_DELIVERED;
+ } else {
+ // Otherwise outgoing message is complete
+ bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
+ }
+ } else {
+ // All incoming SMS messages are complete
+ bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE;
+ }
+ return bugleStatus;
+ }
+
+ /**
+ * Store the MMS message into local database
+ *
+ * @param mms
+ */
+ private void storeMms(final DatabaseWrapper db, final MmsMessage mms) {
+ if (mms.mParts.size() < 1) {
+ LogUtil.w(TAG, "SyncMessageBatch: MMS " + mms.mUri + " has no parts");
+ }
+
+ // TODO : We need to also deal with messages in a failed/retry state
+ final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX;
+ final boolean isNotification = (mms.mMmsMessageType ==
+ PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+
+ final String senderId = mms.mSender;
+
+ // A forced resync of all messages should still keep the archived states.
+ // The database upgrade code notifies sync manager of this. We need to
+ // honor the original customization to this conversation if created.
+ final String conversationId = mCache.getOrCreateConversation(db, mms.mThreadId, mms.mSubId,
+ DataModel.get().getSyncManager().getCustomizationForThread(mms.mThreadId));
+ if (conversationId == null) {
+ LogUtil.e(TAG, "SyncMessageBatch: Failed to create conversation for MMS thread "
+ + mms.mThreadId);
+ return;
+ }
+ final ParticipantData self = ParticipantData.getSelfParticipant(mms.getSubId());
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+ final ParticipantData sender = isOutgoing ?
+ self : ParticipantData.getFromRawPhoneBySimLocale(senderId, mms.getSubId());
+ final String participantId = (isOutgoing ? selfId :
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, sender));
+
+ final int bugleStatus = MmsUtils.bugleStatusForMms(isOutgoing, isNotification, mms.mType);
+
+ // Import message and all of the parts.
+ // TODO : For now we are importing these in the order we found them in the MMS
+ // database. Ideally we would load and parse the SMIL which describes how the parts relate
+ // to one another.
+
+ // TODO: Need to set correct status on message
+ final MessageData message = MmsUtils.createMmsMessage(mms, conversationId, participantId,
+ selfId, bugleStatus);
+
+ // Inserting mms content into messages table
+ try {
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
+ } catch (SQLiteConstraintException e) {
+ rethrowSQLiteConstraintExceptionWithDetails(e, db, mms.mUri, mms.mThreadId,
+ conversationId, selfId, participantId);
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SyncMessageBatch: Inserted new message " + message.getMessageId()
+ + " for MMS " + message.getSmsMessageUri() + " received at "
+ + message.getReceivedTimeStamp());
+ }
+
+ // Keep track of updated conversation for later updating the conversation snippet, etc.
+ mConversationsToUpdate.add(conversationId);
+ }
+
+ // TODO: Remove this after we no longer see this crash (b/18375758)
+ private static void rethrowSQLiteConstraintExceptionWithDetails(SQLiteConstraintException e,
+ DatabaseWrapper db, String messageUri, long threadId, String conversationId,
+ String selfId, String senderId) {
+ // Add some extra debug information to the exception for tracking down b/18375758.
+ // The default detail message for SQLiteConstraintException tells us that a foreign
+ // key constraint failed, but not which one! Messages have foreign keys to 3 tables:
+ // conversations, participants (self), participants (sender). We'll query each one
+ // to determine which one(s) violated the constraint, and then throw a new exception
+ // with those details.
+
+ String foundConversationId = null;
+ Cursor cursor = null;
+ try {
+ // Look for an existing conversation in the db with the conversation id
+ cursor = db.rawQuery("SELECT " + ConversationColumns._ID
+ + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
+ + " WHERE " + ConversationColumns._ID + "=" + conversationId,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ Assert.isTrue(cursor.getCount() == 1);
+ foundConversationId = cursor.getString(0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ ParticipantData foundSelfParticipant =
+ BugleDatabaseOperations.getExistingParticipant(db, selfId);
+ ParticipantData foundSenderParticipant =
+ BugleDatabaseOperations.getExistingParticipant(db, senderId);
+
+ String errorMsg = "SQLiteConstraintException while inserting message for " + messageUri
+ + "; conversation id from getOrCreateConversation = " + conversationId
+ + " (lookup thread = " + threadId + "), found conversation id = "
+ + foundConversationId + ", found self participant = "
+ + LogUtil.sanitizePII(foundSelfParticipant.getNormalizedDestination())
+ + " (lookup id = " + selfId + "), found sender participant = "
+ + LogUtil.sanitizePII(foundSenderParticipant.getNormalizedDestination())
+ + " (lookup id = " + senderId + ")";
+ throw new RuntimeException(errorMsg, e);
+ }
+
+ /**
+ * Use the tracked latest message info to update conversations, including
+ * latest chat message and sort timestamp.
+ */
+ private void updateConversations(final DatabaseWrapper db) {
+ for (final String conversationId : mConversationsToUpdate) {
+ if (BugleDatabaseOperations.deleteConversationIfEmptyInTransaction(db,
+ conversationId)) {
+ continue;
+ }
+
+ final boolean archived = mCache.isArchived(conversationId);
+ // Always attempt to auto-switch conversation self id for sync/import case.
+ BugleDatabaseOperations.maybeRefreshConversationMetadataInTransaction(db,
+ conversationId, true /*shouldAutoSwitchSelfId*/, archived /*keepArchived*/);
+ }
+ }
+
+
+ /**
+ * Batch delete database rows by matching a column with a list of values, usually some
+ * kind of IDs.
+ *
+ * @param table
+ * @param column
+ * @param ids
+ * @return Total number of deleted messages
+ */
+ private static int batchDelete(final DatabaseWrapper db, final String table,
+ final String column, final String[] ids) {
+ int totalDeleted = 0;
+ final int totalIds = ids.length;
+ for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) {
+ final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding
+ final int count = end - start;
+ final String batchSelection = String.format(
+ Locale.US,
+ "%s IN %s",
+ column,
+ MmsUtils.getSqlInOperand(count));
+ final String[] batchSelectionArgs = Arrays.copyOfRange(ids, start, end);
+ final int deleted = db.delete(
+ table,
+ batchSelection,
+ batchSelectionArgs);
+ totalDeleted += deleted;
+ }
+ return totalDeleted;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/SyncMessagesAction.java b/src/com/android/messaging/datamodel/action/SyncMessagesAction.java
new file mode 100644
index 0000000..f4a3e1f
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/SyncMessagesAction.java
@@ -0,0 +1,637 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.provider.Telephony.Mms;
+import android.support.v4.util.LongSparseArray;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.sms.DatabaseMessages;
+import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
+import com.android.messaging.sms.DatabaseMessages.MmsMessage;
+import com.android.messaging.sms.DatabaseMessages.SmsMessage;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Action used to sync messages from smsmms db to local database
+ */
+public class SyncMessagesAction extends Action implements Parcelable {
+ static final long SYNC_FAILED = Long.MIN_VALUE;
+
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ private static final String KEY_START_TIMESTAMP = "start_timestamp";
+ private static final String KEY_MAX_UPDATE = "max_update";
+ private static final String KEY_LOWER_BOUND = "lower_bound";
+ private static final String KEY_UPPER_BOUND = "upper_bound";
+ private static final String BUNDLE_KEY_LAST_TIMESTAMP = "last_timestamp";
+ private static final String BUNDLE_KEY_SMS_MESSAGES = "sms_to_add";
+ private static final String BUNDLE_KEY_MMS_MESSAGES = "mms_to_add";
+ private static final String BUNDLE_KEY_MESSAGES_TO_DELETE = "messages_to_delete";
+
+ /**
+ * Start a full sync (backed off a few seconds to avoid pulling sending/receiving messages).
+ */
+ public static void fullSync() {
+ final BugleGservices bugleGservices = BugleGservices.get();
+ final long smsSyncBackoffTimeMillis = bugleGservices.getLong(
+ BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS,
+ BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
+
+ final long now = System.currentTimeMillis();
+ // TODO: Could base this off most recent message in db but now should be okay...
+ final long startTimestamp = now - smsSyncBackoffTimeMillis;
+
+ final SyncMessagesAction action = new SyncMessagesAction(-1L, startTimestamp,
+ 0, startTimestamp);
+ action.start();
+ }
+
+ /**
+ * Start an incremental sync to pull messages since last sync (backed off a few seconds)..
+ */
+ public static void sync() {
+ final BugleGservices bugleGservices = BugleGservices.get();
+ final long smsSyncBackoffTimeMillis = bugleGservices.getLong(
+ BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS,
+ BugleGservicesKeys.SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
+
+ final long now = System.currentTimeMillis();
+ // TODO: Could base this off most recent message in db but now should be okay...
+ final long startTimestamp = now - smsSyncBackoffTimeMillis;
+
+ sync(startTimestamp);
+ }
+
+ /**
+ * Start an incremental sync when the application starts up (no back off as not yet
+ * sending/receiving).
+ */
+ public static void immediateSync() {
+ final long now = System.currentTimeMillis();
+ // TODO: Could base this off most recent message in db but now should be okay...
+ final long startTimestamp = now;
+
+ sync(startTimestamp);
+ }
+
+ private static void sync(final long startTimestamp) {
+ if (!OsUtil.hasSmsPermission()) {
+ // Sync requires READ_SMS permission
+ return;
+ }
+
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ // Lower bound is end of previous sync
+ final long syncLowerBoundTimeMillis = prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME,
+ BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT);
+
+ final SyncMessagesAction action = new SyncMessagesAction(syncLowerBoundTimeMillis,
+ startTimestamp, 0, startTimestamp);
+ action.start();
+ }
+
+ private SyncMessagesAction(final long lowerBound, final long upperBound,
+ final int maxMessagesToUpdate, final long startTimestamp) {
+ actionParameters.putLong(KEY_LOWER_BOUND, lowerBound);
+ actionParameters.putLong(KEY_UPPER_BOUND, upperBound);
+ actionParameters.putInt(KEY_MAX_UPDATE, maxMessagesToUpdate);
+ actionParameters.putLong(KEY_START_TIMESTAMP, startTimestamp);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND);
+ final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND);
+ final int initialMaxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE);
+ final long startTimestamp = actionParameters.getLong(KEY_START_TIMESTAMP);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Request to sync messages from "
+ + lowerBoundTimeMillis + " to " + upperBoundTimeMillis + " (start timestamp = "
+ + startTimestamp + ", message update limit = " + initialMaxMessagesToUpdate
+ + ")");
+ }
+
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ if (lowerBoundTimeMillis >= 0) {
+ // Cursors
+ final SyncCursorPair cursors = new SyncCursorPair(-1L, lowerBoundTimeMillis);
+ final boolean inSync = cursors.isSynchronized(db);
+ if (!inSync) {
+ if (syncManager.delayUntilFullSync(startTimestamp) == 0) {
+ lowerBoundTimeMillis = -1;
+ actionParameters.putLong(KEY_LOWER_BOUND, lowerBoundTimeMillis);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Messages before "
+ + lowerBoundTimeMillis + " not in sync; promoting to full sync");
+ }
+ } else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Messages before "
+ + lowerBoundTimeMillis + " not in sync; will do incremental sync");
+ }
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Messages before " + lowerBoundTimeMillis
+ + " are in sync");
+ }
+ }
+ }
+
+ // Check if sync allowed (can be too soon after last or one is already running)
+ if (syncManager.shouldSync(lowerBoundTimeMillis < 0, startTimestamp)) {
+ syncManager.startSyncBatch(upperBoundTimeMillis);
+ requestBackgroundWork();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected Bundle doBackgroundWork() {
+ final BugleGservices bugleGservices = BugleGservices.get();
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final int maxMessagesToScan = bugleGservices.getInt(
+ BugleGservicesKeys.SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN,
+ BugleGservicesKeys.SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN_DEFAULT);
+
+ final int initialMaxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE);
+ final int smsSyncSubsequentBatchSizeMin = bugleGservices.getInt(
+ BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MIN,
+ BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MIN_DEFAULT);
+ final int smsSyncSubsequentBatchSizeMax = bugleGservices.getInt(
+ BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MAX,
+ BugleGservicesKeys.SMS_SYNC_BATCH_SIZE_MAX_DEFAULT);
+
+ // Cap sync size to GServices limits
+ final int maxMessagesToUpdate = Math.max(smsSyncSubsequentBatchSizeMin,
+ Math.min(initialMaxMessagesToUpdate, smsSyncSubsequentBatchSizeMax));
+
+ final long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND);
+ final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND);
+
+ LogUtil.i(TAG, "SyncMessagesAction: Starting batch for messages from "
+ + lowerBoundTimeMillis + " to " + upperBoundTimeMillis
+ + " (message update limit = " + maxMessagesToUpdate + ", message scan limit = "
+ + maxMessagesToScan + ")");
+
+ // Clear last change time so that we can work out if this batch is dirty when it completes
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+
+ // Clear the singleton cache that maps threads to recipients and to conversations.
+ final SyncManager.ThreadInfoCache cache = syncManager.getThreadInfoCache();
+ cache.clear();
+
+ // Sms messages to store
+ final ArrayList<SmsMessage> smsToAdd = new ArrayList<SmsMessage>();
+ // Mms messages to store
+ final LongSparseArray<MmsMessage> mmsToAdd = new LongSparseArray<MmsMessage>();
+ // List of local SMS/MMS to remove
+ final ArrayList<LocalDatabaseMessage> messagesToDelete =
+ new ArrayList<LocalDatabaseMessage>();
+
+ long lastTimestampMillis = SYNC_FAILED;
+ if (syncManager.isSyncing(upperBoundTimeMillis)) {
+ // Cursors
+ final SyncCursorPair cursors = new SyncCursorPair(lowerBoundTimeMillis,
+ upperBoundTimeMillis);
+
+ // Actually compare the messages using cursor pair
+ lastTimestampMillis = syncCursorPair(db, cursors, smsToAdd, mmsToAdd,
+ messagesToDelete, maxMessagesToScan, maxMessagesToUpdate, cache);
+ }
+ final Bundle response = new Bundle();
+
+ // If comparison succeeds bundle up the changes for processing in ActionService
+ if (lastTimestampMillis > SYNC_FAILED) {
+ final ArrayList<MmsMessage> mmsToAddList = new ArrayList<MmsMessage>();
+ for (int i = 0; i < mmsToAdd.size(); i++) {
+ final MmsMessage mms = mmsToAdd.valueAt(i);
+ mmsToAddList.add(mms);
+ }
+
+ response.putParcelableArrayList(BUNDLE_KEY_SMS_MESSAGES, smsToAdd);
+ response.putParcelableArrayList(BUNDLE_KEY_MMS_MESSAGES, mmsToAddList);
+ response.putParcelableArrayList(BUNDLE_KEY_MESSAGES_TO_DELETE, messagesToDelete);
+ }
+ response.putLong(BUNDLE_KEY_LAST_TIMESTAMP, lastTimestampMillis);
+
+ return response;
+ }
+
+ /**
+ * Compare messages based on timestamp and uri
+ * @param db local database wrapper
+ * @param cursors cursor pair holding references to local and remote messages
+ * @param smsToAdd newly found sms messages to add
+ * @param mmsToAdd newly found mms messages to add
+ * @param messagesToDelete messages not found needing deletion
+ * @param maxMessagesToScan max messages to scan for changes
+ * @param maxMessagesToUpdate max messages to return for updates
+ * @param cache cache for conversation id / thread id / recipient set mapping
+ * @return timestamp of the oldest message seen during the sync scan
+ */
+ private long syncCursorPair(final DatabaseWrapper db, final SyncCursorPair cursors,
+ final ArrayList<SmsMessage> smsToAdd, final LongSparseArray<MmsMessage> mmsToAdd,
+ final ArrayList<LocalDatabaseMessage> messagesToDelete, final int maxMessagesToScan,
+ final int maxMessagesToUpdate, final ThreadInfoCache cache) {
+ long lastTimestampMillis;
+ final long startTimeMillis = SystemClock.elapsedRealtime();
+
+ // Number of messages scanned local and remote
+ int localPos = 0;
+ int remotePos = 0;
+ int localTotal = 0;
+ int remoteTotal = 0;
+ // Scan through the messages on both sides and prepare messages for local message table
+ // changes (including adding and deleting)
+ try {
+ cursors.query(db);
+
+ localTotal = cursors.getLocalCount();
+ remoteTotal = cursors.getRemoteCount();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Scanning cursors (local count = " + localTotal
+ + ", remote count = " + remoteTotal + ", message update limit = "
+ + maxMessagesToUpdate + ", message scan limit = " + maxMessagesToScan
+ + ")");
+ }
+
+ lastTimestampMillis = cursors.scan(maxMessagesToScan, maxMessagesToUpdate,
+ smsToAdd, mmsToAdd, messagesToDelete, cache);
+
+ localPos = cursors.getLocalPosition();
+ remotePos = cursors.getRemotePosition();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Scanned cursors (local position = " + localPos
+ + " of " + localTotal + ", remote position = " + remotePos + " of "
+ + remoteTotal + ")");
+ }
+
+ // Batch loading the parts of the MMS messages in this batch
+ loadMmsParts(mmsToAdd);
+ // Lookup senders for incoming mms messages
+ setMmsSenders(mmsToAdd, cache);
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "SyncMessagesAction: Database exception", e);
+ // Let's abort
+ lastTimestampMillis = SYNC_FAILED;
+ } catch (final Exception e) {
+ // We want to catch anything unexpected since this is running in a separate thread
+ // and any unexpected exception will just fail this thread silently.
+ // Let's crash for dogfooders!
+ LogUtil.wtf(TAG, "SyncMessagesAction: unexpected failure in scan", e);
+ lastTimestampMillis = SYNC_FAILED;
+ } finally {
+ if (cursors != null) {
+ cursors.close();
+ }
+ }
+
+ final long endTimeMillis = SystemClock.elapsedRealtime();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: Scan complete (took "
+ + (endTimeMillis - startTimeMillis) + " ms). " + smsToAdd.size()
+ + " remote SMS to add, " + mmsToAdd.size() + " MMS to add, "
+ + messagesToDelete.size() + " local messages to delete. "
+ + "Oldest timestamp seen = " + lastTimestampMillis);
+ }
+
+ return lastTimestampMillis;
+ }
+
+ /**
+ * Perform local database updates and schedule follow on sync actions
+ */
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ final long lastTimestampMillis = response.getLong(BUNDLE_KEY_LAST_TIMESTAMP);
+ final long lowerBoundTimeMillis = actionParameters.getLong(KEY_LOWER_BOUND);
+ final long upperBoundTimeMillis = actionParameters.getLong(KEY_UPPER_BOUND);
+ final int maxMessagesToUpdate = actionParameters.getInt(KEY_MAX_UPDATE);
+ final long startTimestamp = actionParameters.getLong(KEY_START_TIMESTAMP);
+
+ // Check with the sync manager if any conflicting updates have been made to databases
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ final boolean orphan = !syncManager.isSyncing(upperBoundTimeMillis);
+
+ // lastTimestampMillis used to indicate failure
+ if (orphan) {
+ // This batch does not match current in progress timestamp.
+ LogUtil.w(TAG, "SyncMessagesAction: Ignoring orphan sync batch for messages from "
+ + lowerBoundTimeMillis + " to " + upperBoundTimeMillis);
+ } else {
+ final boolean dirty = syncManager.isBatchDirty(lastTimestampMillis);
+ if (lastTimestampMillis == SYNC_FAILED) {
+ LogUtil.e(TAG, "SyncMessagesAction: Sync failed - terminating");
+
+ // Failed - update last sync times to throttle our failure rate
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ // Save sync completion time so next sync will start from here
+ prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, startTimestamp);
+ // Remember last full sync so that don't start background full sync right away
+ prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, startTimestamp);
+
+ syncManager.complete();
+ } else if (dirty) {
+ LogUtil.w(TAG, "SyncMessagesAction: Redoing dirty sync batch of messages from "
+ + lowerBoundTimeMillis + " to " + upperBoundTimeMillis);
+
+ // Redo this batch
+ final SyncMessagesAction nextBatch =
+ new SyncMessagesAction(lowerBoundTimeMillis, upperBoundTimeMillis,
+ maxMessagesToUpdate, startTimestamp);
+
+ syncManager.startSyncBatch(upperBoundTimeMillis);
+ requestBackgroundWork(nextBatch);
+ } else {
+ // Succeeded
+ final ArrayList<SmsMessage> smsToAdd =
+ response.getParcelableArrayList(BUNDLE_KEY_SMS_MESSAGES);
+ final ArrayList<MmsMessage> mmsToAdd =
+ response.getParcelableArrayList(BUNDLE_KEY_MMS_MESSAGES);
+ final ArrayList<LocalDatabaseMessage> messagesToDelete =
+ response.getParcelableArrayList(BUNDLE_KEY_MESSAGES_TO_DELETE);
+
+ final int messagesUpdated = smsToAdd.size() + mmsToAdd.size()
+ + messagesToDelete.size();
+
+ // Perform local database changes in one transaction
+ long txnTimeMillis = 0;
+ if (messagesUpdated > 0) {
+ final long startTimeMillis = SystemClock.elapsedRealtime();
+ final SyncMessageBatch batch = new SyncMessageBatch(smsToAdd, mmsToAdd,
+ messagesToDelete, syncManager.getThreadInfoCache());
+ batch.updateLocalDatabase();
+ final long endTimeMillis = SystemClock.elapsedRealtime();
+ txnTimeMillis = endTimeMillis - startTimeMillis;
+
+ LogUtil.i(TAG, "SyncMessagesAction: Updated local database "
+ + "(took " + txnTimeMillis + " ms). Added "
+ + smsToAdd.size() + " SMS, added " + mmsToAdd.size() + " MMS, deleted "
+ + messagesToDelete.size() + " messages.");
+
+ // TODO: Investigate whether we can make this more fine-grained.
+ MessagingContentProvider.notifyEverythingChanged();
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: No local database updates to make");
+ }
+
+ if (!syncManager.getHasFirstSyncCompleted()) {
+ // If we have never completed a sync before (fresh install) and there are
+ // no messages, still inform the UI of a change so it can update syncing
+ // messages shown to the user
+ MessagingContentProvider.notifyConversationListChanged();
+ MessagingContentProvider.notifyPartsChanged();
+ }
+ }
+ // Determine if there are more messages that need to be scanned
+ if (lastTimestampMillis >= 0 && lastTimestampMillis >= lowerBoundTimeMillis) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SyncMessagesAction: More messages to sync; scheduling next "
+ + "sync batch now.");
+ }
+
+ // Include final millisecond of last sync in next sync
+ final long newUpperBoundTimeMillis = lastTimestampMillis + 1;
+ final int newMaxMessagesToUpdate = nextBatchSize(messagesUpdated,
+ txnTimeMillis);
+
+ final SyncMessagesAction nextBatch =
+ new SyncMessagesAction(lowerBoundTimeMillis, newUpperBoundTimeMillis,
+ newMaxMessagesToUpdate, startTimestamp);
+
+ // Proceed with next batch
+ syncManager.startSyncBatch(newUpperBoundTimeMillis);
+ requestBackgroundWork(nextBatch);
+ } else {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ // Save sync completion time so next sync will start from here
+ prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, startTimestamp);
+ if (lowerBoundTimeMillis < 0) {
+ // Remember last full sync so that don't start another full sync right away
+ prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, startTimestamp);
+ }
+
+ final long now = System.currentTimeMillis();
+
+ // After any sync check if new messages have arrived
+ final SyncCursorPair recents = new SyncCursorPair(startTimestamp, now);
+ final SyncCursorPair olders = new SyncCursorPair(-1L, startTimestamp);
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ if (!recents.isSynchronized(db)) {
+ LogUtil.i(TAG, "SyncMessagesAction: Changed messages after sync; "
+ + "scheduling an incremental sync now.");
+
+ // Just add a new batch for recent messages
+ final SyncMessagesAction nextBatch =
+ new SyncMessagesAction(startTimestamp, now, 0, startTimestamp);
+ syncManager.startSyncBatch(now);
+ requestBackgroundWork(nextBatch);
+ // After partial sync verify sync state
+ } else if (lowerBoundTimeMillis >= 0 && !olders.isSynchronized(db)) {
+ // Add a batch going back to start of time
+ LogUtil.w(TAG, "SyncMessagesAction: Changed messages before sync batch; "
+ + "scheduling a full sync now.");
+
+ final SyncMessagesAction nextBatch =
+ new SyncMessagesAction(-1L, startTimestamp, 0, startTimestamp);
+
+ syncManager.startSyncBatch(startTimestamp);
+ requestBackgroundWork(nextBatch);
+ } else {
+ LogUtil.i(TAG, "SyncMessagesAction: All messages now in sync");
+
+ // All done, in sync
+ syncManager.complete();
+ }
+ }
+ // Either sync should be complete or we should have a follow up request
+ Assert.isTrue(hasBackgroundActions() || !syncManager.isSyncing());
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Decide the next batch size based on the stats we collected with past batch
+ * @param messagesUpdated number of messages updated in this batch
+ * @param txnTimeMillis time the transaction took in ms
+ * @return Target number of messages to sync for next batch
+ */
+ private static int nextBatchSize(final int messagesUpdated, final long txnTimeMillis) {
+ final BugleGservices bugleGservices = BugleGservices.get();
+ final long smsSyncSubsequentBatchTimeLimitMillis = bugleGservices.getLong(
+ BugleGservicesKeys.SMS_SYNC_BATCH_TIME_LIMIT_MILLIS,
+ BugleGservicesKeys.SMS_SYNC_BATCH_TIME_LIMIT_MILLIS_DEFAULT);
+
+ if (txnTimeMillis <= 0) {
+ return 0;
+ }
+ // Number of messages we can sync within the batch time limit using
+ // the average sync time calculated based on the stats we collected
+ // in previous batch
+ return (int) ((double) (messagesUpdated) / (double) txnTimeMillis
+ * smsSyncSubsequentBatchTimeLimitMillis);
+ }
+
+ /**
+ * Batch loading MMS parts for the messages in current batch
+ */
+ private void loadMmsParts(final LongSparseArray<MmsMessage> mmses) {
+ final Context context = Factory.get().getApplicationContext();
+ final int totalIds = mmses.size();
+ for (int start = 0; start < totalIds; start += MmsUtils.MAX_IDS_PER_QUERY) {
+ final int end = Math.min(start + MmsUtils.MAX_IDS_PER_QUERY, totalIds); //excluding
+ final int count = end - start;
+ final String batchSelection = String.format(
+ Locale.US,
+ "%s != '%s' AND %s IN %s",
+ Mms.Part.CONTENT_TYPE,
+ ContentType.APP_SMIL,
+ Mms.Part.MSG_ID,
+ MmsUtils.getSqlInOperand(count));
+ final String[] batchSelectionArgs = new String[count];
+ for (int i = 0; i < count; i++) {
+ batchSelectionArgs[i] = Long.toString(mmses.valueAt(start + i).getId());
+ }
+ final Cursor cursor = SqliteWrapper.query(
+ context,
+ context.getContentResolver(),
+ MmsUtils.MMS_PART_CONTENT_URI,
+ DatabaseMessages.MmsPart.PROJECTION,
+ batchSelection,
+ batchSelectionArgs,
+ null/*sortOrder*/);
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ // Delay loading the media content for parsing for efficiency
+ // TODO: load the media and fill in the dimensions when
+ // we actually display it
+ final DatabaseMessages.MmsPart part =
+ DatabaseMessages.MmsPart.get(cursor, false/*loadMedia*/);
+ final DatabaseMessages.MmsMessage mms = mmses.get(part.mMessageId);
+ if (mms != null) {
+ mms.addPart(part);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * Batch loading MMS sender for the messages in current batch
+ */
+ private void setMmsSenders(final LongSparseArray<MmsMessage> mmses,
+ final ThreadInfoCache cache) {
+ // Store all the MMS messages
+ for (int i = 0; i < mmses.size(); i++) {
+ final MmsMessage mms = mmses.valueAt(i);
+
+ final boolean isOutgoing = mms.mType != Mms.MESSAGE_BOX_INBOX;
+ String senderId = null;
+ if (!isOutgoing) {
+ // We only need to find out sender phone number for received message
+ senderId = getMmsSender(mms, cache);
+ if (senderId == null) {
+ LogUtil.w(TAG, "SyncMessagesAction: Could not find sender of incoming MMS "
+ + "message " + mms.getUri() + "; using 'unknown sender' instead");
+ senderId = ParticipantData.getUnknownSenderDestination();
+ }
+ }
+ mms.setSender(senderId);
+ }
+ }
+
+ /**
+ * Find out the sender of an MMS message
+ */
+ private String getMmsSender(final MmsMessage mms, final ThreadInfoCache cache) {
+ final List<String> recipients = cache.getThreadRecipients(mms.mThreadId);
+ Assert.notNull(recipients);
+ Assert.isTrue(recipients.size() > 0);
+
+ if (recipients.size() == 1
+ && recipients.get(0).equals(ParticipantData.getUnknownSenderDestination())) {
+ LogUtil.w(TAG, "SyncMessagesAction: MMS message " + mms.mUri + " has unknown sender "
+ + "(thread id = " + mms.mThreadId + ")");
+ }
+
+ return MmsUtils.getMmsSender(recipients, mms.mUri);
+ }
+
+ private SyncMessagesAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<SyncMessagesAction> CREATOR
+ = new Parcelable.Creator<SyncMessagesAction>() {
+ @Override
+ public SyncMessagesAction createFromParcel(final Parcel in) {
+ return new SyncMessagesAction(in);
+ }
+
+ @Override
+ public SyncMessagesAction[] newArray(final int size) {
+ return new SyncMessagesAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java b/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java
new file mode 100644
index 0000000..066ad74
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/UpdateConversationArchiveStatusAction.java
@@ -0,0 +1,93 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.util.Assert;
+
+public class UpdateConversationArchiveStatusAction extends Action {
+
+ public static void archiveConversation(final String conversationId) {
+ final UpdateConversationArchiveStatusAction action =
+ new UpdateConversationArchiveStatusAction(conversationId, true /* isArchive */);
+ action.start();
+ }
+
+ public static void unarchiveConversation(final String conversationId) {
+ final UpdateConversationArchiveStatusAction action =
+ new UpdateConversationArchiveStatusAction(conversationId, false /* isArchive */);
+ action.start();
+ }
+
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+ private static final String KEY_IS_ARCHIVE = "is_archive";
+
+ protected UpdateConversationArchiveStatusAction(
+ final String conversationId, final boolean isArchive) {
+ Assert.isTrue(!TextUtils.isEmpty(conversationId));
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ actionParameters.putBoolean(KEY_IS_ARCHIVE, isArchive);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final boolean isArchived = actionParameters.getBoolean(KEY_IS_ARCHIVE);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ BugleDatabaseOperations.updateConversationArchiveStatusInTransaction(
+ db, conversationId, isArchived);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ MessagingContentProvider.notifyConversationListChanged();
+ MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
+ return null;
+ }
+
+ protected UpdateConversationArchiveStatusAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<UpdateConversationArchiveStatusAction> CREATOR
+ = new Parcelable.Creator<UpdateConversationArchiveStatusAction>() {
+ @Override
+ public UpdateConversationArchiveStatusAction createFromParcel(final Parcel in) {
+ return new UpdateConversationArchiveStatusAction(in);
+ }
+
+ @Override
+ public UpdateConversationArchiveStatusAction[] newArray(final int size) {
+ return new UpdateConversationArchiveStatusAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java b/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java
new file mode 100644
index 0000000..6c9e739
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/UpdateConversationOptionsAction.java
@@ -0,0 +1,156 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.util.Assert;
+
+/**
+ * Action used to update conversation options such as notification settings.
+ */
+public class UpdateConversationOptionsAction extends Action
+ implements Parcelable {
+ /**
+ * Enable/disable conversation notifications.
+ */
+ public static void enableConversationNotifications(final String conversationId,
+ final boolean enableNotification) {
+ Assert.notNull(conversationId);
+
+ final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction(
+ conversationId, enableNotification, null, null);
+ action.start();
+ }
+
+ /**
+ * Sets conversation notification sound.
+ */
+ public static void setConversationNotificationSound(final String conversationId,
+ final String ringtoneUri) {
+ Assert.notNull(conversationId);
+
+ final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction(
+ conversationId, null, ringtoneUri, null);
+ action.start();
+ }
+
+ /**
+ * Enable/disable vibrations for conversation notification.
+ */
+ public static void enableVibrationForConversationNotification(final String conversationId,
+ final boolean enableVibration) {
+ Assert.notNull(conversationId);
+
+ final UpdateConversationOptionsAction action = new UpdateConversationOptionsAction(
+ conversationId, null, null, enableVibration);
+ action.start();
+ }
+
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+
+ // Keys for all settable settings.
+ private static final String KEY_ENABLE_NOTIFICATION = "enable_notification";
+ private static final String KEY_RINGTONE_URI = "ringtone_uri";
+ private static final String KEY_ENABLE_VIBRATION = "enable_vibration";
+
+ protected UpdateConversationOptionsAction(final String conversationId,
+ final Boolean enableNotification, final String ringtoneUri,
+ final Boolean enableVibration) {
+ Assert.notNull(conversationId);
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ if (enableNotification != null) {
+ actionParameters.putBoolean(KEY_ENABLE_NOTIFICATION, enableNotification);
+ }
+
+ if (ringtoneUri != null) {
+ actionParameters.putString(KEY_RINGTONE_URI, ringtoneUri);
+ }
+
+ if (enableVibration != null) {
+ actionParameters.putBoolean(KEY_ENABLE_VIBRATION, enableVibration);
+ }
+ }
+
+ protected void putOptionValuesInTransaction(final ContentValues values,
+ final DatabaseWrapper dbWrapper) {
+ Assert.isTrue(dbWrapper.getDatabase().inTransaction());
+ if (actionParameters.containsKey(KEY_ENABLE_NOTIFICATION)) {
+ values.put(ConversationColumns.NOTIFICATION_ENABLED,
+ actionParameters.getBoolean(KEY_ENABLE_NOTIFICATION));
+ }
+
+ if (actionParameters.containsKey(KEY_RINGTONE_URI)) {
+ values.put(ConversationColumns.NOTIFICATION_SOUND_URI,
+ actionParameters.getString(KEY_RINGTONE_URI));
+ }
+
+ if (actionParameters.containsKey(KEY_ENABLE_VIBRATION)) {
+ values.put(ConversationColumns.NOTIFICATION_VIBRATION,
+ actionParameters.getBoolean(KEY_ENABLE_VIBRATION));
+ }
+ }
+
+ @Override
+ protected Object executeAction() {
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ putOptionValuesInTransaction(values, db);
+
+ BugleDatabaseOperations.updateConversationRowIfExists(db, conversationId, values);
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
+ return null;
+ }
+
+ protected UpdateConversationOptionsAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<UpdateConversationOptionsAction> CREATOR
+ = new Parcelable.Creator<UpdateConversationOptionsAction>() {
+ @Override
+ public UpdateConversationOptionsAction createFromParcel(final Parcel in) {
+ return new UpdateConversationOptionsAction(in);
+ }
+
+ @Override
+ public UpdateConversationOptionsAction[] newArray(final int size) {
+ return new UpdateConversationOptionsAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java b/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java
new file mode 100644
index 0000000..c74096d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/UpdateDestinationBlockedAction.java
@@ -0,0 +1,148 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.util.Assert;
+
+public class UpdateDestinationBlockedAction extends Action {
+ public interface UpdateDestinationBlockedActionListener {
+ @Assert.RunsOnMainThread
+ abstract void onUpdateDestinationBlockedAction(final UpdateDestinationBlockedAction action,
+ final boolean success,
+ final boolean block,
+ final String destination);
+ }
+
+ public static class UpdateDestinationBlockedActionMonitor extends ActionMonitor
+ implements ActionMonitor.ActionCompletedListener {
+ private final UpdateDestinationBlockedActionListener mListener;
+
+ public UpdateDestinationBlockedActionMonitor(
+ Object data, UpdateDestinationBlockedActionListener mListener) {
+ super(STATE_CREATED, generateUniqueActionKey("UpdateDestinationBlockedAction"), data);
+ setCompletedListener(this);
+ this.mListener = mListener;
+ }
+
+ private void onActionDone(final boolean succeeded,
+ final ActionMonitor monitor,
+ final Action action,
+ final Object data,
+ final Object result) {
+ mListener.onUpdateDestinationBlockedAction(
+ (UpdateDestinationBlockedAction) action,
+ succeeded,
+ action.actionParameters.getBoolean(KEY_BLOCKED),
+ action.actionParameters.getString(KEY_DESTINATION));
+ }
+
+ @Override
+ public void onActionSucceeded(final ActionMonitor monitor,
+ final Action action,
+ final Object data,
+ final Object result) {
+ onActionDone(true, monitor, action, data, result);
+ }
+
+ @Override
+ public void onActionFailed(final ActionMonitor monitor,
+ final Action action,
+ final Object data,
+ final Object result) {
+ onActionDone(false, monitor, action, data, result);
+ }
+ }
+
+
+ public static UpdateDestinationBlockedActionMonitor updateDestinationBlocked(
+ final String destination, final boolean blocked, final String conversationId,
+ final UpdateDestinationBlockedActionListener listener) {
+ Assert.notNull(listener);
+ final UpdateDestinationBlockedActionMonitor monitor =
+ new UpdateDestinationBlockedActionMonitor(null, listener);
+ final UpdateDestinationBlockedAction action =
+ new UpdateDestinationBlockedAction(destination, blocked, conversationId,
+ monitor.getActionKey());
+ action.start(monitor);
+ return monitor;
+ }
+
+ private static final String KEY_CONVERSATION_ID = "conversation_id";
+ private static final String KEY_DESTINATION = "destination";
+ private static final String KEY_BLOCKED = "blocked";
+
+ protected UpdateDestinationBlockedAction(
+ final String destination, final boolean blocked, final String conversationId,
+ final String actionKey) {
+ super(actionKey);
+ Assert.isTrue(!TextUtils.isEmpty(destination));
+ actionParameters.putString(KEY_DESTINATION, destination);
+ actionParameters.putBoolean(KEY_BLOCKED, blocked);
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final String destination = actionParameters.getString(KEY_DESTINATION);
+ final boolean isBlocked = actionParameters.getBoolean(KEY_BLOCKED);
+ String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ BugleDatabaseOperations.updateDestination(db, destination, isBlocked);
+ if (conversationId == null) {
+ conversationId = BugleDatabaseOperations
+ .getConversationFromOtherParticipantDestination(db, destination);
+ }
+ if (conversationId != null) {
+ if (isBlocked) {
+ UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
+ } else {
+ UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
+ }
+ MessagingContentProvider.notifyParticipantsChanged(conversationId);
+ }
+ return null;
+ }
+
+ protected UpdateDestinationBlockedAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<UpdateDestinationBlockedAction> CREATOR
+ = new Parcelable.Creator<UpdateDestinationBlockedAction>() {
+ @Override
+ public UpdateDestinationBlockedAction createFromParcel(final Parcel in) {
+ return new UpdateDestinationBlockedAction(in);
+ }
+
+ @Override
+ public UpdateDestinationBlockedAction[] newArray(final int size) {
+ return new UpdateDestinationBlockedAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java b/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java
new file mode 100644
index 0000000..94e6f3b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/UpdateMessageNotificationAction.java
@@ -0,0 +1,63 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleNotifications;
+
+/**
+ * Updates the message notification (generally, to include voice replies we've
+ * made since the notification was first posted).
+ */
+public class UpdateMessageNotificationAction extends Action {
+
+ public static void updateMessageNotification() {
+ new UpdateMessageNotificationAction().start();
+ }
+
+ private UpdateMessageNotificationAction() {
+ }
+
+ @Override
+ protected Object executeAction() {
+ BugleNotifications.update(true /* silent */, BugleNotifications.UPDATE_MESSAGES);
+ return null;
+ }
+
+ private UpdateMessageNotificationAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<UpdateMessageNotificationAction> CREATOR
+ = new Parcelable.Creator<UpdateMessageNotificationAction>() {
+ @Override
+ public UpdateMessageNotificationAction createFromParcel(final Parcel in) {
+ return new UpdateMessageNotificationAction(in);
+ }
+
+ @Override
+ public UpdateMessageNotificationAction[] newArray(final int size) {
+ return new UpdateMessageNotificationAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java b/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java
new file mode 100644
index 0000000..273dce9
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/UpdateMessagePartSizeAction.java
@@ -0,0 +1,103 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
+import com.android.messaging.util.Assert;
+
+/**
+ * Action used to update size fields of a single part
+ */
+public class UpdateMessagePartSizeAction extends Action implements Parcelable {
+ /**
+ * Update size of part
+ */
+ public static void updateSize(final String partId, final int width, final int height) {
+ Assert.notNull(partId);
+ Assert.inRange(width, 0, Integer.MAX_VALUE);
+ Assert.inRange(height, 0, Integer.MAX_VALUE);
+
+ final UpdateMessagePartSizeAction action = new UpdateMessagePartSizeAction(
+ partId, width, height);
+ action.start();
+ }
+
+ private static final String KEY_PART_ID = "part_id";
+ private static final String KEY_WIDTH = "width";
+ private static final String KEY_HEIGHT = "height";
+
+ private UpdateMessagePartSizeAction(final String partId, final int width, final int height) {
+ actionParameters.putString(KEY_PART_ID, partId);
+ actionParameters.putInt(KEY_WIDTH, width);
+ actionParameters.putInt(KEY_HEIGHT, height);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final String partId = actionParameters.getString(KEY_PART_ID);
+ final int width = actionParameters.getInt(KEY_WIDTH);
+ final int height = actionParameters.getInt(KEY_HEIGHT);
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+
+ values.put(PartColumns.WIDTH, width);
+ values.put(PartColumns.HEIGHT, height);
+
+ // Part may have been deleted so allow update to fail without asserting
+ BugleDatabaseOperations.updateRowIfExists(db, DatabaseHelper.PARTS_TABLE,
+ PartColumns._ID, partId, values);
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ return null;
+ }
+
+ private UpdateMessagePartSizeAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<UpdateMessagePartSizeAction> CREATOR
+ = new Parcelable.Creator<UpdateMessagePartSizeAction>() {
+ @Override
+ public UpdateMessagePartSizeAction createFromParcel(final Parcel in) {
+ return new UpdateMessagePartSizeAction(in);
+ }
+
+ @Override
+ public UpdateMessagePartSizeAction[] newArray(final int size) {
+ return new UpdateMessagePartSizeAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java b/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java
new file mode 100644
index 0000000..c1f39e1
--- /dev/null
+++ b/src/com/android/messaging/datamodel/action/WriteDraftMessageAction.java
@@ -0,0 +1,104 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.LogUtil;
+
+public class WriteDraftMessageAction extends Action implements Parcelable {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+
+ /**
+ * Set draft message (no listener)
+ */
+ public static void writeDraftMessage(final String conversationId, final MessageData message) {
+ final WriteDraftMessageAction action = new WriteDraftMessageAction(conversationId, message);
+ action.start();
+ }
+
+ private static final String KEY_CONVERSATION_ID = "conversationId";
+ private static final String KEY_MESSAGE = "message";
+
+ private WriteDraftMessageAction(final String conversationId, final MessageData message) {
+ actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
+ actionParameters.putParcelable(KEY_MESSAGE, message);
+ }
+
+ @Override
+ protected Object executeAction() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
+ final MessageData message = actionParameters.getParcelable(KEY_MESSAGE);
+ if (message.getSelfId() == null || message.getParticipantId() == null) {
+ // This could happen when this occurs before the draft message is loaded
+ // In this case, we just use the conversation's current self id as draft's
+ // self id and/or participant id
+ final ConversationListItemData conversation =
+ ConversationListItemData.getExistingConversation(db, conversationId);
+ if (conversation != null) {
+ final String senderAndSelf = conversation.getSelfId();
+ if (message.getSelfId() == null) {
+ message.bindSelfId(senderAndSelf);
+ }
+ if (message.getParticipantId() == null) {
+ message.bindParticipantId(senderAndSelf);
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_DATAMODEL_TAG, "Conversation " + conversationId +
+ "already deleted before saving draft message " +
+ message.getMessageId() + ". Aborting WriteDraftMessageAction.");
+ return null;
+ }
+ }
+ // Drafts are only kept in the local DB...
+ final String messageId = BugleDatabaseOperations.updateDraftMessageData(
+ db, conversationId, message, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
+ MessagingContentProvider.notifyConversationListChanged();
+ MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
+ return messageId;
+ }
+
+ private WriteDraftMessageAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<WriteDraftMessageAction> CREATOR
+ = new Parcelable.Creator<WriteDraftMessageAction>() {
+ @Override
+ public WriteDraftMessageAction createFromParcel(final Parcel in) {
+ return new WriteDraftMessageAction(in);
+ }
+
+ @Override
+ public WriteDraftMessageAction[] newArray(final int size) {
+ return new WriteDraftMessageAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/binding/BindableData.java b/src/com/android/messaging/datamodel/binding/BindableData.java
new file mode 100644
index 0000000..5446098
--- /dev/null
+++ b/src/com/android/messaging/datamodel/binding/BindableData.java
@@ -0,0 +1,72 @@
+/*
+ * 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.datamodel.binding;
+
+/**
+ * Base class for data objects that will be bound to a piece of the UI
+ */
+public abstract class BindableData {
+ /**
+ * Called by Binding during unbind to allow data to proactively unregister callbacks
+ * Data instance should release all listeners that may call back to the host UI
+ */
+ protected abstract void unregisterListeners();
+
+ /**
+ * Key used to identify the piece of UI that the data is currently bound to
+ */
+ private String mBindingId;
+
+ /**
+ * Bind this data to the ui host - checks data is currently unbound
+ */
+ public void bind(final String bindingId) {
+ if (isBound() || bindingId == null) {
+ throw new IllegalStateException();
+ }
+ mBindingId = bindingId;
+ }
+
+ /**
+ * Unbind this data from the ui host - checks that the data is currently bound to specified id
+ */
+ public void unbind(final String bindingId) {
+ if (!isBound(bindingId)) {
+ throw new IllegalStateException();
+ }
+ unregisterListeners();
+ mBindingId = null;
+ }
+
+ /**
+ * Check to see if the data is bound to anything
+ *
+ * TODO: This should be package private because it's supposed to only be used by Binding,
+ * however, several classes call this directly. We want the classes to track what they are
+ * bound to.
+ */
+ protected boolean isBound() {
+ return (mBindingId != null);
+ }
+
+ /**
+ * Check to see if data is still bound with specified bindingId before calling over to ui
+ */
+ public boolean isBound(final String bindingId) {
+ return (bindingId.equals(mBindingId));
+ }
+}
diff --git a/src/com/android/messaging/datamodel/binding/BindableOnceData.java b/src/com/android/messaging/datamodel/binding/BindableOnceData.java
new file mode 100644
index 0000000..08e11da
--- /dev/null
+++ b/src/com/android/messaging/datamodel/binding/BindableOnceData.java
@@ -0,0 +1,42 @@
+/*
+ * 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.datamodel.binding;
+
+/**
+ * A BindableData that's only used to be bound once. If the client needs to rebind, it needs
+ * to create a new instance of the BindableOnceData.
+ */
+public abstract class BindableOnceData extends BindableData {
+ private boolean boundOnce = false;
+
+ @Override
+ public void bind(final String bindingId) {
+ // Ensures that we can't re-bind again after the first binding.
+ if (boundOnce) {
+ throw new IllegalStateException();
+ }
+ super.bind(bindingId);
+ boundOnce = true;
+ }
+
+ /**
+ * Checks if the instance is bound to anything.
+ */
+ @Override
+ public boolean isBound() {
+ return super.isBound();
+ }
+}
diff --git a/src/com/android/messaging/datamodel/binding/Binding.java b/src/com/android/messaging/datamodel/binding/Binding.java
new file mode 100644
index 0000000..3ec01dd
--- /dev/null
+++ b/src/com/android/messaging/datamodel/binding/Binding.java
@@ -0,0 +1,94 @@
+/*
+ * 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.datamodel.binding;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Binding<T extends BindableData> extends BindingBase<T> {
+ private static AtomicLong sBindingIdx = new AtomicLong(System.currentTimeMillis() * 1000);
+
+ private String mBindingId;
+ private T mData;
+ private final Object mOwner;
+ private boolean mWasBound;
+
+ /**
+ * Initialize a binding instance - the owner is typically the containing class
+ */
+ Binding(final Object owner) {
+ mOwner = owner;
+ }
+
+ @Override
+ public T getData() {
+ ensureBound();
+ return mData;
+ }
+
+ @Override
+ public boolean isBound() {
+ return (mData != null && mData.isBound(mBindingId));
+ }
+
+ @Override
+ public boolean isBound(final T data) {
+ return (isBound() && data == mData);
+ }
+
+ @Override
+ public void ensureBound() {
+ if (!isBound()) {
+ throw new IllegalStateException("not bound; wasBound = " + mWasBound);
+ }
+ }
+
+ @Override
+ public void ensureBound(final T data) {
+ if (!isBound()) {
+ throw new IllegalStateException("not bound; wasBound = " + mWasBound);
+ } else if (data != mData) {
+ throw new IllegalStateException("not bound to correct data " + data + " vs " + mData);
+ }
+ }
+
+ @Override
+ public String getBindingId() {
+ return mBindingId;
+ }
+
+ public void bind(final T data) {
+ // Check both this binding and the data not already bound
+ if (mData != null || data.isBound()) {
+ throw new IllegalStateException("already bound when binding to " + data);
+ }
+ // Generate a unique identifier for this bind call
+ mBindingId = Long.toHexString(sBindingIdx.getAndIncrement());
+ data.bind(mBindingId);
+ mData = data;
+ mWasBound = true;
+ }
+
+ public void unbind() {
+ // Check this binding is bound and that data is bound to this binding
+ if (mData == null || !mData.isBound(mBindingId)) {
+ throw new IllegalStateException("not bound when unbind");
+ }
+ mData.unbind(mBindingId);
+ mData = null;
+ mBindingId = null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/binding/BindingBase.java b/src/com/android/messaging/datamodel/binding/BindingBase.java
new file mode 100644
index 0000000..3d6da9b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/binding/BindingBase.java
@@ -0,0 +1,84 @@
+/*
+ * 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.datamodel.binding;
+
+/**
+ * The binding class keeps track of a binding between a ui component and an item of BindableData
+ * It allows each side to ensure that when it communicates with the other they are still bound
+ * together.
+ * NOTE: Ensure that the UI component uses the same binding instance for it's whole lifetime
+ * (DO NOT CREATE A NEW BINDING EACH TIME A NEW PIECE OF DATA IS BOUND)...
+ *
+ * The ui component owns the binding instance.
+ * It can use it [isBound(data)] to see if the binding still binds to the right piece of data
+ *
+ * Upon binding the data is informed of a unique binding key generated in this class and can use
+ * that to ensure that it is still issuing callbacks to the right piece of ui.
+ */
+public abstract class BindingBase<T extends BindableData> {
+ /**
+ * Creates a new exclusively owned binding for the owner object.
+ */
+ public static <T extends BindableData> Binding<T> createBinding(final Object owner) {
+ return new Binding<T>(owner);
+ }
+
+ /**
+ * Creates a new read-only binding referencing the source binding object.
+ * TODO: We may want to refcount the Binding references, so that when the binding owner
+ * calls unbind() when there's still outstanding references we can catch it.
+ */
+ public static <T extends BindableData> ImmutableBindingRef<T> createBindingReference(
+ final BindingBase<T> srcBinding) {
+ return new ImmutableBindingRef<T>(srcBinding);
+ }
+
+ /**
+ * Creates a detachable binding for the owner object. Use this if your owner object is a UI
+ * component that may undergo a "detached from window" -> "re-attached to window" transition.
+ */
+ public static <T extends BindableData> DetachableBinding<T> createDetachableBinding(
+ final Object owner) {
+ return new DetachableBinding<T>(owner);
+ }
+
+ public abstract T getData();
+
+ /**
+ * Check if binding connects to the specified data instance
+ */
+ public abstract boolean isBound();
+
+ /**
+ * Check if binding connects to the specified data instance
+ */
+ public abstract boolean isBound(final T data);
+
+ /**
+ * Throw if binding connects to the specified data instance
+ */
+ public abstract void ensureBound();
+
+ /**
+ * Throw if binding connects to the specified data instance
+ */
+ public abstract void ensureBound(final T data);
+
+ /**
+ * Return the binding id for this binding (will be null if not bound)
+ */
+ public abstract String getBindingId();
+}
diff --git a/src/com/android/messaging/datamodel/binding/DetachableBinding.java b/src/com/android/messaging/datamodel/binding/DetachableBinding.java
new file mode 100644
index 0000000..a414c3b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/binding/DetachableBinding.java
@@ -0,0 +1,56 @@
+/*
+ * 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.datamodel.binding;
+
+import com.android.messaging.util.Assert;
+
+/**
+ * An extension on {@link Binding} that allows for temporary data detachment from the UI component.
+ * This is used when, instead of destruction or data rebinding, the owning UI undergoes a
+ * "detached from window" -> "re-attached to window" transition, in which case we want to
+ * temporarily unbind the data and remember it so that it can be rebound when the UI is re-attached
+ * to window later.
+ */
+public class DetachableBinding<T extends BindableData> extends Binding<T> {
+ private T mDetachedData;
+
+ DetachableBinding(Object owner) {
+ super(owner);
+ }
+
+ @Override
+ public void bind(T data) {
+ super.bind(data);
+ // Rebinding before re-attaching. Pre-emptively throw away the detached data because
+ // it's now stale.
+ mDetachedData = null;
+ }
+
+ public void detach() {
+ Assert.isNull(mDetachedData);
+ Assert.isTrue(isBound());
+ mDetachedData = getData();
+ unbind();
+ }
+
+ public void reAttachIfPossible() {
+ if (mDetachedData != null) {
+ Assert.isFalse(isBound());
+ bind(mDetachedData);
+ mDetachedData = null;
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java b/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java
new file mode 100644
index 0000000..9a0a3d6
--- /dev/null
+++ b/src/com/android/messaging/datamodel/binding/ImmutableBindingRef.java
@@ -0,0 +1,83 @@
+/*
+ * 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.datamodel.binding;
+
+import com.android.messaging.util.Assert;
+
+/**
+ * A immutable wrapper around a Binding object. Callers can only access readonly methods like
+ * getData(), isBound() and ensureBound() but not bind() and unbind(). This is used for MVC pattern
+ * where both the View and the Controller needs access to a centrally bound Model object. The View
+ * is the one that owns the bind/unbind logic of the Binding, whereas controller only serves as a
+ * consumer.
+ */
+public class ImmutableBindingRef<T extends BindableData> extends BindingBase<T> {
+ /**
+ * The referenced, read-only binding object.
+ */
+ private final BindingBase<T> mBinding;
+
+ /**
+ * Hidden ctor.
+ */
+ ImmutableBindingRef(final BindingBase<T> binding) {
+ mBinding = resolveBinding(binding);
+ }
+
+ @Override
+ public T getData() {
+ return mBinding.getData();
+ }
+
+ @Override
+ public boolean isBound() {
+ return mBinding.isBound();
+ }
+
+ @Override
+ public boolean isBound(final T data) {
+ return mBinding.isBound(data);
+ }
+
+ @Override
+ public void ensureBound() {
+ mBinding.ensureBound();
+ }
+
+ @Override
+ public void ensureBound(final T data) {
+ mBinding.ensureBound(data);
+ }
+
+ @Override
+ public String getBindingId() {
+ return mBinding.getBindingId();
+ }
+
+ /**
+ * Resolve the source binding to the real BindingImpl it's referencing. This avoids the
+ * redundancy of multiple wrapper calls when creating a binding reference from an existing
+ * binding reference.
+ */
+ private BindingBase<T> resolveBinding(final BindingBase<T> binding) {
+ BindingBase<T> resolvedBinding = binding;
+ while (resolvedBinding instanceof ImmutableBindingRef<?>) {
+ resolvedBinding = ((ImmutableBindingRef<T>) resolvedBinding).mBinding;
+ }
+ Assert.isTrue(resolvedBinding instanceof Binding<?>);
+ return resolvedBinding;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java b/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java
new file mode 100644
index 0000000..4e94ee1
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/BlockedParticipantsData.java
@@ -0,0 +1,103 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.messaging.datamodel.BoundCursorLoader;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.util.Assert;
+
+/**
+ * Services data needs for BlockedParticipantsFragment
+ */
+public class BlockedParticipantsData extends BindableData implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+ public interface BlockedParticipantsDataListener {
+ public void onBlockedParticipantsCursorUpdated(final Cursor cursor);
+ }
+ private static final String BINDING_ID = "bindingId";
+ private static final int BLOCKED_PARTICIPANTS_LOADER = 1;
+ private final Context mContext;
+ private LoaderManager mLoaderManager;
+ private BlockedParticipantsDataListener mListener;
+
+ public BlockedParticipantsData(final Context context,
+ final BlockedParticipantsDataListener listener) {
+ mContext = context;
+ mListener = listener;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ Assert.isTrue(id == BLOCKED_PARTICIPANTS_LOADER);
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ final Uri uri = MessagingContentProvider.PARTICIPANTS_URI;
+ return new BoundCursorLoader(bindingId, mContext, uri,
+ ParticipantData.ParticipantsQuery.PROJECTION,
+ ParticipantColumns.BLOCKED + "=1", null, null);
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) {
+ Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER);
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ Assert.isTrue(isBound(cursorLoader.getBindingId()));
+ mListener.onBlockedParticipantsCursorUpdated(cursor);
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<Cursor> loader) {
+ Assert.isTrue(loader.getId() == BLOCKED_PARTICIPANTS_LOADER);
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ Assert.isTrue(isBound(cursorLoader.getBindingId()));
+ mListener.onBlockedParticipantsCursorUpdated(null);
+ }
+
+ public void init(final LoaderManager loaderManager,
+ final BindingBase<BlockedParticipantsData> binding) {
+ final Bundle args = new Bundle();
+ args.putString(BINDING_ID, binding.getBindingId());
+ mLoaderManager = loaderManager;
+ mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_LOADER, args, this);
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_LOADER);
+ mLoaderManager = null;
+ }
+ }
+
+ public ParticipantListItemData createParticipantListItemData(Cursor cursor) {
+ return new ParticipantListItemData(ParticipantData.getFromCursor(cursor));
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ContactListItemData.java b/src/com/android/messaging/datamodel/data/ContactListItemData.java
new file mode 100644
index 0000000..dcc7e20
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ContactListItemData.java
@@ -0,0 +1,160 @@
+/*
+ * 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.datamodel.data;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.DisplayNameSources;
+
+import com.android.ex.chips.RecipientEntry;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactRecipientEntryUtils;
+import com.android.messaging.util.ContactUtil;
+
+/**
+ * Data model object used to power ContactListItemViews, which may be displayed either in
+ * our contact list, or in the chips UI search drop down presented by ContactDropdownLayouter.
+ */
+public class ContactListItemData {
+ // Keeps the contact data in the form of RecipientEntry that RecipientEditTextView can
+ // directly use.
+ private RecipientEntry mRecipientEntry;
+
+ private CharSequence mStyledName;
+ private CharSequence mStyledDestination;
+
+ // If this contact is the first in the list for its first letter, then this will be the
+ // first letter, otherwise this is null.
+ private String mAlphabetHeader;
+
+ // Is the contact the only item in the list (happens when the user clicks on an
+ // existing chip for which we show full contact detail for the selected contact).
+ private boolean mSingleRecipient;
+
+ /**
+ * Bind to a contact cursor in the contact list.
+ */
+ public void bind(final Cursor cursor, final String alphabetHeader) {
+ final long dataId = cursor.getLong(ContactUtil.INDEX_DATA_ID);
+ final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ final String lookupKey = cursor.getString(ContactUtil.INDEX_LOOKUP_KEY);
+ final String displayName = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
+ final String photoThumbnailUri = cursor.getString(ContactUtil.INDEX_PHOTO_URI);
+ final String destination = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL);
+ final int destinationType = cursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE);
+ final String destinationLabel = cursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL);
+ mStyledName = null;
+ mStyledDestination = null;
+ mAlphabetHeader = alphabetHeader;
+ mSingleRecipient = false;
+
+ // Check whether this contact is first level (i.e. whether it's the first entry of this
+ // contact in the contact list).
+ boolean isFirstLevel = true;
+ if (!cursor.isFirst() && cursor.moveToPrevious()) {
+ final long contactIdPrevious = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ if (contactId == contactIdPrevious) {
+ isFirstLevel = false;
+ }
+ cursor.moveToNext();
+ }
+
+ mRecipientEntry = ContactUtil.createRecipientEntry(displayName,
+ DisplayNameSources.STRUCTURED_NAME, destination, destinationType, destinationLabel,
+ contactId, lookupKey, dataId, photoThumbnailUri, isFirstLevel);
+ }
+
+ /**
+ * Bind to a RecipientEntry produced by the chips text view in the search drop down, plus
+ * optional styled name & destination for showing bold search match.
+ */
+ public void bind(final RecipientEntry entry, final CharSequence styledName,
+ final CharSequence styledDestination, final boolean singleRecipient) {
+ Assert.isTrue(entry.isValid());
+ mRecipientEntry = entry;
+ mStyledName = styledName;
+ mStyledDestination = styledDestination;
+ mAlphabetHeader = null;
+ mSingleRecipient = singleRecipient;
+ }
+
+ public CharSequence getDisplayName() {
+ final CharSequence displayName = mStyledName != null ? mStyledName :
+ ContactRecipientEntryUtils.getDisplayNameForContactList(mRecipientEntry);
+ return displayName == null ? "" : displayName;
+ }
+
+ public Uri getPhotoThumbnailUri() {
+ return mRecipientEntry.getPhotoThumbnailUri() == null ? null :
+ mRecipientEntry.getPhotoThumbnailUri();
+ }
+
+ public CharSequence getDestination() {
+ final CharSequence destination = mStyledDestination != null ?
+ mStyledDestination : ContactRecipientEntryUtils.formatDestination(mRecipientEntry);
+ return destination == null ? "" : destination;
+ }
+
+ public int getDestinationType() {
+ return mRecipientEntry.getDestinationType();
+ }
+
+ public String getDestinationLabel() {
+ return mRecipientEntry.getDestinationLabel();
+ }
+
+ public long getContactId() {
+ return mRecipientEntry.getContactId();
+ }
+
+ public String getLookupKey() {
+ return mRecipientEntry.getLookupKey();
+ }
+
+ /**
+ * Returns if this item is "first-level," i.e. whether it's the first entry of the contact
+ * that it represents in the list. For example, if John Smith has 3 different phone numbers,
+ * then the first number is considered first-level, while the other two are considered
+ * second-level.
+ */
+ public boolean getIsFirstLevel() {
+ // Treat the item as first level if it's a top-level recipient entry, or if it's the only
+ // item in the list.
+ return mRecipientEntry.isFirstLevel() || mSingleRecipient;
+ }
+
+ /**
+ * Returns if this item is simple, i.e. it has only avatar and a display name with phone number
+ * embedded so we can hide everything else.
+ */
+ public boolean getIsSimpleContactItem() {
+ return ContactRecipientEntryUtils.isAvatarAndNumberOnlyContact(mRecipientEntry) ||
+ ContactRecipientEntryUtils.isSendToDestinationContact(mRecipientEntry);
+ }
+
+ public String getAlphabetHeader() {
+ return mAlphabetHeader;
+ }
+
+ /**
+ * Returns a RecipientEntry instance readily usable by the RecipientEditTextView.
+ */
+ public RecipientEntry getRecipientEntry() {
+ return mRecipientEntry;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ContactPickerData.java b/src/com/android/messaging/datamodel/data/ContactPickerData.java
new file mode 100644
index 0000000..fd6fca0
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ContactPickerData.java
@@ -0,0 +1,194 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+
+import com.android.messaging.datamodel.BoundCursorLoader;
+import com.android.messaging.datamodel.FrequentContactsCursorBuilder;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Class to access phone contacts.
+ * The caller is responsible for ensuring that the app has READ_CONTACTS permission (see
+ * {@link ContactUtil#hasReadContactsPermission()}) before instantiating this class.
+ */
+public class ContactPickerData extends BindableData implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+ public interface ContactPickerDataListener {
+ void onAllContactsCursorUpdated(Cursor data);
+ void onFrequentContactsCursorUpdated(Cursor data);
+ void onContactCustomColorLoaded(ContactPickerData data);
+ }
+
+ private static final String BINDING_ID = "bindingId";
+ private final Context mContext;
+ private LoaderManager mLoaderManager;
+ private ContactPickerDataListener mListener;
+ private final FrequentContactsCursorBuilder mFrequentContactsCursorBuilder;
+
+ public ContactPickerData(final Context context, final ContactPickerDataListener listener) {
+ mListener = listener;
+ mContext = context;
+ mFrequentContactsCursorBuilder = new FrequentContactsCursorBuilder();
+ }
+
+ private static final int ALL_CONTACTS_LOADER = 1;
+ private static final int FREQUENT_CONTACTS_LOADER = 2;
+ private static final int PARTICIPANT_LOADER = 3;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ switch (id) {
+ case ALL_CONTACTS_LOADER:
+ return ContactUtil.getPhones(mContext)
+ .createBoundCursorLoader(bindingId);
+ case FREQUENT_CONTACTS_LOADER:
+ return ContactUtil.getFrequentContacts(mContext)
+ .createBoundCursorLoader(bindingId);
+ case PARTICIPANT_LOADER:
+ return new BoundCursorLoader(bindingId, mContext,
+ MessagingContentProvider.PARTICIPANTS_URI,
+ ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
+ default:
+ Assert.fail("Unknown loader id for contact picker!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the contacts list");
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ if (isBound(cursorLoader.getBindingId())) {
+ switch (loader.getId()) {
+ case ALL_CONTACTS_LOADER:
+ mListener.onAllContactsCursorUpdated(data);
+ mFrequentContactsCursorBuilder.setAllContacts(data);
+ break;
+ case FREQUENT_CONTACTS_LOADER:
+ mFrequentContactsCursorBuilder.setFrequents(data);
+ break;
+ case PARTICIPANT_LOADER:
+ mListener.onContactCustomColorLoaded(this);
+ break;
+ default:
+ Assert.fail("Unknown loader id for contact picker!");
+ break;
+ }
+
+ if (loader.getId() != PARTICIPANT_LOADER) {
+ // The frequent contacts cursor to be used in the UI depends on results from both
+ // all contacts and frequent contacts loader, and we don't know which will finish
+ // first. Therefore, try to build the cursor and notify the listener if it's
+ // successfully built.
+ final Cursor frequentContactsCursor = mFrequentContactsCursorBuilder.build();
+ if (frequentContactsCursor != null) {
+ mListener.onFrequentContactsCursorUpdated(frequentContactsCursor);
+ }
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the contacts list");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<Cursor> loader) {
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ if (isBound(cursorLoader.getBindingId())) {
+ switch (loader.getId()) {
+ case ALL_CONTACTS_LOADER:
+ mListener.onAllContactsCursorUpdated(null);
+ mFrequentContactsCursorBuilder.setAllContacts(null);
+ break;
+ case FREQUENT_CONTACTS_LOADER:
+ mListener.onFrequentContactsCursorUpdated(null);
+ mFrequentContactsCursorBuilder.setFrequents(null);
+ break;
+ case PARTICIPANT_LOADER:
+ mListener.onContactCustomColorLoaded(this);
+ break;
+ default:
+ Assert.fail("Unknown loader id for contact picker!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the contacts list");
+ }
+ }
+
+ public void init(final LoaderManager loaderManager,
+ final BindingBase<ContactPickerData> binding) {
+ final Bundle args = new Bundle();
+ args.putString(BINDING_ID, binding.getBindingId());
+ mLoaderManager = loaderManager;
+ mLoaderManager.initLoader(ALL_CONTACTS_LOADER, args, this);
+ mLoaderManager.initLoader(FREQUENT_CONTACTS_LOADER, args, this);
+ mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+
+
+ // This could be null if we bind but the caller doesn't init the BindableData
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(ALL_CONTACTS_LOADER);
+ mLoaderManager.destroyLoader(FREQUENT_CONTACTS_LOADER);
+ mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
+ mLoaderManager = null;
+ }
+ mFrequentContactsCursorBuilder.resetBuilder();
+ }
+
+ public static boolean isTooManyParticipants(final int participantCount) {
+ // When creating a conversation, the conversation will be created using the system's
+ // default SIM, so use the default MmsConfig's recipient limit.
+ return (participantCount > MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
+ .getRecipientLimit());
+ }
+
+ public static boolean getCanAddMoreParticipants(final int participantCount) {
+ // When creating a conversation, the conversation will be created using the system's
+ // default SIM, so use the default MmsConfig's recipient limit.
+ return (participantCount < MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
+ .getRecipientLimit());
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationData.java b/src/com/android/messaging/datamodel/data/ConversationData.java
new file mode 100644
index 0000000..d504928
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ConversationData.java
@@ -0,0 +1,849 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.sqlite.SQLiteFullException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.common.contacts.DataUsageStatUpdater;
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.BoundCursorLoader;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.DeleteConversationAction;
+import com.android.messaging.datamodel.action.DeleteMessageAction;
+import com.android.messaging.datamodel.action.InsertNewMessageAction;
+import com.android.messaging.datamodel.action.RedownloadMmsAction;
+import com.android.messaging.datamodel.action.ResendMessageAction;
+import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.widget.WidgetConversationProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ConversationData extends BindableData {
+
+ private static final String TAG = "bugle_datamodel";
+ private static final String BINDING_ID = "bindingId";
+ private static final long LAST_MESSAGE_TIMESTAMP_NaN = -1;
+ private static final int MESSAGE_COUNT_NaN = -1;
+
+ /**
+ * Takes a conversation id and a list of message ids and computes the positions
+ * for each message.
+ */
+ public List<Integer> getPositions(final String conversationId, final List<Long> ids) {
+ final ArrayList<Integer> result = new ArrayList<Integer>();
+
+ if (ids.isEmpty()) {
+ return result;
+ }
+
+ final Cursor c = new ConversationData.ReversedCursor(
+ DataModel.get().getDatabase().rawQuery(
+ ConversationMessageData.getConversationMessageIdsQuerySql(),
+ new String [] { conversationId }));
+ if (c != null) {
+ try {
+ final Set<Long> idsSet = new HashSet<Long>(ids);
+ if (c.moveToLast()) {
+ do {
+ final long messageId = c.getLong(0);
+ if (idsSet.contains(messageId)) {
+ result.add(c.getPosition());
+ }
+ } while (c.moveToPrevious());
+ }
+ } finally {
+ c.close();
+ }
+ }
+ Collections.sort(result);
+ return result;
+ }
+
+ public interface ConversationDataListener {
+ public void onConversationMessagesCursorUpdated(ConversationData data, Cursor cursor,
+ @Nullable ConversationMessageData newestMessage, boolean isSync);
+ public void onConversationMetadataUpdated(ConversationData data);
+ public void closeConversation(String conversationId);
+ public void onConversationParticipantDataLoaded(ConversationData data);
+ public void onSubscriptionListDataLoaded(ConversationData data);
+ }
+
+ private static class ReversedCursor extends CursorWrapper {
+ final int mCount;
+
+ public ReversedCursor(final Cursor cursor) {
+ super(cursor);
+ mCount = cursor.getCount();
+ }
+
+ @Override
+ public boolean moveToPosition(final int position) {
+ return super.moveToPosition(mCount - position - 1);
+ }
+
+ @Override
+ public int getPosition() {
+ return mCount - super.getPosition() - 1;
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return super.isBeforeFirst();
+ }
+
+ @Override
+ public boolean isBeforeFirst() {
+ return super.isAfterLast();
+ }
+
+ @Override
+ public boolean isFirst() {
+ return super.isLast();
+ }
+
+ @Override
+ public boolean isLast() {
+ return super.isFirst();
+ }
+
+ @Override
+ public boolean move(final int offset) {
+ return super.move(-offset);
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ return super.moveToLast();
+ }
+
+ @Override
+ public boolean moveToLast() {
+ return super.moveToFirst();
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return super.moveToPrevious();
+ }
+
+ @Override
+ public boolean moveToPrevious() {
+ return super.moveToNext();
+ }
+ }
+
+ /**
+ * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
+ */
+ private class MetadataLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ Assert.equals(CONVERSATION_META_DATA_LOADER, id);
+ Loader<Cursor> loader = null;
+
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ final Uri uri =
+ MessagingContentProvider.buildConversationMetadataUri(mConversationId);
+ loader = new BoundCursorLoader(bindingId, mContext, uri,
+ ConversationListItemData.PROJECTION, null, null, null);
+ } else {
+ LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " +
+ mConversationId);
+ }
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ if (data.moveToNext()) {
+ Assert.isTrue(data.getCount() == 1);
+ mConversationMetadata.bind(data);
+ mListeners.onConversationMetadataUpdated(ConversationData.this);
+ } else {
+ // Close the conversation, no meta data means conversation was deleted
+ LogUtil.w(TAG, "Meta data loader returned nothing for mConversationId = " +
+ mConversationId);
+ mListeners.closeConversation(mConversationId);
+ // Notify the widget the conversation is deleted so it can go into its
+ // configure state.
+ WidgetConversationProvider.notifyConversationDeleted(
+ Factory.get().getApplicationContext(),
+ mConversationId);
+ }
+ } else {
+ LogUtil.w(TAG, "Meta data loader finished after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<Cursor> generic) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ // Clear the conversation meta data
+ mConversationMetadata = new ConversationListItemData();
+ mListeners.onConversationMetadataUpdated(ConversationData.this);
+ } else {
+ LogUtil.w(TAG, "Meta data loader reset after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+ }
+
+ /**
+ * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
+ */
+ private class MessagesLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ Assert.equals(CONVERSATION_MESSAGES_LOADER, id);
+ Loader<Cursor> loader = null;
+
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ final Uri uri =
+ MessagingContentProvider.buildConversationMessagesUri(mConversationId);
+ loader = new BoundCursorLoader(bindingId, mContext, uri,
+ ConversationMessageData.getProjection(), null, null, null);
+ mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
+ mMessageCount = MESSAGE_COUNT_NaN;
+ } else {
+ LogUtil.w(TAG, "Creating messages loader after unbinding mConversationId = " +
+ mConversationId);
+ }
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<Cursor> generic, final Cursor rawData) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ // Check if we have a new message, or if we had a message sync.
+ ConversationMessageData newMessage = null;
+ boolean isSync = false;
+ Cursor data = null;
+ if (rawData != null) {
+ // Note that the cursor is sorted DESC so here we reverse it.
+ // This is a performance issue (improvement) for large cursors.
+ data = new ReversedCursor(rawData);
+
+ final int messageCountOld = mMessageCount;
+ mMessageCount = data.getCount();
+ final ConversationMessageData lastMessage = getLastMessage(data);
+ if (lastMessage != null) {
+ final long lastMessageTimestampOld = mLastMessageTimestamp;
+ mLastMessageTimestamp = lastMessage.getReceivedTimeStamp();
+ final String lastMessageIdOld = mLastMessageId;
+ mLastMessageId = lastMessage.getMessageId();
+ if (TextUtils.equals(lastMessageIdOld, mLastMessageId) &&
+ messageCountOld < mMessageCount) {
+ // Last message stays the same (no incoming message) but message
+ // count increased, which means there has been a message sync.
+ isSync = true;
+ } else if (messageCountOld != MESSAGE_COUNT_NaN && // Ignore initial load
+ mLastMessageTimestamp != LAST_MESSAGE_TIMESTAMP_NaN &&
+ mLastMessageTimestamp > lastMessageTimestampOld) {
+ newMessage = lastMessage;
+ }
+ } else {
+ mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
+ }
+ } else {
+ mMessageCount = MESSAGE_COUNT_NaN;
+ }
+
+ mListeners.onConversationMessagesCursorUpdated(ConversationData.this, data,
+ newMessage, isSync);
+ } else {
+ LogUtil.w(TAG, "Messages loader finished after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<Cursor> generic) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mListeners.onConversationMessagesCursorUpdated(ConversationData.this, null, null,
+ false);
+ mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
+ mMessageCount = MESSAGE_COUNT_NaN;
+ } else {
+ LogUtil.w(TAG, "Messages loader reset after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+
+ private ConversationMessageData getLastMessage(final Cursor cursor) {
+ if (cursor != null && cursor.getCount() > 0) {
+ final int position = cursor.getPosition();
+ if (cursor.moveToLast()) {
+ final ConversationMessageData messageData = new ConversationMessageData();
+ messageData.bind(cursor);
+ cursor.move(position);
+ return messageData;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
+ */
+ private class ParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ Assert.equals(PARTICIPANT_LOADER, id);
+ Loader<Cursor> loader = null;
+
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ final Uri uri =
+ MessagingContentProvider.buildConversationParticipantsUri(mConversationId);
+ loader = new BoundCursorLoader(bindingId, mContext, uri,
+ ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
+ } else {
+ LogUtil.w(TAG, "Creating participant loader after unbinding mConversationId = " +
+ mConversationId);
+ }
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mParticipantData.bind(data);
+ mListeners.onConversationParticipantDataLoaded(ConversationData.this);
+ } else {
+ LogUtil.w(TAG, "Participant loader finished after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<Cursor> generic) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mParticipantData.bind(null);
+ } else {
+ LogUtil.w(TAG, "Participant loader reset after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+ }
+
+ /**
+ * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
+ */
+ private class SelfParticipantLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ Assert.equals(SELF_PARTICIPANT_LOADER, id);
+ Loader<Cursor> loader = null;
+
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ loader = new BoundCursorLoader(bindingId, mContext,
+ MessagingContentProvider.PARTICIPANTS_URI,
+ ParticipantData.ParticipantsQuery.PROJECTION,
+ ParticipantColumns.SUB_ID + " <> ?",
+ new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
+ null);
+ } else {
+ LogUtil.w(TAG, "Creating self loader after unbinding mConversationId = " +
+ mConversationId);
+ }
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mSelfParticipantsData.bind(data);
+ mSubscriptionListData.bind(mSelfParticipantsData.getSelfParticipants(true));
+ mListeners.onSubscriptionListDataLoaded(ConversationData.this);
+ } else {
+ LogUtil.w(TAG, "Self loader finished after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<Cursor> generic) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mSelfParticipantsData.bind(null);
+ } else {
+ LogUtil.w(TAG, "Self loader reset after unbinding mConversationId = " +
+ mConversationId);
+ }
+ }
+ }
+
+ private final ConversationDataEventDispatcher mListeners;
+ private final MetadataLoaderCallbacks mMetadataLoaderCallbacks;
+ private final MessagesLoaderCallbacks mMessagesLoaderCallbacks;
+ private final ParticipantLoaderCallbacks mParticipantsLoaderCallbacks;
+ private final SelfParticipantLoaderCallbacks mSelfParticipantLoaderCallbacks;
+ private final Context mContext;
+ private final String mConversationId;
+ private final ConversationParticipantsData mParticipantData;
+ private final SelfParticipantsData mSelfParticipantsData;
+ private ConversationListItemData mConversationMetadata;
+ private final SubscriptionListData mSubscriptionListData;
+ private LoaderManager mLoaderManager;
+ private long mLastMessageTimestamp = LAST_MESSAGE_TIMESTAMP_NaN;
+ private int mMessageCount = MESSAGE_COUNT_NaN;
+ private String mLastMessageId;
+
+ public ConversationData(final Context context, final ConversationDataListener listener,
+ final String conversationId) {
+ Assert.isTrue(conversationId != null);
+ mContext = context;
+ mConversationId = conversationId;
+ mMetadataLoaderCallbacks = new MetadataLoaderCallbacks();
+ mMessagesLoaderCallbacks = new MessagesLoaderCallbacks();
+ mParticipantsLoaderCallbacks = new ParticipantLoaderCallbacks();
+ mSelfParticipantLoaderCallbacks = new SelfParticipantLoaderCallbacks();
+ mParticipantData = new ConversationParticipantsData();
+ mConversationMetadata = new ConversationListItemData();
+ mSelfParticipantsData = new SelfParticipantsData();
+ mSubscriptionListData = new SubscriptionListData(context);
+
+ mListeners = new ConversationDataEventDispatcher();
+ mListeners.add(listener);
+ }
+
+ @RunsOnMainThread
+ public void addConversationDataListener(final ConversationDataListener listener) {
+ Assert.isMainThread();
+ mListeners.add(listener);
+ }
+
+ public String getConversationName() {
+ return mConversationMetadata.getName();
+ }
+
+ public boolean getIsArchived() {
+ return mConversationMetadata.getIsArchived();
+ }
+
+ public String getIcon() {
+ return mConversationMetadata.getIcon();
+ }
+
+ public String getConversationId() {
+ return mConversationId;
+ }
+
+ public void setFocus() {
+ DataModel.get().setFocusedConversation(mConversationId);
+ // As we are loading the conversation assume the user has read the messages...
+ // Do this late though so that it doesn't get in the way of other actions
+ BugleNotifications.markMessagesAsRead(mConversationId);
+ }
+
+ public void unsetFocus() {
+ DataModel.get().setFocusedConversation(null);
+ }
+
+ public boolean isFocused() {
+ return isBound() && DataModel.get().isFocusedConversation(mConversationId);
+ }
+
+ private static final int CONVERSATION_META_DATA_LOADER = 1;
+ private static final int CONVERSATION_MESSAGES_LOADER = 2;
+ private static final int PARTICIPANT_LOADER = 3;
+ private static final int SELF_PARTICIPANT_LOADER = 4;
+
+ public void init(final LoaderManager loaderManager,
+ final BindingBase<ConversationData> binding) {
+ // Remember the binding id so that loader callbacks can check if data is still bound
+ // to same ui component
+ final Bundle args = new Bundle();
+ args.putString(BINDING_ID, binding.getBindingId());
+ mLoaderManager = loaderManager;
+ mLoaderManager.initLoader(CONVERSATION_META_DATA_LOADER, args, mMetadataLoaderCallbacks);
+ mLoaderManager.initLoader(CONVERSATION_MESSAGES_LOADER, args, mMessagesLoaderCallbacks);
+ mLoaderManager.initLoader(PARTICIPANT_LOADER, args, mParticipantsLoaderCallbacks);
+ mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, mSelfParticipantLoaderCallbacks);
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListeners.clear();
+ // Make sure focus has moved away from this conversation
+ // TODO: May false trigger if destroy happens after "new" conversation is focused.
+ // Assert.isTrue(!DataModel.get().isFocusedConversation(mConversationId));
+
+ // This could be null if we bind but the caller doesn't init the BindableData
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(CONVERSATION_META_DATA_LOADER);
+ mLoaderManager.destroyLoader(CONVERSATION_MESSAGES_LOADER);
+ mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
+ mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER);
+ mLoaderManager = null;
+ }
+ }
+
+ /**
+ * Gets the default self participant in the participant table (NOT the conversation's self).
+ * This is available as soon as self participant data is loaded.
+ */
+ public ParticipantData getDefaultSelfParticipant() {
+ return mSelfParticipantsData.getDefaultSelfParticipant();
+ }
+
+ public List<ParticipantData> getSelfParticipants(final boolean activeOnly) {
+ return mSelfParticipantsData.getSelfParticipants(activeOnly);
+ }
+
+ public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) {
+ return mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(activeOnly);
+ }
+
+ public ParticipantData getSelfParticipantById(final String selfId) {
+ return mSelfParticipantsData.getSelfParticipantById(selfId);
+ }
+
+ /**
+ * For a 1:1 conversation return the other (not self) participant (else null)
+ */
+ public ParticipantData getOtherParticipant() {
+ return mParticipantData.getOtherParticipant();
+ }
+
+ /**
+ * Return true once the participants are loaded
+ */
+ public boolean getParticipantsLoaded() {
+ return mParticipantData.isLoaded();
+ }
+
+ public void sendMessage(final BindingBase<ConversationData> binding,
+ final MessageData message) {
+ Assert.isTrue(TextUtils.equals(mConversationId, message.getConversationId()));
+ Assert.isTrue(binding.getData() == this);
+
+ if (!OsUtil.isAtLeastL_MR1() || message.getSelfId() == null) {
+ InsertNewMessageAction.insertNewMessage(message);
+ } else {
+ final int systemDefaultSubId = PhoneUtils.getDefault().getDefaultSmsSubscriptionId();
+ if (systemDefaultSubId != ParticipantData.DEFAULT_SELF_SUB_ID &&
+ mSelfParticipantsData.isDefaultSelf(message.getSelfId())) {
+ // Lock the sub selection to the system default SIM as soon as the user clicks on
+ // the send button to avoid races between this and when InsertNewMessageAction is
+ // actually executed on the data model thread, during which the user can potentially
+ // change the system default SIM in Settings.
+ InsertNewMessageAction.insertNewMessage(message, systemDefaultSubId);
+ } else {
+ InsertNewMessageAction.insertNewMessage(message);
+ }
+ }
+ // Update contacts so Frequents will reflect messaging activity.
+ if (!getParticipantsLoaded()) {
+ return; // oh well, not critical
+ }
+ final ArrayList<String> phones = new ArrayList<>();
+ final ArrayList<String> emails = new ArrayList<>();
+ for (final ParticipantData participant : mParticipantData) {
+ if (!participant.isSelf()) {
+ if (participant.isEmail()) {
+ emails.add(participant.getSendDestination());
+ } else {
+ phones.add(participant.getSendDestination());
+ }
+ }
+ }
+
+ if (ContactUtil.hasReadContactsPermission()) {
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ final DataUsageStatUpdater updater = new DataUsageStatUpdater(
+ Factory.get().getApplicationContext());
+ try {
+ if (!phones.isEmpty()) {
+ updater.updateWithPhoneNumber(phones);
+ }
+ if (!emails.isEmpty()) {
+ updater.updateWithAddress(emails);
+ }
+ } catch (final SQLiteFullException ex) {
+ LogUtil.w(TAG, "Unable to update contact", ex);
+ }
+ }
+ });
+ }
+ }
+
+ public void downloadMessage(final BindingBase<ConversationData> binding,
+ final String messageId) {
+ Assert.isTrue(binding.getData() == this);
+ Assert.notNull(messageId);
+ RedownloadMmsAction.redownloadMessage(messageId);
+ }
+
+ public void resendMessage(final BindingBase<ConversationData> binding, final String messageId) {
+ Assert.isTrue(binding.getData() == this);
+ Assert.notNull(messageId);
+ ResendMessageAction.resendMessage(messageId);
+ }
+
+ public void deleteMessage(final BindingBase<ConversationData> binding, final String messageId) {
+ Assert.isTrue(binding.getData() == this);
+ Assert.notNull(messageId);
+ DeleteMessageAction.deleteMessage(messageId);
+ }
+
+ public void deleteConversation(final Binding<ConversationData> binding) {
+ Assert.isTrue(binding.getData() == this);
+ // If possible use timestamp of last message shown to delete only messages user is aware of
+ if (mConversationMetadata == null) {
+ DeleteConversationAction.deleteConversation(mConversationId,
+ System.currentTimeMillis());
+ } else {
+ mConversationMetadata.deleteConversation();
+ }
+ }
+
+ public void archiveConversation(final BindingBase<ConversationData> binding) {
+ Assert.isTrue(binding.getData() == this);
+ UpdateConversationArchiveStatusAction.archiveConversation(mConversationId);
+ }
+
+ public void unarchiveConversation(final BindingBase<ConversationData> binding) {
+ Assert.isTrue(binding.getData() == this);
+ UpdateConversationArchiveStatusAction.unarchiveConversation(mConversationId);
+ }
+
+ public ConversationParticipantsData getParticipants() {
+ return mParticipantData;
+ }
+
+ /**
+ * Returns a dialable phone number for the participant if we are in a 1-1 conversation.
+ * @return the participant phone number, or null if the phone number is not valid or if there
+ * are more than one participant.
+ */
+ public String getParticipantPhoneNumber() {
+ final ParticipantData participant = this.getOtherParticipant();
+ if (participant != null) {
+ final String phoneNumber = participant.getSendDestination();
+ if (!TextUtils.isEmpty(phoneNumber) && MmsSmsUtils.isPhoneNumber(phoneNumber)) {
+ return phoneNumber;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create a message to be forwarded from an existing message.
+ */
+ public MessageData createForwardedMessage(final ConversationMessageData message) {
+ final MessageData forwardedMessage = new MessageData();
+
+ final String originalSubject =
+ MmsUtils.cleanseMmsSubject(mContext.getResources(), message.getMmsSubject());
+ if (!TextUtils.isEmpty(originalSubject)) {
+ forwardedMessage.setMmsSubject(
+ mContext.getResources().getString(R.string.message_fwd, originalSubject));
+ }
+
+ for (final MessagePartData part : message.getParts()) {
+ MessagePartData forwardedPart;
+
+ // Depending on the part type, if it is text, we can directly create a text part;
+ // if it is attachment, then we need to create a pending attachment data out of it, so
+ // that we may persist the attachment locally in the scratch folder when the user picks
+ // a conversation to forward to.
+ if (part.isText()) {
+ forwardedPart = MessagePartData.createTextMessagePart(part.getText());
+ } else {
+ final PendingAttachmentData pendingAttachmentData = PendingAttachmentData
+ .createPendingAttachmentData(part.getContentType(), part.getContentUri());
+ forwardedPart = pendingAttachmentData;
+ }
+ forwardedMessage.addPart(forwardedPart);
+ }
+ return forwardedMessage;
+ }
+
+ public int getNumberOfParticipantsExcludingSelf() {
+ return mParticipantData.getNumberOfParticipantsExcludingSelf();
+ }
+
+ /**
+ * Returns {@link com.android.messaging.datamodel.data.SubscriptionListData
+ * .SubscriptionListEntry} for a given self participant so UI can display SIM-related info
+ * (icon, name etc.) for multi-SIM.
+ */
+ public SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
+ final String selfParticipantId, final boolean excludeDefault) {
+ return getSubscriptionEntryForSelfParticipant(selfParticipantId, excludeDefault,
+ mSubscriptionListData, mSelfParticipantsData);
+ }
+
+ /**
+ * Returns {@link com.android.messaging.datamodel.data.SubscriptionListData
+ * .SubscriptionListEntry} for a given self participant so UI can display SIM-related info
+ * (icon, name etc.) for multi-SIM.
+ */
+ public static SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
+ final String selfParticipantId, final boolean excludeDefault,
+ final SubscriptionListData subscriptionListData,
+ final SelfParticipantsData selfParticipantsData) {
+ // SIM indicators are shown in the UI only if:
+ // 1. Framework has MSIM support AND
+ // 2. The device has had multiple *active* subscriptions. AND
+ // 3. The message's subscription is active.
+ if (OsUtil.isAtLeastL_MR1() &&
+ selfParticipantsData.getSelfParticipantsCountExcludingDefault(true) > 1) {
+ return subscriptionListData.getActiveSubscriptionEntryBySelfId(selfParticipantId,
+ excludeDefault);
+ }
+ return null;
+ }
+
+ public SubscriptionListData getSubscriptionListData() {
+ return mSubscriptionListData;
+ }
+
+ /**
+ * A dummy implementation of {@link ConversationDataListener} so that subclasses may opt to
+ * implement some, but not all, of the interface methods.
+ */
+ public static class SimpleConversationDataListener implements ConversationDataListener {
+
+ @Override
+ public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor,
+ @Nullable
+ final
+ ConversationMessageData newestMessage, final boolean isSync) {}
+
+ @Override
+ public void onConversationMetadataUpdated(final ConversationData data) {}
+
+ @Override
+ public void closeConversation(final String conversationId) {}
+
+ @Override
+ public void onConversationParticipantDataLoaded(final ConversationData data) {}
+
+ @Override
+ public void onSubscriptionListDataLoaded(final ConversationData data) {}
+
+ }
+
+ private class ConversationDataEventDispatcher
+ extends ArrayList<ConversationDataListener>
+ implements ConversationDataListener {
+
+ @Override
+ public void onConversationMessagesCursorUpdated(final ConversationData data, final Cursor cursor,
+ @Nullable
+ final ConversationMessageData newestMessage, final boolean isSync) {
+ for (final ConversationDataListener listener : this) {
+ listener.onConversationMessagesCursorUpdated(data, cursor, newestMessage, isSync);
+ }
+ }
+
+ @Override
+ public void onConversationMetadataUpdated(final ConversationData data) {
+ for (final ConversationDataListener listener : this) {
+ listener.onConversationMetadataUpdated(data);
+ }
+ }
+
+ @Override
+ public void closeConversation(final String conversationId) {
+ for (final ConversationDataListener listener : this) {
+ listener.closeConversation(conversationId);
+ }
+ }
+
+ @Override
+ public void onConversationParticipantDataLoaded(final ConversationData data) {
+ for (final ConversationDataListener listener : this) {
+ listener.onConversationParticipantDataLoaded(data);
+ }
+ }
+
+ @Override
+ public void onSubscriptionListDataLoaded(final ConversationData data) {
+ for (final ConversationDataListener listener : this) {
+ listener.onSubscriptionListDataLoaded(data);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationListData.java b/src/com/android/messaging/datamodel/data/ConversationListData.java
new file mode 100644
index 0000000..3d27ecd
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ConversationListData.java
@@ -0,0 +1,211 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+
+import com.android.messaging.datamodel.BoundCursorLoader;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
+import com.android.messaging.receiver.SmsReceiver;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.util.HashSet;
+
+public class ConversationListData extends BindableData
+ implements LoaderManager.LoaderCallbacks<Cursor> {
+
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final String BINDING_ID = "bindingId";
+ public static final String SORT_ORDER =
+ ConversationListViewColumns.SORT_TIMESTAMP + " DESC";
+
+ private static final String WHERE_ARCHIVED =
+ "(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 1)";
+ public static final String WHERE_NOT_ARCHIVED =
+ "(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 0)";
+
+ public interface ConversationListDataListener {
+ public void onConversationListCursorUpdated(ConversationListData data, Cursor cursor);
+ public void setBlockedParticipantsAvailable(boolean blockedAvailable);
+ }
+
+ private ConversationListDataListener mListener;
+ private final Context mContext;
+ private final boolean mArchivedMode;
+ private LoaderManager mLoaderManager;
+
+ public ConversationListData(final Context context, final ConversationListDataListener listener,
+ final boolean archivedMode) {
+ mListener = listener;
+ mContext = context;
+ mArchivedMode = archivedMode;
+ }
+
+ private static final int CONVERSATION_LIST_LOADER = 1;
+ private static final int BLOCKED_PARTICIPANTS_AVAILABLE_LOADER = 2;
+
+ private static final String[] BLOCKED_PARTICIPANTS_PROJECTION = new String[] {
+ ParticipantColumns._ID,
+ ParticipantColumns.NORMALIZED_DESTINATION,
+ };
+ private static final int INDEX_BLOCKED_PARTICIPANTS_ID = 0;
+ private static final int INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION = 1;
+
+ // all blocked participants
+ private final HashSet<String> mBlockedParticipants = new HashSet<String>();
+
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ final String bindingId = args.getString(BINDING_ID);
+ Loader<Cursor> loader = null;
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ switch (id) {
+ case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
+ loader = new BoundCursorLoader(bindingId, mContext,
+ MessagingContentProvider.PARTICIPANTS_URI,
+ BLOCKED_PARTICIPANTS_PROJECTION,
+ ParticipantColumns.BLOCKED + "=1", null, null);
+ break;
+ case CONVERSATION_LIST_LOADER:
+ loader = new BoundCursorLoader(bindingId, mContext,
+ MessagingContentProvider.CONVERSATIONS_URI,
+ ConversationListItemData.PROJECTION,
+ mArchivedMode ? WHERE_ARCHIVED : WHERE_NOT_ARCHIVED,
+ null, // selection args
+ SORT_ORDER);
+ break;
+ default:
+ Assert.fail("Unknown loader id");
+ break;
+ }
+ } else {
+ LogUtil.w(TAG, "Creating loader after unbinding list");
+ }
+ return loader;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+ if (isBound(loader.getBindingId())) {
+ switch (loader.getId()) {
+ case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
+ mBlockedParticipants.clear();
+ for (int i = 0; i < data.getCount(); i++) {
+ data.moveToPosition(i);
+ mBlockedParticipants.add(data.getString(
+ INDEX_BLOCKED_PARTICIPANTS_NORMALIZED_DESTINATION));
+ }
+ mListener.setBlockedParticipantsAvailable(data != null && data.getCount() > 0);
+ break;
+ case CONVERSATION_LIST_LOADER:
+ mListener.onConversationListCursorUpdated(this, data);
+ break;
+ default:
+ Assert.fail("Unknown loader id");
+ break;
+ }
+ } else {
+ LogUtil.w(TAG, "Loader finished after unbinding list");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<Cursor> generic) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+ if (isBound(loader.getBindingId())) {
+ switch (loader.getId()) {
+ case BLOCKED_PARTICIPANTS_AVAILABLE_LOADER:
+ mListener.setBlockedParticipantsAvailable(false);
+ break;
+ case CONVERSATION_LIST_LOADER:
+ mListener.onConversationListCursorUpdated(this, null);
+ break;
+ default:
+ Assert.fail("Unknown loader id");
+ break;
+ }
+ } else {
+ LogUtil.w(TAG, "Loader reset after unbinding list");
+ }
+ }
+
+ private Bundle mArgs;
+
+ public void init(final LoaderManager loaderManager,
+ final BindingBase<ConversationListData> binding) {
+ mArgs = new Bundle();
+ mArgs.putString(BINDING_ID, binding.getBindingId());
+ mLoaderManager = loaderManager;
+ mLoaderManager.initLoader(CONVERSATION_LIST_LOADER, mArgs, this);
+ mLoaderManager.initLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER, mArgs, this);
+ }
+
+ public void handleMessagesSeen() {
+ BugleNotifications.markAllMessagesAsSeen();
+
+ SmsReceiver.cancelSecondaryUserNotification();
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+
+ // This could be null if we bind but the caller doesn't init the BindableData
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(CONVERSATION_LIST_LOADER);
+ mLoaderManager.destroyLoader(BLOCKED_PARTICIPANTS_AVAILABLE_LOADER);
+ mLoaderManager = null;
+ }
+ }
+
+ public boolean getHasFirstSyncCompleted() {
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ return syncManager.getHasFirstSyncCompleted();
+ }
+
+ public void setScrolledToNewestConversation(boolean scrolledToNewestConversation) {
+ DataModel.get().setConversationListScrolledToNewestConversation(
+ scrolledToNewestConversation);
+ if (scrolledToNewestConversation) {
+ handleMessagesSeen();
+ }
+ }
+
+ public HashSet<String> getBlockedParticipants() {
+ return mBlockedParticipants;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationListItemData.java b/src/com/android/messaging/datamodel/data/ConversationListItemData.java
new file mode 100644
index 0000000..b2e6e1c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ConversationListItemData.java
@@ -0,0 +1,510 @@
+/*
+ * 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.datamodel.data;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+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.action.DeleteConversationAction;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Dates;
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class wrapping the conversation list view used to display each item in conversation list
+ */
+public class ConversationListItemData {
+ private String mConversationId;
+ private String mName;
+ private String mIcon;
+ private boolean mIsRead;
+ private long mTimestamp;
+ private String mSnippetText;
+ private Uri mPreviewUri;
+ private String mPreviewContentType;
+ private long mParticipantContactId;
+ private String mParticipantLookupKey;
+ private String mOtherParticipantNormalizedDestination;
+ private String mSelfId;
+ private int mParticipantCount;
+ private boolean mNotificationEnabled;
+ private String mNotificationSoundUri;
+ private boolean mNotificationVibrate;
+ private boolean mIncludeEmailAddress;
+ private int mMessageStatus;
+ private int mMessageRawTelephonyStatus;
+ private boolean mShowDraft;
+ private Uri mDraftPreviewUri;
+ private String mDraftPreviewContentType;
+ private String mDraftSnippetText;
+ private boolean mIsArchived;
+ private String mSubject;
+ private String mDraftSubject;
+ private String mSnippetSenderFirstName;
+ private String mSnippetSenderDisplayDestination;
+
+ public ConversationListItemData() {
+ }
+
+ public void bind(final Cursor cursor) {
+ bind(cursor, false);
+ }
+
+ public void bind(final Cursor cursor, final boolean ignoreDraft) {
+ mConversationId = cursor.getString(INDEX_ID);
+ mName = cursor.getString(INDEX_CONVERSATION_NAME);
+ mIcon = cursor.getString(INDEX_CONVERSATION_ICON);
+ mSnippetText = cursor.getString(INDEX_SNIPPET_TEXT);
+ mTimestamp = cursor.getLong(INDEX_SORT_TIMESTAMP);
+ mIsRead = cursor.getInt(INDEX_READ) == 1;
+ final String previewUriString = cursor.getString(INDEX_PREVIEW_URI);
+ mPreviewUri = TextUtils.isEmpty(previewUriString) ? null : Uri.parse(previewUriString);
+ mPreviewContentType = cursor.getString(INDEX_PREVIEW_CONTENT_TYPE);
+ mParticipantContactId = cursor.getLong(INDEX_PARTICIPANT_CONTACT_ID);
+ mParticipantLookupKey = cursor.getString(INDEX_PARTICIPANT_LOOKUP_KEY);
+ mOtherParticipantNormalizedDestination = cursor.getString(
+ INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION);
+ mSelfId = cursor.getString(INDEX_SELF_ID);
+ mParticipantCount = cursor.getInt(INDEX_PARTICIPANT_COUNT);
+ mNotificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
+ mNotificationSoundUri = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
+ mNotificationVibrate = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
+ mIncludeEmailAddress = cursor.getInt(INDEX_INCLUDE_EMAIL_ADDRESS) == 1;
+ mMessageStatus = cursor.getInt(INDEX_MESSAGE_STATUS);
+ mMessageRawTelephonyStatus = cursor.getInt(INDEX_MESSAGE_RAW_TELEPHONY_STATUS);
+ if (!ignoreDraft) {
+ mShowDraft = cursor.getInt(INDEX_SHOW_DRAFT) == 1;
+ final String draftPreviewUriString = cursor.getString(INDEX_DRAFT_PREVIEW_URI);
+ mDraftPreviewUri = TextUtils.isEmpty(draftPreviewUriString) ?
+ null : Uri.parse(draftPreviewUriString);
+ mDraftPreviewContentType = cursor.getString(INDEX_DRAFT_PREVIEW_CONTENT_TYPE);
+ mDraftSnippetText = cursor.getString(INDEX_DRAFT_SNIPPET_TEXT);
+ mDraftSubject = cursor.getString(INDEX_DRAFT_SUBJECT_TEXT);
+ } else {
+ mShowDraft = false;
+ mDraftPreviewUri = null;
+ mDraftPreviewContentType = null;
+ mDraftSnippetText = null;
+ mDraftSubject = null;
+ }
+
+ mIsArchived = cursor.getInt(INDEX_ARCHIVE_STATUS) == 1;
+ mSubject = cursor.getString(INDEX_SUBJECT_TEXT);
+ mSnippetSenderFirstName = cursor.getString(INDEX_SNIPPET_SENDER_FIRST_NAME);
+ mSnippetSenderDisplayDestination =
+ cursor.getString(INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION);
+ }
+
+ public String getConversationId() {
+ return mConversationId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getIcon() {
+ return mIcon;
+ }
+
+ public boolean getIsRead() {
+ return mIsRead;
+ }
+
+ public String getFormattedTimestamp() {
+ return Dates.getConversationTimeString(mTimestamp).toString();
+ }
+
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ public String getSnippetText() {
+ return mSnippetText;
+ }
+
+ public Uri getPreviewUri() {
+ return mPreviewUri;
+ }
+
+ public String getPreviewContentType() {
+ return mPreviewContentType;
+ }
+
+ public long getParticipantContactId() {
+ return mParticipantContactId;
+ }
+
+ public String getParticipantLookupKey() {
+ return mParticipantLookupKey;
+ }
+
+ public String getOtherParticipantNormalizedDestination() {
+ return mOtherParticipantNormalizedDestination;
+ }
+
+ public String getSelfId() {
+ return mSelfId;
+ }
+
+ public int getParticipantCount() {
+ return mParticipantCount;
+ }
+
+ public boolean getIsGroup() {
+ // Participant count excludes self
+ return (mParticipantCount > 1);
+ }
+
+ public boolean getIncludeEmailAddress() {
+ return mIncludeEmailAddress;
+ }
+
+ public boolean getNotificationEnabled() {
+ return mNotificationEnabled;
+ }
+
+ public String getNotificationSoundUri() {
+ return mNotificationSoundUri;
+ }
+
+ public boolean getNotifiationVibrate() {
+ return mNotificationVibrate;
+ }
+
+ public final boolean getIsFailedStatus() {
+ return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED ||
+ mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER ||
+ mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
+ mMessageStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE);
+ }
+
+ public final boolean getIsSendRequested() {
+ return (mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
+ mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY ||
+ mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_SENDING ||
+ mMessageStatus == MessageData.BUGLE_STATUS_OUTGOING_RESENDING);
+ }
+
+ public boolean getIsMessageTypeOutgoing() {
+ return !MessageData.getIsIncoming(mMessageStatus);
+ }
+
+ public int getMessageRawTelephonyStatus() {
+ return mMessageRawTelephonyStatus;
+ }
+
+ public int getMessageStatus() {
+ return mMessageStatus;
+ }
+
+ public boolean getShowDraft() {
+ return mShowDraft;
+ }
+
+ public String getDraftSnippetText() {
+ return mDraftSnippetText;
+ }
+
+ public Uri getDraftPreviewUri() {
+ return mDraftPreviewUri;
+ }
+
+ public String getDraftPreviewContentType() {
+ return mDraftPreviewContentType;
+ }
+
+ public boolean getIsArchived() {
+ return mIsArchived;
+ }
+
+ public String getSubject() {
+ return mSubject;
+ }
+
+ public String getDraftSubject() {
+ return mDraftSubject;
+ }
+
+ public String getSnippetSenderName() {
+ if (!TextUtils.isEmpty(mSnippetSenderFirstName)) {
+ return mSnippetSenderFirstName;
+ }
+ return mSnippetSenderDisplayDestination;
+ }
+
+ public void deleteConversation() {
+ DeleteConversationAction.deleteConversation(mConversationId, mTimestamp);
+ }
+
+ /**
+ * Get the name of the view for this data item
+ */
+ public static final String getConversationListView() {
+ return CONVERSATION_LIST_VIEW;
+ }
+
+ public static final String getConversationListViewSql() {
+ return CONVERSATION_LIST_VIEW_SQL;
+ }
+
+ private static final String CONVERSATION_LIST_VIEW = "conversation_list_view";
+
+ private static final String CONVERSATION_LIST_VIEW_PROJECTION =
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns._ID
+ + " as " + ConversationListViewColumns._ID + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NAME
+ + " as " + ConversationListViewColumns.NAME + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.CURRENT_SELF_ID
+ + " as " + ConversationListViewColumns.CURRENT_SELF_ID + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ARCHIVE_STATUS
+ + " as " + ConversationListViewColumns.ARCHIVE_STATUS + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
+ + " as " + ConversationListViewColumns.READ + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ICON
+ + " as " + ConversationListViewColumns.ICON + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_CONTACT_ID
+ + " as " + ConversationListViewColumns.PARTICIPANT_CONTACT_ID + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_LOOKUP_KEY
+ + " as " + ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.'
+ + ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION
+ + " as " + ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SORT_TIMESTAMP
+ + " as " + ConversationListViewColumns.SORT_TIMESTAMP + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SHOW_DRAFT
+ + " as " + ConversationListViewColumns.SHOW_DRAFT + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SNIPPET_TEXT
+ + " as " + ConversationListViewColumns.DRAFT_SNIPPET_TEXT + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_PREVIEW_URI
+ + " as " + ConversationListViewColumns.DRAFT_PREVIEW_URI + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.DRAFT_SUBJECT_TEXT
+ + " as " + ConversationListViewColumns.DRAFT_SUBJECT_TEXT + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.'
+ + ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE
+ + " as " + ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_URI
+ + " as " + ConversationListViewColumns.PREVIEW_URI + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PREVIEW_CONTENT_TYPE
+ + " as " + ConversationListViewColumns.PREVIEW_CONTENT_TYPE + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.PARTICIPANT_COUNT
+ + " as " + ConversationListViewColumns.PARTICIPANT_COUNT + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_ENABLED
+ + " as " + ConversationListViewColumns.NOTIFICATION_ENABLED + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_SOUND_URI
+ + " as " + ConversationListViewColumns.NOTIFICATION_SOUND_URI + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.NOTIFICATION_VIBRATION
+ + " as " + ConversationListViewColumns.NOTIFICATION_VIBRATION + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' +
+ ConversationColumns.INCLUDE_EMAIL_ADDRESS
+ + " as " + ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
+ + " as " + ConversationListViewColumns.MESSAGE_STATUS + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
+ + " as " + ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
+ + " as " + ConversationListViewColumns.MESSAGE_ID + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
+ + " as " + ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
+ + " as " + ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION;
+
+ private static final String JOIN_PARTICIPANTS =
+ " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE + " ON ("
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
+ + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + DatabaseHelper.ParticipantColumns._ID
+ + ") ";
+
+ // View that makes latest message read flag available with rest of conversation data.
+ private static final String CONVERSATION_LIST_VIEW_SQL = "CREATE VIEW " +
+ CONVERSATION_LIST_VIEW + " AS SELECT "
+ + CONVERSATION_LIST_VIEW_PROJECTION + ", "
+ // Snippet not part of the base projection shared with search view
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SNIPPET_TEXT
+ + " as " + ConversationListViewColumns.SNIPPET_TEXT + ", "
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.SUBJECT_TEXT
+ + " as " + ConversationListViewColumns.SUBJECT_TEXT + " "
+ + " FROM " + DatabaseHelper.CONVERSATIONS_TABLE
+ + " LEFT JOIN " + DatabaseHelper.MESSAGES_TABLE + " ON ("
+ + DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.LATEST_MESSAGE_ID
+ + '=' + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID + ") "
+ + JOIN_PARTICIPANTS
+ + "ORDER BY " + DatabaseHelper.CONVERSATIONS_TABLE + '.'
+ + ConversationColumns.SORT_TIMESTAMP + " DESC";
+
+ public static class ConversationListViewColumns implements BaseColumns {
+ public static final String _ID = ConversationColumns._ID;
+ static final String NAME = ConversationColumns.NAME;
+ static final String ARCHIVE_STATUS = ConversationColumns.ARCHIVE_STATUS;
+ static final String READ = MessageColumns.READ;
+ static final String SORT_TIMESTAMP = ConversationColumns.SORT_TIMESTAMP;
+ static final String PREVIEW_URI = ConversationColumns.PREVIEW_URI;
+ static final String PREVIEW_CONTENT_TYPE = ConversationColumns.PREVIEW_CONTENT_TYPE;
+ static final String SNIPPET_TEXT = ConversationColumns.SNIPPET_TEXT;
+ static final String SUBJECT_TEXT = ConversationColumns.SUBJECT_TEXT;
+ static final String ICON = ConversationColumns.ICON;
+ static final String SHOW_DRAFT = ConversationColumns.SHOW_DRAFT;
+ static final String DRAFT_SUBJECT_TEXT = ConversationColumns.DRAFT_SUBJECT_TEXT;
+ static final String DRAFT_PREVIEW_URI = ConversationColumns.DRAFT_PREVIEW_URI;
+ static final String DRAFT_PREVIEW_CONTENT_TYPE =
+ ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE;
+ static final String DRAFT_SNIPPET_TEXT = ConversationColumns.DRAFT_SNIPPET_TEXT;
+ static final String PARTICIPANT_CONTACT_ID = ConversationColumns.PARTICIPANT_CONTACT_ID;
+ static final String PARTICIPANT_LOOKUP_KEY = ConversationColumns.PARTICIPANT_LOOKUP_KEY;
+ static final String OTHER_PARTICIPANT_NORMALIZED_DESTINATION =
+ ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION;
+ static final String CURRENT_SELF_ID = ConversationColumns.CURRENT_SELF_ID;
+ static final String PARTICIPANT_COUNT = ConversationColumns.PARTICIPANT_COUNT;
+ static final String NOTIFICATION_ENABLED = ConversationColumns.NOTIFICATION_ENABLED;
+ static final String NOTIFICATION_SOUND_URI = ConversationColumns.NOTIFICATION_SOUND_URI;
+ static final String NOTIFICATION_VIBRATION = ConversationColumns.NOTIFICATION_VIBRATION;
+ static final String INCLUDE_EMAIL_ADDRESS =
+ ConversationColumns.INCLUDE_EMAIL_ADDRESS;
+ static final String MESSAGE_STATUS = MessageColumns.STATUS;
+ static final String MESSAGE_RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
+ static final String MESSAGE_ID = "message_id";
+ static final String SNIPPET_SENDER_FIRST_NAME = "snippet_sender_first_name";
+ static final String SNIPPET_SENDER_DISPLAY_DESTINATION =
+ "snippet_sender_display_destination";
+ }
+
+ public static final String[] PROJECTION = {
+ ConversationListViewColumns._ID,
+ ConversationListViewColumns.NAME,
+ ConversationListViewColumns.ICON,
+ ConversationListViewColumns.SNIPPET_TEXT,
+ ConversationListViewColumns.SORT_TIMESTAMP,
+ ConversationListViewColumns.READ,
+ ConversationListViewColumns.PREVIEW_URI,
+ ConversationListViewColumns.PREVIEW_CONTENT_TYPE,
+ ConversationListViewColumns.PARTICIPANT_CONTACT_ID,
+ ConversationListViewColumns.PARTICIPANT_LOOKUP_KEY,
+ ConversationListViewColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION,
+ ConversationListViewColumns.PARTICIPANT_COUNT,
+ ConversationListViewColumns.CURRENT_SELF_ID,
+ ConversationListViewColumns.NOTIFICATION_ENABLED,
+ ConversationListViewColumns.NOTIFICATION_SOUND_URI,
+ ConversationListViewColumns.NOTIFICATION_VIBRATION,
+ ConversationListViewColumns.INCLUDE_EMAIL_ADDRESS,
+ ConversationListViewColumns.MESSAGE_STATUS,
+ ConversationListViewColumns.SHOW_DRAFT,
+ ConversationListViewColumns.DRAFT_PREVIEW_URI,
+ ConversationListViewColumns.DRAFT_PREVIEW_CONTENT_TYPE,
+ ConversationListViewColumns.DRAFT_SNIPPET_TEXT,
+ ConversationListViewColumns.ARCHIVE_STATUS,
+ ConversationListViewColumns.MESSAGE_ID,
+ ConversationListViewColumns.SUBJECT_TEXT,
+ ConversationListViewColumns.DRAFT_SUBJECT_TEXT,
+ ConversationListViewColumns.MESSAGE_RAW_TELEPHONY_STATUS,
+ ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME,
+ ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION,
+ };
+
+ private static final int INDEX_ID = 0;
+ private static final int INDEX_CONVERSATION_NAME = 1;
+ private static final int INDEX_CONVERSATION_ICON = 2;
+ private static final int INDEX_SNIPPET_TEXT = 3;
+ private static final int INDEX_SORT_TIMESTAMP = 4;
+ private static final int INDEX_READ = 5;
+ private static final int INDEX_PREVIEW_URI = 6;
+ private static final int INDEX_PREVIEW_CONTENT_TYPE = 7;
+ private static final int INDEX_PARTICIPANT_CONTACT_ID = 8;
+ private static final int INDEX_PARTICIPANT_LOOKUP_KEY = 9;
+ private static final int INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION = 10;
+ private static final int INDEX_PARTICIPANT_COUNT = 11;
+ private static final int INDEX_SELF_ID = 12;
+ private static final int INDEX_NOTIFICATION_ENABLED = 13;
+ private static final int INDEX_NOTIFICATION_SOUND_URI = 14;
+ private static final int INDEX_NOTIFICATION_VIBRATION = 15;
+ private static final int INDEX_INCLUDE_EMAIL_ADDRESS = 16;
+ private static final int INDEX_MESSAGE_STATUS = 17;
+ private static final int INDEX_SHOW_DRAFT = 18;
+ private static final int INDEX_DRAFT_PREVIEW_URI = 19;
+ private static final int INDEX_DRAFT_PREVIEW_CONTENT_TYPE = 20;
+ private static final int INDEX_DRAFT_SNIPPET_TEXT = 21;
+ private static final int INDEX_ARCHIVE_STATUS = 22;
+ private static final int INDEX_MESSAGE_ID = 23;
+ private static final int INDEX_SUBJECT_TEXT = 24;
+ private static final int INDEX_DRAFT_SUBJECT_TEXT = 25;
+ private static final int INDEX_MESSAGE_RAW_TELEPHONY_STATUS = 26;
+ private static final int INDEX_SNIPPET_SENDER_FIRST_NAME = 27;
+ private static final int INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION = 28;
+
+ private static final String DIVIDER_TEXT = ", ";
+
+ /**
+ * Get a conversation from the local DB based on the conversation id.
+ *
+ * @param dbWrapper The database
+ * @param conversationId The conversation Id to read
+ * @return The existing conversation or null
+ */
+ public static ConversationListItemData getExistingConversation(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ ConversationListItemData conversation = null;
+
+ // Look for an existing conversation in the db with this conversation id
+ Cursor cursor = null;
+ try {
+ // TODO: Should we be able to read a row from just the conversation table?
+ cursor = dbWrapper.query(getConversationListView(),
+ PROJECTION,
+ ConversationColumns._ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ Assert.inRange(cursor.getCount(), 0, 1);
+ if (cursor.moveToFirst()) {
+ conversation = new ConversationListItemData();
+ conversation.bind(cursor);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return conversation;
+ }
+
+ public static String generateConversationName(final List<ParticipantData>
+ participants) {
+ if (participants.size() == 1) {
+ // Prefer full name over first name for 1:1 conversation
+ return participants.get(0).getDisplayName(true);
+ }
+
+ final ArrayList<String> participantNames = new ArrayList<String>();
+ for (final ParticipantData participant : participants) {
+ // Prefer first name over full name for group conversation
+ participantNames.add(participant.getDisplayName(false));
+ }
+
+ final Joiner joiner = Joiner.on(DIVIDER_TEXT).skipNulls();
+ return joiner.join(participantNames);
+ }
+
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java b/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java
new file mode 100644
index 0000000..f329f46
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ConversationMessageBubbleData.java
@@ -0,0 +1,37 @@
+/*
+ * 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.datamodel.data;
+
+import android.text.TextUtils;
+
+/**
+ * Holds data for conversation message bubble which keeps track of whether it's been bound to
+ * a new message.
+ */
+public class ConversationMessageBubbleData {
+ private String mMessageId;
+
+ /**
+ * Binds to ConversationMessageData instance.
+ * @return true if we are binding to a different message, false if we are binding to the
+ * same message (e.g. in order to update the status text)
+ */
+ public boolean bind(final ConversationMessageData data) {
+ final boolean changed = !TextUtils.equals(mMessageId, data.getMessageId());
+ mMessageId = data.getMessageId();
+ return changed;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageData.java b/src/com/android/messaging/datamodel/data/ConversationMessageData.java
new file mode 100644
index 0000000..19e1b97
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ConversationMessageData.java
@@ -0,0 +1,917 @@
+/*
+ * 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.datamodel.data;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Predicate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Class representing a message within a conversation sequence. The message parts
+ * are available via the getParts() method.
+ *
+ * TODO: See if we can delegate to MessageData for the logic that this class duplicates
+ * (e.g. getIsMms).
+ */
+public class ConversationMessageData {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private String mMessageId;
+ private String mConversationId;
+ private String mParticipantId;
+ private int mPartsCount;
+ private List<MessagePartData> mParts;
+ private long mSentTimestamp;
+ private long mReceivedTimestamp;
+ private boolean mSeen;
+ private boolean mRead;
+ private int mProtocol;
+ private int mStatus;
+ private String mSmsMessageUri;
+ private int mSmsPriority;
+ private int mSmsMessageSize;
+ private String mMmsSubject;
+ private long mMmsExpiry;
+ private int mRawTelephonyStatus;
+ private String mSenderFullName;
+ private String mSenderFirstName;
+ private String mSenderDisplayDestination;
+ private String mSenderNormalizedDestination;
+ private String mSenderProfilePhotoUri;
+ private long mSenderContactId;
+ private String mSenderContactLookupKey;
+ private String mSelfParticipantId;
+
+ /** Are we similar enough to the previous/next messages that we can cluster them? */
+ private boolean mCanClusterWithPreviousMessage;
+ private boolean mCanClusterWithNextMessage;
+
+ public ConversationMessageData() {
+ }
+
+ public void bind(final Cursor cursor) {
+ mMessageId = cursor.getString(INDEX_MESSAGE_ID);
+ mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
+ mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
+ mPartsCount = cursor.getInt(INDEX_PARTS_COUNT);
+
+ mParts = makeParts(
+ cursor.getString(INDEX_PARTS_IDS),
+ cursor.getString(INDEX_PARTS_CONTENT_TYPES),
+ cursor.getString(INDEX_PARTS_CONTENT_URIS),
+ cursor.getString(INDEX_PARTS_WIDTHS),
+ cursor.getString(INDEX_PARTS_HEIGHTS),
+ cursor.getString(INDEX_PARTS_TEXTS),
+ mPartsCount,
+ mMessageId);
+
+ mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
+ mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
+ mSeen = (cursor.getInt(INDEX_SEEN) != 0);
+ mRead = (cursor.getInt(INDEX_READ) != 0);
+ mProtocol = cursor.getInt(INDEX_PROTOCOL);
+ mStatus = cursor.getInt(INDEX_STATUS);
+ mSmsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
+ mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
+ mSmsMessageSize = cursor.getInt(INDEX_SMS_MESSAGE_SIZE);
+ mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
+ mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
+ mRawTelephonyStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
+ mSenderFullName = cursor.getString(INDEX_SENDER_FULL_NAME);
+ mSenderFirstName = cursor.getString(INDEX_SENDER_FIRST_NAME);
+ mSenderDisplayDestination = cursor.getString(INDEX_SENDER_DISPLAY_DESTINATION);
+ mSenderNormalizedDestination = cursor.getString(INDEX_SENDER_NORMALIZED_DESTINATION);
+ mSenderProfilePhotoUri = cursor.getString(INDEX_SENDER_PROFILE_PHOTO_URI);
+ mSenderContactId = cursor.getLong(INDEX_SENDER_CONTACT_ID);
+ mSenderContactLookupKey = cursor.getString(INDEX_SENDER_CONTACT_LOOKUP_KEY);
+ mSelfParticipantId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
+
+ if (!cursor.isFirst() && cursor.moveToPrevious()) {
+ mCanClusterWithPreviousMessage = canClusterWithMessage(cursor);
+ cursor.moveToNext();
+ } else {
+ mCanClusterWithPreviousMessage = false;
+ }
+ if (!cursor.isLast() && cursor.moveToNext()) {
+ mCanClusterWithNextMessage = canClusterWithMessage(cursor);
+ cursor.moveToPrevious();
+ } else {
+ mCanClusterWithNextMessage = false;
+ }
+ }
+
+ private boolean canClusterWithMessage(final Cursor cursor) {
+ final String otherParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
+ if (!TextUtils.equals(getParticipantId(), otherParticipantId)) {
+ return false;
+ }
+ final int otherStatus = cursor.getInt(INDEX_STATUS);
+ final boolean otherIsIncoming = (otherStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
+ if (getIsIncoming() != otherIsIncoming) {
+ return false;
+ }
+ final long otherReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
+ final long timestampDeltaMillis = Math.abs(mReceivedTimestamp - otherReceivedTimestamp);
+ if (timestampDeltaMillis > DateUtils.MINUTE_IN_MILLIS) {
+ return false;
+ }
+ final String otherSelfId = cursor.getString(INDEX_SELF_PARTICIPIANT_ID);
+ if (!TextUtils.equals(getSelfParticipantId(), otherSelfId)) {
+ return false;
+ }
+ return true;
+ }
+
+ private static final Character QUOTE_CHAR = '\'';
+ private static final char DIVIDER = '|';
+
+ // statics to avoid unnecessary object allocation
+ private static final StringBuilder sUnquoteStringBuilder = new StringBuilder();
+ private static final ArrayList<String> sUnquoteResults = new ArrayList<String>();
+
+ // this lock is used to guard access to the above statics
+ private static final Object sUnquoteLock = new Object();
+
+ private static void addResult(final ArrayList<String> results, final StringBuilder value) {
+ if (value.length() > 0) {
+ results.add(value.toString());
+ } else {
+ results.add(EMPTY_STRING);
+ }
+ }
+
+ @VisibleForTesting
+ static String[] splitUnquotedString(final String inputString) {
+ if (TextUtils.isEmpty(inputString)) {
+ return new String[0];
+ }
+
+ return inputString.split("\\" + DIVIDER);
+ }
+
+ /**
+ * Takes a group-concated and quoted string and decomposes it into its constituent
+ * parts. A quoted string starts and ends with a single quote. Actual single quotes
+ * within the string are escaped using a second single quote. So, for example, an
+ * input string with 3 constituent parts might look like this:
+ *
+ * 'now is the time'|'I can''t do it'|'foo'
+ *
+ * This would be returned as an array of 3 strings as follows:
+ * now is the time
+ * I can't do it
+ * foo
+ *
+ * This is achieved by walking through the inputString, character by character,
+ * ignoring the outer quotes and the divider and replacing any pair of consecutive
+ * single quotes with a single single quote.
+ *
+ * @param inputString
+ * @return array of constituent strings
+ */
+ @VisibleForTesting
+ static String[] splitQuotedString(final String inputString) {
+ if (TextUtils.isEmpty(inputString)) {
+ return new String[0];
+ }
+
+ // this method can be called from multiple threads but it uses a static
+ // string builder
+ synchronized (sUnquoteLock) {
+ final int length = inputString.length();
+ final ArrayList<String> results = sUnquoteResults;
+ results.clear();
+
+ int characterPos = -1;
+ while (++characterPos < length) {
+ final char mustBeQuote = inputString.charAt(characterPos);
+ Assert.isTrue(QUOTE_CHAR == mustBeQuote);
+ while (++characterPos < length) {
+ final char currentChar = inputString.charAt(characterPos);
+ if (currentChar == QUOTE_CHAR) {
+ final char peekAhead = characterPos < length - 1
+ ? inputString.charAt(characterPos + 1) : 0;
+
+ if (peekAhead == QUOTE_CHAR) {
+ characterPos += 1; // skip the second quote
+ } else {
+ addResult(results, sUnquoteStringBuilder);
+ sUnquoteStringBuilder.setLength(0);
+
+ Assert.isTrue((peekAhead == DIVIDER) || (peekAhead == (char) 0));
+ characterPos += 1; // skip the divider
+ break;
+ }
+ }
+ sUnquoteStringBuilder.append(currentChar);
+ }
+ }
+ return results.toArray(new String[results.size()]);
+ }
+ }
+
+ static MessagePartData makePartData(
+ final String partId,
+ final String contentType,
+ final String contentUriString,
+ final String contentWidth,
+ final String contentHeight,
+ final String text,
+ final String messageId) {
+ if (ContentType.isTextType(contentType)) {
+ final MessagePartData textPart = MessagePartData.createTextMessagePart(text);
+ textPart.updatePartId(partId);
+ textPart.updateMessageId(messageId);
+ return textPart;
+ } else {
+ final Uri contentUri = Uri.parse(contentUriString);
+ final int width = Integer.parseInt(contentWidth);
+ final int height = Integer.parseInt(contentHeight);
+ final MessagePartData attachmentPart = MessagePartData.createMediaMessagePart(
+ contentType, contentUri, width, height);
+ attachmentPart.updatePartId(partId);
+ attachmentPart.updateMessageId(messageId);
+ return attachmentPart;
+ }
+ }
+
+ @VisibleForTesting
+ static List<MessagePartData> makeParts(
+ final String rawIds,
+ final String rawContentTypes,
+ final String rawContentUris,
+ final String rawWidths,
+ final String rawHeights,
+ final String rawTexts,
+ final int partsCount,
+ final String messageId) {
+ final List<MessagePartData> parts = new LinkedList<MessagePartData>();
+ if (partsCount == 1) {
+ parts.add(makePartData(
+ rawIds,
+ rawContentTypes,
+ rawContentUris,
+ rawWidths,
+ rawHeights,
+ rawTexts,
+ messageId));
+ } else {
+ unpackMessageParts(
+ parts,
+ splitUnquotedString(rawIds),
+ splitQuotedString(rawContentTypes),
+ splitQuotedString(rawContentUris),
+ splitUnquotedString(rawWidths),
+ splitUnquotedString(rawHeights),
+ splitQuotedString(rawTexts),
+ partsCount,
+ messageId);
+ }
+ return parts;
+ }
+
+ @VisibleForTesting
+ static void unpackMessageParts(
+ final List<MessagePartData> parts,
+ final String[] ids,
+ final String[] contentTypes,
+ final String[] contentUris,
+ final String[] contentWidths,
+ final String[] contentHeights,
+ final String[] texts,
+ final int partsCount,
+ final String messageId) {
+
+ Assert.equals(partsCount, ids.length);
+ Assert.equals(partsCount, contentTypes.length);
+ Assert.equals(partsCount, contentUris.length);
+ Assert.equals(partsCount, contentWidths.length);
+ Assert.equals(partsCount, contentHeights.length);
+ Assert.equals(partsCount, texts.length);
+
+ for (int i = 0; i < partsCount; i++) {
+ parts.add(makePartData(
+ ids[i],
+ contentTypes[i],
+ contentUris[i],
+ contentWidths[i],
+ contentHeights[i],
+ texts[i],
+ messageId));
+ }
+
+ if (parts.size() != partsCount) {
+ LogUtil.wtf(TAG, "Only unpacked " + parts.size() + " parts from message (id="
+ + messageId + "), expected " + partsCount + " parts");
+ }
+ }
+
+ public final String getMessageId() {
+ return mMessageId;
+ }
+
+ public final String getConversationId() {
+ return mConversationId;
+ }
+
+ public final String getParticipantId() {
+ return mParticipantId;
+ }
+
+ public List<MessagePartData> getParts() {
+ return mParts;
+ }
+
+ public boolean hasText() {
+ for (final MessagePartData part : mParts) {
+ if (part.isText()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get a concatenation of all text parts
+ *
+ * @return the text that is a concatenation of all text parts
+ */
+ public String getText() {
+ // This is optimized for single text part case, which is the majority
+
+ // For single text part, we just return the part without creating the StringBuilder
+ String firstTextPart = null;
+ boolean foundText = false;
+ // For multiple text parts, we need the StringBuilder and the separator for concatenation
+ StringBuilder sb = null;
+ String separator = null;
+ for (final MessagePartData part : mParts) {
+ if (part.isText()) {
+ if (!foundText) {
+ // First text part
+ firstTextPart = part.getText();
+ foundText = true;
+ } else {
+ // Second and beyond
+ if (sb == null) {
+ // Need the StringBuilder and the separator starting from 2nd text part
+ sb = new StringBuilder();
+ if (!TextUtils.isEmpty(firstTextPart)) {
+ sb.append(firstTextPart);
+ }
+ separator = BugleGservices.get().getString(
+ BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR,
+ BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR_DEFAULT);
+ }
+ final String partText = part.getText();
+ if (!TextUtils.isEmpty(partText)) {
+ if (!TextUtils.isEmpty(separator) && sb.length() > 0) {
+ sb.append(separator);
+ }
+ sb.append(partText);
+ }
+ }
+ }
+ }
+ if (sb == null) {
+ // Only one text part
+ return firstTextPart;
+ } else {
+ // More than one
+ return sb.toString();
+ }
+ }
+
+ public boolean hasAttachments() {
+ for (final MessagePartData part : mParts) {
+ if (part.isAttachment()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List<MessagePartData> getAttachments() {
+ return getAttachments(null);
+ }
+
+ public List<MessagePartData> getAttachments(final Predicate<MessagePartData> filter) {
+ if (mParts.isEmpty()) {
+ return Collections.emptyList();
+ }
+ final List<MessagePartData> attachmentParts = new LinkedList<>();
+ for (final MessagePartData part : mParts) {
+ if (part.isAttachment()) {
+ if (filter == null || filter.apply(part)) {
+ attachmentParts.add(part);
+ }
+ }
+ }
+ return attachmentParts;
+ }
+
+ public final long getSentTimeStamp() {
+ return mSentTimestamp;
+ }
+
+ public final long getReceivedTimeStamp() {
+ return mReceivedTimestamp;
+ }
+
+ public final String getFormattedReceivedTimeStamp() {
+ return Dates.getMessageTimeString(mReceivedTimestamp).toString();
+ }
+
+ public final boolean getIsSeen() {
+ return mSeen;
+ }
+
+ public final boolean getIsRead() {
+ return mRead;
+ }
+
+ public final boolean getIsMms() {
+ return (mProtocol == MessageData.PROTOCOL_MMS ||
+ mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
+ }
+
+ public final boolean getIsMmsNotification() {
+ return (mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
+ }
+
+ public final boolean getIsSms() {
+ return mProtocol == (MessageData.PROTOCOL_SMS);
+ }
+
+ final int getProtocol() {
+ return mProtocol;
+ }
+
+ public final int getStatus() {
+ return mStatus;
+ }
+
+ public final String getSmsMessageUri() {
+ return mSmsMessageUri;
+ }
+
+ public final int getSmsPriority() {
+ return mSmsPriority;
+ }
+
+ public final int getSmsMessageSize() {
+ return mSmsMessageSize;
+ }
+
+ public final String getMmsSubject() {
+ return mMmsSubject;
+ }
+
+ public final long getMmsExpiry() {
+ return mMmsExpiry;
+ }
+
+ public final int getRawTelephonyStatus() {
+ return mRawTelephonyStatus;
+ }
+
+ public final String getSelfParticipantId() {
+ return mSelfParticipantId;
+ }
+
+ public boolean getIsIncoming() {
+ return (mStatus >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
+ }
+
+ public boolean hasIncomingErrorStatus() {
+ return (mStatus == MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE ||
+ mStatus == MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED);
+ }
+
+ public boolean getIsSendComplete() {
+ return mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
+ }
+
+ public String getSenderFullName() {
+ return mSenderFullName;
+ }
+
+ public String getSenderFirstName() {
+ return mSenderFirstName;
+ }
+
+ public String getSenderDisplayDestination() {
+ return mSenderDisplayDestination;
+ }
+
+ public String getSenderNormalizedDestination() {
+ return mSenderNormalizedDestination;
+ }
+
+ public Uri getSenderProfilePhotoUri() {
+ return mSenderProfilePhotoUri == null ? null : Uri.parse(mSenderProfilePhotoUri);
+ }
+
+ public long getSenderContactId() {
+ return mSenderContactId;
+ }
+
+ public String getSenderDisplayName() {
+ if (!TextUtils.isEmpty(mSenderFullName)) {
+ return mSenderFullName;
+ }
+ if (!TextUtils.isEmpty(mSenderFirstName)) {
+ return mSenderFirstName;
+ }
+ return mSenderDisplayDestination;
+ }
+
+ public String getSenderContactLookupKey() {
+ return mSenderContactLookupKey;
+ }
+
+ public boolean getShowDownloadMessage() {
+ return MessageData.getShowDownloadMessage(mStatus);
+ }
+
+ public boolean getShowResendMessage() {
+ return MessageData.getShowResendMessage(mStatus);
+ }
+
+ public boolean getCanForwardMessage() {
+ // Even for outgoing messages, we only allow forwarding if the message has finished sending
+ // as media often has issues when send isn't complete
+ return (mStatus == MessageData.BUGLE_STATUS_OUTGOING_COMPLETE ||
+ mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
+ }
+
+ public boolean getCanCopyMessageToClipboard() {
+ return (hasText() &&
+ (!getIsIncoming() || mStatus == MessageData.BUGLE_STATUS_INCOMING_COMPLETE));
+ }
+
+ public boolean getOneClickResendMessage() {
+ return MessageData.getOneClickResendMessage(mStatus, mRawTelephonyStatus);
+ }
+
+ /**
+ * Get sender's lookup uri.
+ * This method doesn't support corp contacts.
+ *
+ * @return Lookup uri of sender's contact
+ */
+ public Uri getSenderContactLookupUri() {
+ if (mSenderContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
+ && !TextUtils.isEmpty(mSenderContactLookupKey)) {
+ return ContactsContract.Contacts.getLookupUri(mSenderContactId,
+ mSenderContactLookupKey);
+ }
+ return null;
+ }
+
+ public boolean getCanClusterWithPreviousMessage() {
+ return mCanClusterWithPreviousMessage;
+ }
+
+ public boolean getCanClusterWithNextMessage() {
+ return mCanClusterWithNextMessage;
+ }
+
+ @Override
+ public String toString() {
+ return MessageData.toString(mMessageId, mParts);
+ }
+
+ // Data definitions
+
+ public static final String getConversationMessagesQuerySql() {
+ return CONVERSATION_MESSAGES_QUERY_SQL
+ + " AND "
+ // Inject the conversation id
+ + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
+ + CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
+ }
+
+ static final String getConversationMessageIdsQuerySql() {
+ return CONVERSATION_MESSAGES_IDS_QUERY_SQL
+ + " AND "
+ // Inject the conversation id
+ + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?)"
+ + CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY;
+ }
+
+ public static final String getNotificationQuerySql() {
+ return CONVERSATION_MESSAGES_QUERY_SQL
+ + " AND "
+ + "(" + DatabaseHelper.MessageColumns.STATUS + " in ("
+ + MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
+ + MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
+ + " AND "
+ + DatabaseHelper.MessageColumns.SEEN + " = 0)"
+ + ")"
+ + NOTIFICATION_QUERY_SQL_GROUP_BY;
+ }
+
+ public static final String getWearableQuerySql() {
+ return CONVERSATION_MESSAGES_QUERY_SQL
+ + " AND "
+ + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.CONVERSATION_ID + "=?"
+ + " AND "
+ + DatabaseHelper.MessageColumns.STATUS + " IN ("
+ + MessageData.BUGLE_STATUS_OUTGOING_DELIVERED + ", "
+ + MessageData.BUGLE_STATUS_OUTGOING_COMPLETE + ", "
+ + MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ", "
+ + MessageData.BUGLE_STATUS_OUTGOING_SENDING + ", "
+ + MessageData.BUGLE_STATUS_OUTGOING_RESENDING + ", "
+ + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ", "
+ + MessageData.BUGLE_STATUS_INCOMING_COMPLETE + ", "
+ + MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD + ")"
+ + ")"
+ + NOTIFICATION_QUERY_SQL_GROUP_BY;
+ }
+
+ /*
+ * Generate a sqlite snippet to call the quote function on the columnName argument.
+ * The columnName doesn't strictly have to be a column name (e.g. it could be an
+ * expression).
+ */
+ private static String quote(final String columnName) {
+ return "quote(" + columnName + ")";
+ }
+
+ private static String makeGroupConcatString(final String column) {
+ return "group_concat(" + column + ", '" + DIVIDER + "')";
+ }
+
+ private static String makeIfNullString(final String column) {
+ return "ifnull(" + column + "," + "''" + ")";
+ }
+
+ private static String makePartsTableColumnString(final String column) {
+ return DatabaseHelper.PARTS_TABLE + '.' + column;
+ }
+
+ private static String makeCaseWhenString(final String column,
+ final boolean quote,
+ final String asColumn) {
+ final String fullColumn = makeIfNullString(makePartsTableColumnString(column));
+ final String groupConcatTerm = quote
+ ? makeGroupConcatString(quote(fullColumn))
+ : makeGroupConcatString(fullColumn);
+ return "CASE WHEN (" + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT + ">1) THEN " + groupConcatTerm
+ + " ELSE " + makePartsTableColumnString(column) + " END AS " + asColumn;
+ }
+
+ private static final String CONVERSATION_MESSAGE_VIEW_PARTS_COUNT =
+ "count(" + DatabaseHelper.PARTS_TABLE + '.' + PartColumns._ID + ")";
+
+ private static final String EMPTY_STRING = "";
+
+ private static final String CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL =
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
+ + " as " + ConversationMessageViewColumns._ID + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.CONVERSATION_ID
+ + " as " + ConversationMessageViewColumns.CONVERSATION_ID + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
+ + " as " + ConversationMessageViewColumns.PARTICIPANT_ID + ", "
+
+ + makeCaseWhenString(PartColumns._ID, false,
+ ConversationMessageViewColumns.PARTS_IDS) + ", "
+ + makeCaseWhenString(PartColumns.CONTENT_TYPE, true,
+ ConversationMessageViewColumns.PARTS_CONTENT_TYPES) + ", "
+ + makeCaseWhenString(PartColumns.CONTENT_URI, true,
+ ConversationMessageViewColumns.PARTS_CONTENT_URIS) + ", "
+ + makeCaseWhenString(PartColumns.WIDTH, false,
+ ConversationMessageViewColumns.PARTS_WIDTHS) + ", "
+ + makeCaseWhenString(PartColumns.HEIGHT, false,
+ ConversationMessageViewColumns.PARTS_HEIGHTS) + ", "
+ + makeCaseWhenString(PartColumns.TEXT, true,
+ ConversationMessageViewColumns.PARTS_TEXTS) + ", "
+
+ + CONVERSATION_MESSAGE_VIEW_PARTS_COUNT
+ + " as " + ConversationMessageViewColumns.PARTS_COUNT + ", "
+
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENT_TIMESTAMP
+ + " as " + ConversationMessageViewColumns.SENT_TIMESTAMP + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP
+ + " as " + ConversationMessageViewColumns.RECEIVED_TIMESTAMP + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SEEN
+ + " as " + ConversationMessageViewColumns.SEEN + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
+ + " as " + ConversationMessageViewColumns.READ + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.PROTOCOL
+ + " as " + ConversationMessageViewColumns.PROTOCOL + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.STATUS
+ + " as " + ConversationMessageViewColumns.STATUS + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_URI
+ + " as " + ConversationMessageViewColumns.SMS_MESSAGE_URI + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_PRIORITY
+ + " as " + ConversationMessageViewColumns.SMS_PRIORITY + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SMS_MESSAGE_SIZE
+ + " as " + ConversationMessageViewColumns.SMS_MESSAGE_SIZE + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_SUBJECT
+ + " as " + ConversationMessageViewColumns.MMS_SUBJECT + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.MMS_EXPIRY
+ + " as " + ConversationMessageViewColumns.MMS_EXPIRY + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RAW_TELEPHONY_STATUS
+ + " as " + ConversationMessageViewColumns.RAW_TELEPHONY_STATUS + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SELF_PARTICIPANT_ID
+ + " as " + ConversationMessageViewColumns.SELF_PARTICIPANT_ID + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FULL_NAME
+ + " as " + ConversationMessageViewColumns.SENDER_FULL_NAME + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.FIRST_NAME
+ + " as " + ConversationMessageViewColumns.SENDER_FIRST_NAME + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.DISPLAY_DESTINATION
+ + " as " + ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.NORMALIZED_DESTINATION
+ + " as " + ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.PROFILE_PHOTO_URI
+ + " as " + ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.CONTACT_ID
+ + " as " + ConversationMessageViewColumns.SENDER_CONTACT_ID + ", "
+ + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.LOOKUP_KEY
+ + " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + " ";
+
+ private static final String CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL =
+ " FROM " + DatabaseHelper.MESSAGES_TABLE
+ + " LEFT JOIN " + DatabaseHelper.PARTS_TABLE
+ + " ON (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns._ID
+ + "=" + DatabaseHelper.PARTS_TABLE + "." + PartColumns.MESSAGE_ID + ") "
+ + " LEFT JOIN " + DatabaseHelper.PARTICIPANTS_TABLE
+ + " ON (" + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.SENDER_PARTICIPANT_ID
+ + '=' + DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns._ID + ")"
+ // Exclude draft messages from main view
+ + " WHERE (" + DatabaseHelper.MESSAGES_TABLE + "." + MessageColumns.STATUS
+ + " <> " + MessageData.BUGLE_STATUS_OUTGOING_DRAFT;
+
+ // This query is mostly static, except for the injection of conversation id. This is for
+ // performance reasons, to ensure that the query uses indices and does not trigger full scans
+ // of the messages table. See b/17160946 for more details.
+ private static final String CONVERSATION_MESSAGES_QUERY_SQL = "SELECT "
+ + CONVERSATION_MESSAGES_QUERY_PROJECTION_SQL
+ + CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
+
+ private static final String CONVERSATION_MESSAGE_IDS_PROJECTION_SQL =
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns._ID
+ + " as " + ConversationMessageViewColumns._ID + " ";
+
+ private static final String CONVERSATION_MESSAGES_IDS_QUERY_SQL = "SELECT "
+ + CONVERSATION_MESSAGE_IDS_PROJECTION_SQL
+ + CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL;
+
+ // Note that we sort DESC and ConversationData reverses the cursor. This is a performance
+ // issue (improvement) for large cursors.
+ private static final String CONVERSATION_MESSAGES_QUERY_SQL_GROUP_BY =
+ " GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
+ + " ORDER BY "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
+
+ private static final String NOTIFICATION_QUERY_SQL_GROUP_BY =
+ " GROUP BY " + DatabaseHelper.PARTS_TABLE + '.' + PartColumns.MESSAGE_ID
+ + " ORDER BY "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.RECEIVED_TIMESTAMP + " DESC";
+
+ interface ConversationMessageViewColumns extends BaseColumns {
+ static final String _ID = MessageColumns._ID;
+ static final String CONVERSATION_ID = MessageColumns.CONVERSATION_ID;
+ static final String PARTICIPANT_ID = MessageColumns.SENDER_PARTICIPANT_ID;
+ static final String PARTS_COUNT = "parts_count";
+ static final String SENT_TIMESTAMP = MessageColumns.SENT_TIMESTAMP;
+ static final String RECEIVED_TIMESTAMP = MessageColumns.RECEIVED_TIMESTAMP;
+ static final String SEEN = MessageColumns.SEEN;
+ static final String READ = MessageColumns.READ;
+ static final String PROTOCOL = MessageColumns.PROTOCOL;
+ static final String STATUS = MessageColumns.STATUS;
+ static final String SMS_MESSAGE_URI = MessageColumns.SMS_MESSAGE_URI;
+ static final String SMS_PRIORITY = MessageColumns.SMS_PRIORITY;
+ static final String SMS_MESSAGE_SIZE = MessageColumns.SMS_MESSAGE_SIZE;
+ static final String MMS_SUBJECT = MessageColumns.MMS_SUBJECT;
+ static final String MMS_EXPIRY = MessageColumns.MMS_EXPIRY;
+ static final String RAW_TELEPHONY_STATUS = MessageColumns.RAW_TELEPHONY_STATUS;
+ static final String SELF_PARTICIPANT_ID = MessageColumns.SELF_PARTICIPANT_ID;
+ static final String SENDER_FULL_NAME = ParticipantColumns.FULL_NAME;
+ static final String SENDER_FIRST_NAME = ParticipantColumns.FIRST_NAME;
+ static final String SENDER_DISPLAY_DESTINATION = ParticipantColumns.DISPLAY_DESTINATION;
+ static final String SENDER_NORMALIZED_DESTINATION =
+ ParticipantColumns.NORMALIZED_DESTINATION;
+ static final String SENDER_PROFILE_PHOTO_URI = ParticipantColumns.PROFILE_PHOTO_URI;
+ static final String SENDER_CONTACT_ID = ParticipantColumns.CONTACT_ID;
+ static final String SENDER_CONTACT_LOOKUP_KEY = ParticipantColumns.LOOKUP_KEY;
+ static final String PARTS_IDS = "parts_ids";
+ static final String PARTS_CONTENT_TYPES = "parts_content_types";
+ static final String PARTS_CONTENT_URIS = "parts_content_uris";
+ static final String PARTS_WIDTHS = "parts_widths";
+ static final String PARTS_HEIGHTS = "parts_heights";
+ static final String PARTS_TEXTS = "parts_texts";
+ }
+
+ private static int sIndexIncrementer = 0;
+
+ private static final int INDEX_MESSAGE_ID = sIndexIncrementer++;
+ private static final int INDEX_CONVERSATION_ID = sIndexIncrementer++;
+ private static final int INDEX_PARTICIPANT_ID = sIndexIncrementer++;
+
+ private static final int INDEX_PARTS_IDS = sIndexIncrementer++;
+ private static final int INDEX_PARTS_CONTENT_TYPES = sIndexIncrementer++;
+ private static final int INDEX_PARTS_CONTENT_URIS = sIndexIncrementer++;
+ private static final int INDEX_PARTS_WIDTHS = sIndexIncrementer++;
+ private static final int INDEX_PARTS_HEIGHTS = sIndexIncrementer++;
+ private static final int INDEX_PARTS_TEXTS = sIndexIncrementer++;
+
+ private static final int INDEX_PARTS_COUNT = sIndexIncrementer++;
+
+ private static final int INDEX_SENT_TIMESTAMP = sIndexIncrementer++;
+ private static final int INDEX_RECEIVED_TIMESTAMP = sIndexIncrementer++;
+ private static final int INDEX_SEEN = sIndexIncrementer++;
+ private static final int INDEX_READ = sIndexIncrementer++;
+ private static final int INDEX_PROTOCOL = sIndexIncrementer++;
+ private static final int INDEX_STATUS = sIndexIncrementer++;
+ private static final int INDEX_SMS_MESSAGE_URI = sIndexIncrementer++;
+ private static final int INDEX_SMS_PRIORITY = sIndexIncrementer++;
+ private static final int INDEX_SMS_MESSAGE_SIZE = sIndexIncrementer++;
+ private static final int INDEX_MMS_SUBJECT = sIndexIncrementer++;
+ private static final int INDEX_MMS_EXPIRY = sIndexIncrementer++;
+ private static final int INDEX_RAW_TELEPHONY_STATUS = sIndexIncrementer++;
+ private static final int INDEX_SELF_PARTICIPIANT_ID = sIndexIncrementer++;
+ private static final int INDEX_SENDER_FULL_NAME = sIndexIncrementer++;
+ private static final int INDEX_SENDER_FIRST_NAME = sIndexIncrementer++;
+ private static final int INDEX_SENDER_DISPLAY_DESTINATION = sIndexIncrementer++;
+ private static final int INDEX_SENDER_NORMALIZED_DESTINATION = sIndexIncrementer++;
+ private static final int INDEX_SENDER_PROFILE_PHOTO_URI = sIndexIncrementer++;
+ private static final int INDEX_SENDER_CONTACT_ID = sIndexIncrementer++;
+ private static final int INDEX_SENDER_CONTACT_LOOKUP_KEY = sIndexIncrementer++;
+
+
+ private static String[] sProjection = {
+ ConversationMessageViewColumns._ID,
+ ConversationMessageViewColumns.CONVERSATION_ID,
+ ConversationMessageViewColumns.PARTICIPANT_ID,
+
+ ConversationMessageViewColumns.PARTS_IDS,
+ ConversationMessageViewColumns.PARTS_CONTENT_TYPES,
+ ConversationMessageViewColumns.PARTS_CONTENT_URIS,
+ ConversationMessageViewColumns.PARTS_WIDTHS,
+ ConversationMessageViewColumns.PARTS_HEIGHTS,
+ ConversationMessageViewColumns.PARTS_TEXTS,
+
+ ConversationMessageViewColumns.PARTS_COUNT,
+ ConversationMessageViewColumns.SENT_TIMESTAMP,
+ ConversationMessageViewColumns.RECEIVED_TIMESTAMP,
+ ConversationMessageViewColumns.SEEN,
+ ConversationMessageViewColumns.READ,
+ ConversationMessageViewColumns.PROTOCOL,
+ ConversationMessageViewColumns.STATUS,
+ ConversationMessageViewColumns.SMS_MESSAGE_URI,
+ ConversationMessageViewColumns.SMS_PRIORITY,
+ ConversationMessageViewColumns.SMS_MESSAGE_SIZE,
+ ConversationMessageViewColumns.MMS_SUBJECT,
+ ConversationMessageViewColumns.MMS_EXPIRY,
+ ConversationMessageViewColumns.RAW_TELEPHONY_STATUS,
+ ConversationMessageViewColumns.SELF_PARTICIPANT_ID,
+ ConversationMessageViewColumns.SENDER_FULL_NAME,
+ ConversationMessageViewColumns.SENDER_FIRST_NAME,
+ ConversationMessageViewColumns.SENDER_DISPLAY_DESTINATION,
+ ConversationMessageViewColumns.SENDER_NORMALIZED_DESTINATION,
+ ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
+ ConversationMessageViewColumns.SENDER_CONTACT_ID,
+ ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY,
+ };
+
+ public static String[] getProjection() {
+ return sProjection;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java b/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
new file mode 100644
index 0000000..0b5ef51
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
@@ -0,0 +1,125 @@
+/*
+ * 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.datamodel.data;
+
+import android.database.Cursor;
+import android.support.v4.util.SimpleArrayMap;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A class that contains the list of all participants potentially involved in a conversation.
+ * Includes both the participant records for each participant referenced in conversation
+ * participants table (i.e. "other" phone numbers) plus all participants representing self
+ * (i.e. one per sim recorded in the subscription manager db).
+ */
+public class ConversationParticipantsData implements Iterable<ParticipantData> {
+ // A map from a participant id to a participant
+ private final SimpleArrayMap<String, ParticipantData> mConversationParticipantsMap;
+ private int mParticipantCountExcludingSelf = 0;
+
+ public ConversationParticipantsData() {
+ mConversationParticipantsMap = new SimpleArrayMap<String, ParticipantData>();
+ }
+
+ public void bind(final Cursor cursor) {
+ mConversationParticipantsMap.clear();
+ mParticipantCountExcludingSelf = 0;
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor);
+ if (!newParticipant.isSelf()) {
+ mParticipantCountExcludingSelf++;
+ }
+ mConversationParticipantsMap.put(newParticipant.getId(), newParticipant);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ ParticipantData getParticipantById(final String participantId) {
+ return mConversationParticipantsMap.get(participantId);
+ }
+
+ ArrayList<ParticipantData> getParticipantListExcludingSelf() {
+ final ArrayList<ParticipantData> retList =
+ new ArrayList<ParticipantData>(mConversationParticipantsMap.size());
+ for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
+ final ParticipantData participant = mConversationParticipantsMap.valueAt(i);
+ if (!participant.isSelf()) {
+ retList.add(participant);
+ }
+ }
+ return retList;
+ }
+
+ /**
+ * For a 1:1 conversation return the other (not self) participant
+ */
+ public ParticipantData getOtherParticipant() {
+ if (mParticipantCountExcludingSelf == 1) {
+ for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
+ final ParticipantData participant = mConversationParticipantsMap.valueAt(i);
+ if (!participant.isSelf()) {
+ return participant;
+ }
+ }
+ Assert.fail();
+ }
+ return null;
+ }
+
+ public int getNumberOfParticipantsExcludingSelf() {
+ return mParticipantCountExcludingSelf;
+ }
+
+ public boolean isLoaded() {
+ return !mConversationParticipantsMap.isEmpty();
+ }
+
+ @Override
+ public Iterator<ParticipantData> iterator() {
+ return new Iterator<ParticipantData>() {
+ private int mCurrentIndex = -1;
+
+ @Override
+ public boolean hasNext() {
+ return mCurrentIndex < mConversationParticipantsMap.size() - 1;
+ }
+
+ @Override
+ public ParticipantData next() {
+ mCurrentIndex++;
+ if (mCurrentIndex >= mConversationParticipantsMap.size()) {
+ throw new NoSuchElementException();
+ }
+ return mConversationParticipantsMap.valueAt(mCurrentIndex);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/DraftMessageData.java b/src/com/android/messaging/datamodel/data/DraftMessageData.java
new file mode 100644
index 0000000..7a7199a
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/DraftMessageData.java
@@ -0,0 +1,855 @@
+/*
+ * 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.datamodel.data;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.MessageTextStats;
+import com.android.messaging.datamodel.action.ReadDraftDataAction;
+import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener;
+import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionMonitor;
+import com.android.messaging.datamodel.action.WriteDraftMessageAction;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.SafeAsyncTask;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class DraftMessageData extends BindableData implements ReadDraftDataActionListener {
+
+ /**
+ * Interface for DraftMessageData listeners
+ */
+ public interface DraftMessageDataListener {
+ @RunsOnMainThread
+ void onDraftChanged(DraftMessageData data, int changeFlags);
+
+ @RunsOnMainThread
+ void onDraftAttachmentLimitReached(DraftMessageData data);
+
+ @RunsOnMainThread
+ void onDraftAttachmentLoadFailed();
+ }
+
+ /**
+ * Interface for providing subscription-related data to DraftMessageData
+ */
+ public interface DraftMessageSubscriptionDataProvider {
+ int getConversationSelfSubId();
+ }
+
+ // Flags sent to onDraftChanged to help the receiver limit the amount of work done
+ public static int ATTACHMENTS_CHANGED = 0x0001;
+ public static int MESSAGE_TEXT_CHANGED = 0x0002;
+ public static int MESSAGE_SUBJECT_CHANGED = 0x0004;
+ // Whether the self participant data has been loaded
+ public static int SELF_CHANGED = 0x0008;
+ public static int ALL_CHANGED = 0x00FF;
+ // ALL_CHANGED intentionally doesn't include WIDGET_CHANGED. ConversationFragment needs to
+ // be notified if the draft it is looking at is changed externally (by a desktop widget) so it
+ // can reload the draft.
+ public static int WIDGET_CHANGED = 0x0100;
+
+ private final String mConversationId;
+ private ReadDraftDataActionMonitor mMonitor;
+ private final DraftMessageDataEventDispatcher mListeners;
+ private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
+
+ private boolean mIncludeEmailAddress;
+ private boolean mIsGroupConversation;
+ private String mMessageText;
+ private String mMessageSubject;
+ private String mSelfId;
+ private MessageTextStats mMessageTextStats;
+ private boolean mSending;
+
+ /** Keeps track of completed attachments in the message draft. This data is persisted to db */
+ private final List<MessagePartData> mAttachments;
+
+ /** A read-only wrapper on mAttachments surfaced to the UI layer for rendering */
+ private final List<MessagePartData> mReadOnlyAttachments;
+
+ /** Keeps track of pending attachments that are being loaded. The pending attachments are
+ * transient, because they are not persisted to the database and are dropped once we go
+ * to the background (after the UI calls saveToStorage) */
+ private final List<PendingAttachmentData> mPendingAttachments;
+
+ /** A read-only wrapper on mPendingAttachments surfaced to the UI layer for rendering */
+ private final List<PendingAttachmentData> mReadOnlyPendingAttachments;
+
+ /** Is the current draft a cached copy of what's been saved to the database. If so, we
+ * may skip loading from database if we are still bound */
+ private boolean mIsDraftCachedCopy;
+
+ /** Whether we are currently asynchronously validating the draft before sending. */
+ private CheckDraftForSendTask mCheckDraftForSendTask;
+
+ public DraftMessageData(final String conversationId) {
+ mConversationId = conversationId;
+ mAttachments = new ArrayList<MessagePartData>();
+ mReadOnlyAttachments = Collections.unmodifiableList(mAttachments);
+ mPendingAttachments = new ArrayList<PendingAttachmentData>();
+ mReadOnlyPendingAttachments = Collections.unmodifiableList(mPendingAttachments);
+ mListeners = new DraftMessageDataEventDispatcher();
+ mMessageTextStats = new MessageTextStats();
+ }
+
+ public void addListener(final DraftMessageDataListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
+ mSubscriptionDataProvider = provider;
+ }
+
+ public void updateFromMessageData(final MessageData message, final String bindingId) {
+ // New attachments have arrived - only update if the user hasn't already edited
+ Assert.notNull(bindingId);
+ // The draft is now synced with actual MessageData and no longer a cached copy.
+ mIsDraftCachedCopy = false;
+ // Do not use the loaded draft if the user began composing a message before the draft loaded
+ // During config changes (orientation), the text fields preserve their data, so allow them
+ // to be the same and still consider the draft unchanged by the user
+ if (isDraftEmpty() || (TextUtils.equals(mMessageText, message.getMessageText()) &&
+ TextUtils.equals(mMessageSubject, message.getMmsSubject()) &&
+ mAttachments.isEmpty())) {
+ // No need to clear as just checked it was empty or a subset
+ setMessageText(message.getMessageText(), false /* notify */);
+ setMessageSubject(message.getMmsSubject(), false /* notify */);
+ for (final MessagePartData part : message.getParts()) {
+ if (part.isAttachment() && getAttachmentCount() >= getAttachmentLimit()) {
+ dispatchAttachmentLimitReached();
+ break;
+ }
+
+ if (part instanceof PendingAttachmentData) {
+ // This is a pending attachment data from share intent (e.g. an shared image
+ // that we need to persist locally).
+ final PendingAttachmentData data = (PendingAttachmentData) part;
+ Assert.equals(PendingAttachmentData.STATE_PENDING, data.getCurrentState());
+ addOnePendingAttachmentNoNotify(data, bindingId);
+ } else if (part.isAttachment()) {
+ addOneAttachmentNoNotify(part);
+ }
+ }
+ dispatchChanged(ALL_CHANGED);
+ } else {
+ // The user has started a new message so we throw out the draft message data if there
+ // is one but we also loaded the self metadata and need to let our listeners know.
+ dispatchChanged(SELF_CHANGED);
+ }
+ }
+
+ /**
+ * Create a MessageData object containing a copy of all the parts in this DraftMessageData.
+ *
+ * @param clearLocalCopy whether we should clear out the in-memory copy of the parts. If we
+ * are simply pausing/resuming and not sending the message, then we can keep
+ * @return the MessageData for the draft, null if self id is not set
+ */
+ public MessageData createMessageWithCurrentAttachments(final boolean clearLocalCopy) {
+ MessageData message = null;
+ if (getIsMms()) {
+ message = MessageData.createDraftMmsMessage(mConversationId, mSelfId,
+ mMessageText, mMessageSubject);
+ for (final MessagePartData attachment : mAttachments) {
+ message.addPart(attachment);
+ }
+ } else {
+ message = MessageData.createDraftSmsMessage(mConversationId, mSelfId,
+ mMessageText);
+ }
+
+ if (clearLocalCopy) {
+ // The message now owns all the attachments and the text...
+ clearLocalDraftCopy();
+ dispatchChanged(ALL_CHANGED);
+ } else {
+ // The draft message becomes a cached copy for UI.
+ mIsDraftCachedCopy = true;
+ }
+ return message;
+ }
+
+ private void clearLocalDraftCopy() {
+ mIsDraftCachedCopy = false;
+ mAttachments.clear();
+ setMessageText("");
+ setMessageSubject("");
+ }
+
+ public String getConversationId() {
+ return mConversationId;
+ }
+
+ public String getMessageText() {
+ return mMessageText;
+ }
+
+ public String getMessageSubject() {
+ return mMessageSubject;
+ }
+
+ public boolean getIsMms() {
+ final int selfSubId = getSelfSubId();
+ return MmsSmsUtils.getRequireMmsForEmailAddress(mIncludeEmailAddress, selfSubId) ||
+ (mIsGroupConversation && MmsUtils.groupMmsEnabled(selfSubId)) ||
+ mMessageTextStats.getMessageLengthRequiresMms() || !mAttachments.isEmpty() ||
+ !TextUtils.isEmpty(mMessageSubject);
+ }
+
+ public boolean getIsGroupMmsConversation() {
+ return getIsMms() && mIsGroupConversation;
+ }
+
+ public String getSelfId() {
+ return mSelfId;
+ }
+
+ public int getNumMessagesToBeSent() {
+ return mMessageTextStats.getNumMessagesToBeSent();
+ }
+
+ public int getCodePointsRemainingInCurrentMessage() {
+ return mMessageTextStats.getCodePointsRemainingInCurrentMessage();
+ }
+
+ public int getSelfSubId() {
+ return mSubscriptionDataProvider == null ? ParticipantData.DEFAULT_SELF_SUB_ID :
+ mSubscriptionDataProvider.getConversationSelfSubId();
+ }
+
+ private void setMessageText(final String messageText, final boolean notify) {
+ mMessageText = messageText;
+ mMessageTextStats.updateMessageTextStats(getSelfSubId(), mMessageText);
+ if (notify) {
+ dispatchChanged(MESSAGE_TEXT_CHANGED);
+ }
+ }
+
+ private void setMessageSubject(final String subject, final boolean notify) {
+ mMessageSubject = subject;
+ if (notify) {
+ dispatchChanged(MESSAGE_SUBJECT_CHANGED);
+ }
+ }
+
+ public void setMessageText(final String messageText) {
+ setMessageText(messageText, false);
+ }
+
+ public void setMessageSubject(final String subject) {
+ setMessageSubject(subject, false);
+ }
+
+ public void addAttachments(final Collection<? extends MessagePartData> attachments) {
+ // If the incoming attachments contains a single-only attachment, we need to clear
+ // the existing attachments.
+ for (final MessagePartData data : attachments) {
+ if (data.isSinglePartOnly()) {
+ // clear any existing attachments because the attachment we're adding can only
+ // exist by itself.
+ destroyAttachments();
+ break;
+ }
+ }
+ // If the existing attachments contain a single-only attachment, we need to clear the
+ // existing attachments to make room for the incoming attachment.
+ for (final MessagePartData data : mAttachments) {
+ if (data.isSinglePartOnly()) {
+ // clear any existing attachments because the single attachment can only exist
+ // by itself
+ destroyAttachments();
+ break;
+ }
+ }
+ // If any of the pending attachments contain a single-only attachment, we need to clear the
+ // existing attachments to make room for the incoming attachment.
+ for (final MessagePartData data : mPendingAttachments) {
+ if (data.isSinglePartOnly()) {
+ // clear any existing attachments because the single attachment can only exist
+ // by itself
+ destroyAttachments();
+ break;
+ }
+ }
+
+ boolean reachedLimit = false;
+ for (final MessagePartData data : attachments) {
+ // Don't break out of loop even if limit has been reached so we can destroy all
+ // of the over-limit attachments.
+ reachedLimit |= addOneAttachmentNoNotify(data);
+ }
+ if (reachedLimit) {
+ dispatchAttachmentLimitReached();
+ }
+ dispatchChanged(ATTACHMENTS_CHANGED);
+ }
+
+ public boolean containsAttachment(final Uri contentUri) {
+ for (final MessagePartData existingAttachment : mAttachments) {
+ if (existingAttachment.getContentUri().equals(contentUri)) {
+ return true;
+ }
+ }
+
+ for (final PendingAttachmentData pendingAttachment : mPendingAttachments) {
+ if (pendingAttachment.getContentUri().equals(contentUri)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Try to add one attachment to the attachment list, while guarding against duplicates and
+ * going over the limit.
+ * @return true if the attachment limit was reached, false otherwise
+ */
+ private boolean addOneAttachmentNoNotify(final MessagePartData attachment) {
+ Assert.isTrue(attachment.isAttachment());
+ final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit();
+ if (reachedLimit || containsAttachment(attachment.getContentUri())) {
+ // Never go over the limit. Never add duplicated attachments.
+ attachment.destroyAsync();
+ return reachedLimit;
+ } else {
+ addAttachment(attachment, null /*pendingAttachment*/);
+ return false;
+ }
+ }
+
+ private void addAttachment(final MessagePartData attachment,
+ final PendingAttachmentData pendingAttachment) {
+ if (attachment != null && attachment.isSinglePartOnly()) {
+ // clear any existing attachments because the attachment we're adding can only
+ // exist by itself.
+ destroyAttachments();
+ }
+ if (pendingAttachment != null && pendingAttachment.isSinglePartOnly()) {
+ // clear any existing attachments because the attachment we're adding can only
+ // exist by itself.
+ destroyAttachments();
+ }
+ // If the existing attachments contain a single-only attachment, we need to clear the
+ // existing attachments to make room for the incoming attachment.
+ for (final MessagePartData data : mAttachments) {
+ if (data.isSinglePartOnly()) {
+ // clear any existing attachments because the single attachment can only exist
+ // by itself
+ destroyAttachments();
+ break;
+ }
+ }
+ // If any of the pending attachments contain a single-only attachment, we need to clear the
+ // existing attachments to make room for the incoming attachment.
+ for (final MessagePartData data : mPendingAttachments) {
+ if (data.isSinglePartOnly()) {
+ // clear any existing attachments because the single attachment can only exist
+ // by itself
+ destroyAttachments();
+ break;
+ }
+ }
+ if (attachment != null) {
+ mAttachments.add(attachment);
+ } else if (pendingAttachment != null) {
+ mPendingAttachments.add(pendingAttachment);
+ }
+ }
+
+ public void addPendingAttachment(final PendingAttachmentData pendingAttachment,
+ final BindingBase<DraftMessageData> binding) {
+ final boolean reachedLimit = addOnePendingAttachmentNoNotify(pendingAttachment,
+ binding.getBindingId());
+ if (reachedLimit) {
+ dispatchAttachmentLimitReached();
+ }
+ dispatchChanged(ATTACHMENTS_CHANGED);
+ }
+
+ /**
+ * Try to add one pending attachment, while guarding against duplicates and
+ * going over the limit.
+ * @return true if the attachment limit was reached, false otherwise
+ */
+ private boolean addOnePendingAttachmentNoNotify(final PendingAttachmentData pendingAttachment,
+ final String bindingId) {
+ final boolean reachedLimit = getAttachmentCount() >= getAttachmentLimit();
+ if (reachedLimit || containsAttachment(pendingAttachment.getContentUri())) {
+ // Never go over the limit. Never add duplicated attachments.
+ pendingAttachment.destroyAsync();
+ return reachedLimit;
+ } else {
+ Assert.isTrue(!mPendingAttachments.contains(pendingAttachment));
+ Assert.equals(PendingAttachmentData.STATE_PENDING, pendingAttachment.getCurrentState());
+ addAttachment(null /*attachment*/, pendingAttachment);
+
+ pendingAttachment.loadAttachmentForDraft(this, bindingId);
+ return false;
+ }
+ }
+
+ public void setSelfId(final String selfId, final boolean notify) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: set selfId=" + selfId
+ + " for conversationId=" + mConversationId);
+ mSelfId = selfId;
+ if (notify) {
+ dispatchChanged(SELF_CHANGED);
+ }
+ }
+
+ public boolean hasAttachments() {
+ return !mAttachments.isEmpty();
+ }
+
+ public boolean hasPendingAttachments() {
+ return !mPendingAttachments.isEmpty();
+ }
+
+ private int getAttachmentCount() {
+ return mAttachments.size() + mPendingAttachments.size();
+ }
+
+ private int getVideoAttachmentCount() {
+ int count = 0;
+ for (MessagePartData part : mAttachments) {
+ if (part.isVideo()) {
+ count++;
+ }
+ }
+ for (MessagePartData part : mPendingAttachments) {
+ if (part.isVideo()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private int getAttachmentLimit() {
+ return BugleGservices.get().getInt(
+ BugleGservicesKeys.MMS_ATTACHMENT_LIMIT,
+ BugleGservicesKeys.MMS_ATTACHMENT_LIMIT_DEFAULT);
+ }
+
+ public void removeAttachment(final MessagePartData attachment) {
+ for (final MessagePartData existingAttachment : mAttachments) {
+ if (existingAttachment.getContentUri().equals(attachment.getContentUri())) {
+ mAttachments.remove(existingAttachment);
+ existingAttachment.destroyAsync();
+ dispatchChanged(ATTACHMENTS_CHANGED);
+ break;
+ }
+ }
+ }
+
+ public void removeExistingAttachments(final Set<MessagePartData> attachmentsToRemove) {
+ boolean removed = false;
+ final Iterator<MessagePartData> iterator = mAttachments.iterator();
+ while (iterator.hasNext()) {
+ final MessagePartData existingAttachment = iterator.next();
+ if (attachmentsToRemove.contains(existingAttachment)) {
+ iterator.remove();
+ existingAttachment.destroyAsync();
+ removed = true;
+ }
+ }
+
+ if (removed) {
+ dispatchChanged(ATTACHMENTS_CHANGED);
+ }
+ }
+
+ public void removePendingAttachment(final PendingAttachmentData pendingAttachment) {
+ for (final PendingAttachmentData existingAttachment : mPendingAttachments) {
+ if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) {
+ mPendingAttachments.remove(pendingAttachment);
+ pendingAttachment.destroyAsync();
+ dispatchChanged(ATTACHMENTS_CHANGED);
+ break;
+ }
+ }
+ }
+
+ public void updatePendingAttachment(final MessagePartData updatedAttachment,
+ final PendingAttachmentData pendingAttachment) {
+ for (final PendingAttachmentData existingAttachment : mPendingAttachments) {
+ if (existingAttachment.getContentUri().equals(pendingAttachment.getContentUri())) {
+ mPendingAttachments.remove(pendingAttachment);
+ if (pendingAttachment.isSinglePartOnly()) {
+ updatedAttachment.setSinglePartOnly(true);
+ }
+ mAttachments.add(updatedAttachment);
+ dispatchChanged(ATTACHMENTS_CHANGED);
+ return;
+ }
+ }
+
+ // If we are here, this means the pending attachment has been dropped before the task
+ // to load it was completed. In this case destroy the temporarily staged file since it
+ // is no longer needed.
+ updatedAttachment.destroyAsync();
+ }
+
+ /**
+ * Remove the attachments from the draft and notify any listeners.
+ * @param flags typically this will be ATTACHMENTS_CHANGED. When attachments are cleared in a
+ * widget, flags will also contain WIDGET_CHANGED.
+ */
+ public void clearAttachments(final int flags) {
+ destroyAttachments();
+ dispatchChanged(flags);
+ }
+
+ public List<MessagePartData> getReadOnlyAttachments() {
+ return mReadOnlyAttachments;
+ }
+
+ public List<PendingAttachmentData> getReadOnlyPendingAttachments() {
+ return mReadOnlyPendingAttachments;
+ }
+
+ public boolean loadFromStorage(final BindingBase<DraftMessageData> binding,
+ final MessageData optionalIncomingDraft, boolean clearLocalDraft) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: "
+ + (optionalIncomingDraft == null ? "loading" : "setting")
+ + " for conversationId=" + mConversationId);
+ if (clearLocalDraft) {
+ clearLocalDraftCopy();
+ }
+ final boolean isDraftCachedCopy = mIsDraftCachedCopy;
+ mIsDraftCachedCopy = false;
+ // Before reading message from db ensure the caller is bound to us (and knows the id)
+ if (mMonitor == null && !isDraftCachedCopy && isBound(binding.getBindingId())) {
+ mMonitor = ReadDraftDataAction.readDraftData(mConversationId,
+ optionalIncomingDraft, binding.getBindingId(), this);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Saves the current draft to db. This will save the draft and drop any pending attachments
+ * we have. The UI typically goes into the background when this is called, and instead of
+ * trying to persist the state of the pending attachments (the app may be killed, the activity
+ * may be destroyed), we simply drop the pending attachments for consistency.
+ */
+ public void saveToStorage(final BindingBase<DraftMessageData> binding) {
+ saveToStorageInternal(binding);
+ dropPendingAttachments();
+ }
+
+ private void saveToStorageInternal(final BindingBase<DraftMessageData> binding) {
+ // Create MessageData to store to db, but don't clear the in-memory copy so UI will
+ // continue to display it.
+ // If self id is null then we'll not attempt to change the conversation's self id.
+ final MessageData message = createMessageWithCurrentAttachments(false /* clearLocalCopy */);
+ // Before writing message to db ensure the caller is bound to us (and knows the id)
+ if (isBound(binding.getBindingId())){
+ WriteDraftMessageAction.writeDraftMessage(mConversationId, message);
+ }
+ }
+
+ /**
+ * Called when we are ready to send the message. This will assemble/return the MessageData for
+ * sending and clear the local draft data, both from memory and from DB. This will also bind
+ * the message data with a self Id through which the message will be sent.
+ *
+ * @param binding the binding object from our consumer. We need to make sure we are still bound
+ * to that binding before saving to storage.
+ */
+ public MessageData prepareMessageForSending(final BindingBase<DraftMessageData> binding) {
+ // We can't send the message while there's still stuff pending.
+ Assert.isTrue(!hasPendingAttachments());
+ mSending = true;
+ // Assembles the message to send and empty working draft data.
+ // If self id is null then message is sent with conversation's self id.
+ final MessageData messageToSend =
+ createMessageWithCurrentAttachments(true /* clearLocalCopy */);
+ // Note sending message will empty the draft data in DB.
+ mSending = false;
+ return messageToSend;
+ }
+
+ public boolean isSending() {
+ return mSending;
+ }
+
+ @Override // ReadDraftMessageActionListener.onReadDraftMessageSucceeded
+ public void onReadDraftDataSucceeded(final ReadDraftDataAction action, final Object data,
+ final MessageData message, final ConversationListItemData conversation) {
+ final String bindingId = (String) data;
+
+ // Before passing draft message on to ui ensure the data is bound to the same bindingid
+ if (isBound(bindingId)) {
+ mSelfId = message.getSelfId();
+ mIsGroupConversation = conversation.getIsGroup();
+ mIncludeEmailAddress = conversation.getIncludeEmailAddress();
+ updateFromMessageData(message, bindingId);
+ LogUtil.d(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded. "
+ + "conversationId=" + mConversationId + " selfId=" + mSelfId);
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft loaded but not bound. "
+ + "conversationId=" + mConversationId);
+ }
+ mMonitor = null;
+ }
+
+ @Override // ReadDraftMessageActionListener.onReadDraftDataFailed
+ public void onReadDraftDataFailed(final ReadDraftDataAction action, final Object data) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "DraftMessageData: draft not loaded. "
+ + "conversationId=" + mConversationId);
+ // The draft is now synced with actual MessageData and no longer a cached copy.
+ mIsDraftCachedCopy = false;
+ // Just clear the monitor - no update to draft data
+ mMonitor = null;
+ }
+
+ /**
+ * Check if Bugle is default sms app
+ * @return
+ */
+ public boolean getIsDefaultSmsApp() {
+ return PhoneUtils.getDefault().isDefaultSmsApp();
+ }
+
+ @Override //BindableData.unregisterListeners
+ protected void unregisterListeners() {
+ if (mMonitor != null) {
+ mMonitor.unregister();
+ }
+ mMonitor = null;
+ mListeners.clear();
+ }
+
+ private void destroyAttachments() {
+ for (final MessagePartData attachment : mAttachments) {
+ attachment.destroyAsync();
+ }
+ mAttachments.clear();
+ mPendingAttachments.clear();
+ }
+
+ private void dispatchChanged(final int changeFlags) {
+ // No change is expected to be made to the draft if it is in cached copy state.
+ if (mIsDraftCachedCopy) {
+ return;
+ }
+ // Any change in the draft will cancel any pending draft checking task, since the
+ // size/status of the draft may have changed.
+ if (mCheckDraftForSendTask != null) {
+ mCheckDraftForSendTask.cancel(true /* mayInterruptIfRunning */);
+ mCheckDraftForSendTask = null;
+ }
+ mListeners.onDraftChanged(this, changeFlags);
+ }
+
+ private void dispatchAttachmentLimitReached() {
+ mListeners.onDraftAttachmentLimitReached(this);
+ }
+
+ /**
+ * Drop any pending attachments that haven't finished. This is called after the UI goes to
+ * the background and we persist the draft data to the database.
+ */
+ private void dropPendingAttachments() {
+ mPendingAttachments.clear();
+ }
+
+ private boolean isDraftEmpty() {
+ return TextUtils.isEmpty(mMessageText) && mAttachments.isEmpty() &&
+ TextUtils.isEmpty(mMessageSubject);
+ }
+
+ public boolean isCheckingDraft() {
+ return mCheckDraftForSendTask != null && !mCheckDraftForSendTask.isCancelled();
+ }
+
+ public void checkDraftForAction(final boolean checkMessageSize, final int selfSubId,
+ final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
+ new CheckDraftForSendTask(checkMessageSize, selfSubId, callback, binding)
+ .executeOnThreadPool((Void) null);
+ }
+
+ /**
+ * Allows us to have multiple data listeners for DraftMessageData
+ */
+ private class DraftMessageDataEventDispatcher
+ extends ArrayList<DraftMessageDataListener>
+ implements DraftMessageDataListener {
+
+ @Override
+ @RunsOnMainThread
+ public void onDraftChanged(DraftMessageData data, int changeFlags) {
+ Assert.isMainThread();
+ for (final DraftMessageDataListener listener : this) {
+ listener.onDraftChanged(data, changeFlags);
+ }
+ }
+
+ @Override
+ @RunsOnMainThread
+ public void onDraftAttachmentLimitReached(DraftMessageData data) {
+ Assert.isMainThread();
+ for (final DraftMessageDataListener listener : this) {
+ listener.onDraftAttachmentLimitReached(data);
+ }
+ }
+
+ @Override
+ @RunsOnMainThread
+ public void onDraftAttachmentLoadFailed() {
+ Assert.isMainThread();
+ for (final DraftMessageDataListener listener : this) {
+ listener.onDraftAttachmentLoadFailed();
+ }
+ }
+ }
+
+ public interface CheckDraftTaskCallback {
+ void onDraftChecked(DraftMessageData data, int result);
+ }
+
+ public class CheckDraftForSendTask extends SafeAsyncTask<Void, Void, Integer> {
+ public static final int RESULT_PASSED = 0;
+ public static final int RESULT_HAS_PENDING_ATTACHMENTS = 1;
+ public static final int RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS = 2;
+ public static final int RESULT_MESSAGE_OVER_LIMIT = 3;
+ public static final int RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED = 4;
+ public static final int RESULT_SIM_NOT_READY = 5;
+ private final boolean mCheckMessageSize;
+ private final int mSelfSubId;
+ private final CheckDraftTaskCallback mCallback;
+ private final String mBindingId;
+ private final List<MessagePartData> mAttachmentsCopy;
+ private int mPreExecuteResult = RESULT_PASSED;
+
+ public CheckDraftForSendTask(final boolean checkMessageSize, final int selfSubId,
+ final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
+ mCheckMessageSize = checkMessageSize;
+ mSelfSubId = selfSubId;
+ mCallback = callback;
+ mBindingId = binding.getBindingId();
+ // Obtain an immutable copy of the attachment list so we can operate on it in the
+ // background thread.
+ mAttachmentsCopy = new ArrayList<MessagePartData>(mAttachments);
+
+ mCheckDraftForSendTask = this;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ // Perform checking work that can happen on the main thread.
+ if (hasPendingAttachments()) {
+ mPreExecuteResult = RESULT_HAS_PENDING_ATTACHMENTS;
+ return;
+ }
+ if (getIsGroupMmsConversation()) {
+ try {
+ if (TextUtils.isEmpty(PhoneUtils.get(mSelfSubId).getSelfRawNumber(true))) {
+ mPreExecuteResult = RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS;
+ return;
+ }
+ } catch (IllegalStateException e) {
+ // This happens when there is no active subscription, e.g. on Nova
+ // when the phone switches carrier.
+ mPreExecuteResult = RESULT_SIM_NOT_READY;
+ return;
+ }
+ }
+ if (getVideoAttachmentCount() > MmsUtils.MAX_VIDEO_ATTACHMENT_COUNT) {
+ mPreExecuteResult = RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED;
+ return;
+ }
+ }
+
+ @Override
+ protected Integer doInBackgroundTimed(Void... params) {
+ if (mPreExecuteResult != RESULT_PASSED) {
+ return mPreExecuteResult;
+ }
+
+ if (mCheckMessageSize && getIsMessageOverLimit()) {
+ return RESULT_MESSAGE_OVER_LIMIT;
+ }
+ return RESULT_PASSED;
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ mCheckDraftForSendTask = null;
+ // Only call back if we are bound to the original binding.
+ if (isBound(mBindingId) && !isCancelled()) {
+ mCallback.onDraftChecked(DraftMessageData.this, result);
+ } else {
+ if (!isBound(mBindingId)) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft not bound");
+ }
+ if (isCancelled()) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: draft is cancelled");
+ }
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ mCheckDraftForSendTask = null;
+ }
+
+ /**
+ * 1. Check if the draft message contains too many attachments to send
+ * 2. Computes the minimum size that this message could be compressed/downsampled/encoded
+ * before sending and check if it meets the carrier max size for sending.
+ * @see MessagePartData#getMinimumSizeInBytesForSending()
+ */
+ @DoesNotRunOnMainThread
+ private boolean getIsMessageOverLimit() {
+ Assert.isNotMainThread();
+ if (mAttachmentsCopy.size() > getAttachmentLimit()) {
+ return true;
+ }
+
+ // Aggregate the size from all the attachments.
+ long totalSize = 0;
+ for (final MessagePartData attachment : mAttachmentsCopy) {
+ totalSize += attachment.getMinimumSizeInBytesForSending();
+ }
+ return totalSize > MmsConfig.get(mSelfSubId).getMaxMessageSize();
+ }
+ }
+
+ public void onPendingAttachmentLoadFailed(PendingAttachmentData data) {
+ mListeners.onDraftAttachmentLoadFailed();
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
new file mode 100644
index 0000000..6649757
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
@@ -0,0 +1,128 @@
+/*
+ * 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.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.datamodel.media.FileImageRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.util.Assert;
+
+/**
+ * Provides data for GalleryGridItemView
+ */
+public class GalleryGridItemData {
+ public static final String[] IMAGE_PROJECTION = new String[] {
+ Media._ID,
+ Media.DATA,
+ Media.WIDTH,
+ Media.HEIGHT,
+ 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_WIDTH = 2;
+ private static final int INDEX_HEIGHT = 3;
+ private static final int INDEX_MIME_TYPE = 4;
+ private static final int INDEX_DATE_MODIFIED = 5;
+
+ /** A special item's id for picking images from document picker */
+ public static final String ID_DOCUMENT_PICKER_ITEM = "-1";
+
+ private UriImageRequestDescriptor mImageData;
+ private String mContentType;
+ private boolean mIsDocumentPickerItem;
+ private long mDateSeconds;
+
+ public 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 {
+ int sourceWidth = cursor.getInt(INDEX_WIDTH);
+ int sourceHeight = cursor.getInt(INDEX_HEIGHT);
+
+ // Guard against bad data
+ if (sourceWidth <= 0) {
+ sourceWidth = ImageRequest.UNSPECIFIED_SIZE;
+ }
+ if (sourceHeight <= 0) {
+ sourceHeight = ImageRequest.UNSPECIFIED_SIZE;
+ }
+
+ 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 */);
+ }
+ }
+
+ public boolean isDocumentPickerItem() {
+ return mIsDocumentPickerItem;
+ }
+
+ public Uri getImageUri() {
+ return mImageData.uri;
+ }
+
+ public UriImageRequestDescriptor getImageRequestDescriptor() {
+ return mImageData;
+ }
+
+ public MessagePartData constructMessagePartData(final Rect startRect) {
+ Assert.isTrue(!mIsDocumentPickerItem);
+ return new MediaPickerMessagePartData(startRect, mContentType,
+ mImageData.uri, mImageData.sourceWidth, mImageData.sourceHeight);
+ }
+
+ /**
+ * @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/LaunchConversationData.java b/src/com/android/messaging/datamodel/data/LaunchConversationData.java
new file mode 100644
index 0000000..7eea580
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/LaunchConversationData.java
@@ -0,0 +1,90 @@
+/*
+ * 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.datamodel.data;
+
+import com.android.messaging.datamodel.action.ActionMonitor;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.LogUtil;
+
+public class LaunchConversationData extends BindableData implements
+ GetOrCreateConversationActionListener {
+ public interface LaunchConversationDataListener {
+ void onGetOrCreateNewConversation(String conversationId);
+ void onGetOrCreateNewConversationFailed();
+ }
+
+ private LaunchConversationDataListener mListener;
+ private GetOrCreateConversationActionMonitor mMonitor;
+
+ public LaunchConversationData(final LaunchConversationDataListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+ if (mMonitor != null) {
+ mMonitor.unregister();
+ }
+ mMonitor = null;
+ }
+
+ public void getOrCreateConversation(final BindingBase<LaunchConversationData> binding,
+ final String[] recipients) {
+ final String bindingId = binding.getBindingId();
+
+ // Start a new conversation from the list of contacts.
+ if (isBound(bindingId) && mMonitor == null) {
+ mMonitor = GetOrCreateConversationAction.getOrCreateConversation(recipients,
+ bindingId, this);
+ }
+ }
+
+ @Override
+ @RunsOnMainThread
+ public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
+ final Object data, final String conversationId) {
+ Assert.isTrue(monitor == mMonitor);
+ Assert.isTrue(conversationId != null);
+
+ final String bindingId = (String) data;
+ if (isBound(bindingId) && mListener != null) {
+ mListener.onGetOrCreateNewConversation(conversationId);
+ }
+
+ mMonitor = null;
+ }
+
+ @Override
+ @RunsOnMainThread
+ public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
+ final Object data) {
+ Assert.isTrue(monitor == mMonitor);
+ final String bindingId = (String) data;
+ if (isBound(bindingId) && mListener != null) {
+ mListener.onGetOrCreateNewConversationFailed();
+ }
+ LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
+ mMonitor = null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/MediaPickerData.java b/src/com/android/messaging/datamodel/data/MediaPickerData.java
new file mode 100644
index 0000000..b0c8bf7
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/MediaPickerData.java
@@ -0,0 +1,175 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+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.util.Assert;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Services data needs for MediaPicker.
+ */
+public class MediaPickerData extends BindableData {
+ public interface MediaPickerDataListener {
+ void onMediaPickerDataUpdated(MediaPickerData mediaPickerData, Object data, int loaderId);
+ }
+
+ private static final String BINDING_ID = "bindingId";
+ private final Context mContext;
+ private LoaderManager mLoaderManager;
+ private final GalleryLoaderCallbacks mGalleryLoaderCallbacks;
+ private MediaPickerDataListener mListener;
+
+ public MediaPickerData(final Context context) {
+ mContext = context;
+ mGalleryLoaderCallbacks = new GalleryLoaderCallbacks();
+ }
+
+ public static final int GALLERY_IMAGE_LOADER = 1;
+
+ /**
+ * A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
+ */
+ private class GalleryLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ switch (id) {
+ case GALLERY_IMAGE_LOADER:
+ return new GalleryBoundCursorLoader(bindingId, mContext);
+
+ default:
+ Assert.fail("Unknown loader id for gallery picker!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the media picker");
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ if (isBound(cursorLoader.getBindingId())) {
+ switch (loader.getId()) {
+ case GALLERY_IMAGE_LOADER:
+ mListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
+ GALLERY_IMAGE_LOADER);
+ break;
+
+ default:
+ Assert.fail("Unknown loader id for gallery picker!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the media picker");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<Cursor> loader) {
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ if (isBound(cursorLoader.getBindingId())) {
+ switch (loader.getId()) {
+ case GALLERY_IMAGE_LOADER:
+ mListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
+ GALLERY_IMAGE_LOADER);
+ break;
+
+ default:
+ Assert.fail("Unknown loader id for media picker!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the media picker");
+ }
+ }
+ }
+
+
+
+ public void startLoader(final int loaderId, final BindingBase<MediaPickerData> binding,
+ @Nullable Bundle args, final MediaPickerDataListener listener) {
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putString(BINDING_ID, binding.getBindingId());
+ if (loaderId == GALLERY_IMAGE_LOADER) {
+ mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
+ } else {
+ Assert.fail("Unsupported loader id for media picker!");
+ }
+ mListener = listener;
+ }
+
+ public void destroyLoader(final int loaderId) {
+ mLoaderManager.destroyLoader(loaderId);
+ }
+
+ public void init(final LoaderManager loaderManager) {
+ mLoaderManager = loaderManager;
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ // This could be null if we bind but the caller doesn't init the BindableData
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(GALLERY_IMAGE_LOADER);
+ mLoaderManager = null;
+ }
+ }
+
+ /**
+ * Gets the last selected chooser index, or -1 if no selection has been saved.
+ */
+ public int getSelectedChooserIndex() {
+ return BuglePrefs.getApplicationPrefs().getInt(
+ BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX,
+ BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT);
+ }
+
+ /**
+ * Saves the selected media chooser index.
+ * @param selectedIndex the selected media chooser index.
+ */
+ public void saveSelectedChooserIndex(final int selectedIndex) {
+ BuglePrefs.getApplicationPrefs().putInt(BuglePrefsKeys.SELECTED_MEDIA_PICKER_CHOOSER_INDEX,
+ selectedIndex);
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java b/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java
new file mode 100644
index 0000000..7de9166
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/MediaPickerMessagePartData.java
@@ -0,0 +1,64 @@
+/*
+ * 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.datamodel.data;
+
+import android.graphics.Rect;
+import android.net.Uri;
+
+public class MediaPickerMessagePartData extends MessagePartData {
+ private final Rect mStartRect;
+
+ public MediaPickerMessagePartData(final Rect startRect, final String contentType,
+ final Uri contentUri, final int width, final int height) {
+ this(startRect, null /* messageText */, contentType, contentUri, width, height);
+ }
+
+ public MediaPickerMessagePartData(final Rect startRect, final String messageText,
+ final String contentType, final Uri contentUri, final int width, final int height) {
+ this(startRect, messageText, contentType, contentUri, width, height,
+ false /*onlySingleAttachment*/);
+ }
+
+ public MediaPickerMessagePartData(final Rect startRect, final String contentType,
+ final Uri contentUri, final int width, final int height,
+ final boolean onlySingleAttachment) {
+ this(startRect, null /* messageText */, contentType, contentUri, width, height,
+ onlySingleAttachment);
+ }
+
+ public MediaPickerMessagePartData(final Rect startRect, final String messageText,
+ final String contentType, final Uri contentUri, final int width, final int height,
+ final boolean onlySingleAttachment) {
+ super(messageText, contentType, contentUri, width, height, onlySingleAttachment);
+ mStartRect = startRect;
+ }
+
+ /**
+ * @return The starting rect to animate the attachment preview from in order to perform a smooth
+ * transition
+ */
+ public Rect getStartRect() {
+ return mStartRect;
+ }
+
+ /**
+ * Modify the start rect of the attachment.
+ */
+ public void setStartRect(final Rect startRect) {
+ mStartRect.set(startRect);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/MessageData.java b/src/com/android/messaging/datamodel/data/MessageData.java
new file mode 100644
index 0000000..a3698a9
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/MessageData.java
@@ -0,0 +1,922 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.OsUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MessageData implements Parcelable {
+ private static final String[] sProjection = {
+ MessageColumns._ID,
+ MessageColumns.CONVERSATION_ID,
+ MessageColumns.SENDER_PARTICIPANT_ID,
+ MessageColumns.SELF_PARTICIPANT_ID,
+ MessageColumns.SENT_TIMESTAMP,
+ MessageColumns.RECEIVED_TIMESTAMP,
+ MessageColumns.SEEN,
+ MessageColumns.READ,
+ MessageColumns.PROTOCOL,
+ MessageColumns.STATUS,
+ MessageColumns.SMS_MESSAGE_URI,
+ MessageColumns.SMS_PRIORITY,
+ MessageColumns.SMS_MESSAGE_SIZE,
+ MessageColumns.MMS_SUBJECT,
+ MessageColumns.MMS_TRANSACTION_ID,
+ MessageColumns.MMS_CONTENT_LOCATION,
+ MessageColumns.MMS_EXPIRY,
+ MessageColumns.RAW_TELEPHONY_STATUS,
+ MessageColumns.RETRY_START_TIMESTAMP,
+ };
+
+ private static final int INDEX_ID = 0;
+ private static final int INDEX_CONVERSATION_ID = 1;
+ private static final int INDEX_PARTICIPANT_ID = 2;
+ private static final int INDEX_SELF_ID = 3;
+ private static final int INDEX_SENT_TIMESTAMP = 4;
+ private static final int INDEX_RECEIVED_TIMESTAMP = 5;
+ private static final int INDEX_SEEN = 6;
+ private static final int INDEX_READ = 7;
+ private static final int INDEX_PROTOCOL = 8;
+ private static final int INDEX_BUGLE_STATUS = 9;
+ private static final int INDEX_SMS_MESSAGE_URI = 10;
+ private static final int INDEX_SMS_PRIORITY = 11;
+ private static final int INDEX_SMS_MESSAGE_SIZE = 12;
+ private static final int INDEX_MMS_SUBJECT = 13;
+ private static final int INDEX_MMS_TRANSACTION_ID = 14;
+ private static final int INDEX_MMS_CONTENT_LOCATION = 15;
+ private static final int INDEX_MMS_EXPIRY = 16;
+ private static final int INDEX_RAW_TELEPHONY_STATUS = 17;
+ private static final int INDEX_RETRY_START_TIMESTAMP = 18;
+
+ // SQL statement to insert a "complete" message row (columns based on the projection above).
+ private static final String INSERT_MESSAGE_SQL =
+ "INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( "
+ + TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1,
+ INDEX_RETRY_START_TIMESTAMP + 1))
+ + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ private String mMessageId;
+ private String mConversationId;
+ private String mParticipantId;
+ private String mSelfId;
+ private long mSentTimestamp;
+ private long mReceivedTimestamp;
+ private boolean mSeen;
+ private boolean mRead;
+ private int mProtocol;
+ private Uri mSmsMessageUri;
+ private int mSmsPriority;
+ private long mSmsMessageSize;
+ private String mMmsSubject;
+ private String mMmsTransactionId;
+ private String mMmsContentLocation;
+ private long mMmsExpiry;
+ private int mRawStatus;
+ private int mStatus;
+ private final ArrayList<MessagePartData> mParts;
+ private long mRetryStartTimestamp;
+
+ // PROTOCOL Values
+ public static final int PROTOCOL_UNKNOWN = -1; // Unknown type
+ public static final int PROTOCOL_SMS = 0; // SMS message
+ public static final int PROTOCOL_MMS = 1; // MMS message
+ public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification
+
+ // Bugle STATUS Values
+ public static final int BUGLE_STATUS_UNKNOWN = 0;
+
+ // Outgoing
+ public static final int BUGLE_STATUS_OUTGOING_COMPLETE = 1;
+ public static final int BUGLE_STATUS_OUTGOING_DELIVERED = 2;
+ // Transitions to either YET_TO_SEND or SEND_AFTER_PROCESSING depending attachments.
+ public static final int BUGLE_STATUS_OUTGOING_DRAFT = 3;
+ public static final int BUGLE_STATUS_OUTGOING_YET_TO_SEND = 4;
+ public static final int BUGLE_STATUS_OUTGOING_SENDING = 5;
+ public static final int BUGLE_STATUS_OUTGOING_RESENDING = 6;
+ public static final int BUGLE_STATUS_OUTGOING_AWAITING_RETRY = 7;
+ public static final int BUGLE_STATUS_OUTGOING_FAILED = 8;
+ public static final int BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER = 9;
+
+ // Incoming
+ public static final int BUGLE_STATUS_INCOMING_COMPLETE = 100;
+ public static final int BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD = 101;
+ public static final int BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD = 102;
+ public static final int BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING = 103;
+ public static final int BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD = 104;
+ public static final int BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING = 105;
+ public static final int BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED = 106;
+ public static final int BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE = 107;
+
+ public static final String getStatusDescription(int status) {
+ switch (status) {
+ case BUGLE_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case BUGLE_STATUS_OUTGOING_COMPLETE:
+ return "OUTGOING_COMPLETE";
+ case BUGLE_STATUS_OUTGOING_DELIVERED:
+ return "OUTGOING_DELIVERED";
+ case BUGLE_STATUS_OUTGOING_DRAFT:
+ return "OUTGOING_DRAFT";
+ case BUGLE_STATUS_OUTGOING_YET_TO_SEND:
+ return "OUTGOING_YET_TO_SEND";
+ case BUGLE_STATUS_OUTGOING_SENDING:
+ return "OUTGOING_SENDING";
+ case BUGLE_STATUS_OUTGOING_RESENDING:
+ return "OUTGOING_RESENDING";
+ case BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
+ return "OUTGOING_AWAITING_RETRY";
+ case BUGLE_STATUS_OUTGOING_FAILED:
+ return "OUTGOING_FAILED";
+ case BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
+ return "OUTGOING_FAILED_EMERGENCY_NUMBER";
+ case BUGLE_STATUS_INCOMING_COMPLETE:
+ return "INCOMING_COMPLETE";
+ case BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
+ return "INCOMING_YET_TO_MANUAL_DOWNLOAD";
+ case BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
+ return "INCOMING_RETRYING_MANUAL_DOWNLOAD";
+ case BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
+ return "INCOMING_MANUAL_DOWNLOADING";
+ case BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
+ return "INCOMING_RETRYING_AUTO_DOWNLOAD";
+ case BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
+ return "INCOMING_AUTO_DOWNLOADING";
+ case BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
+ return "INCOMING_DOWNLOAD_FAILED";
+ case BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
+ return "INCOMING_EXPIRED_OR_NOT_AVAILABLE";
+ default:
+ return String.valueOf(status) + " (check MessageData)";
+ }
+ }
+
+ // All incoming messages expect to have status >= BUGLE_STATUS_FIRST_INCOMING
+ public static final int BUGLE_STATUS_FIRST_INCOMING = BUGLE_STATUS_INCOMING_COMPLETE;
+
+ // Detailed MMS failures. Most of the values are defined in PduHeaders. However, a few are
+ // defined here instead. These are never returned in the MMS HTTP response, but are used
+ // internally. The values here must not conflict with any of the existing PduHeader values.
+ public static final int RAW_TELEPHONY_STATUS_UNDEFINED = MmsUtils.PDU_HEADER_VALUE_UNDEFINED;
+ public static final int RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG = 10000;
+
+ // Unknown result code for MMS sending/downloading. This is used as the default value
+ // for result code returned from platform MMS API.
+ public static final int UNKNOWN_RESULT_CODE = 0;
+
+ /**
+ * Create an "empty" message
+ */
+ public MessageData() {
+ mParts = new ArrayList<MessagePartData>();
+ }
+
+ public static String[] getProjection() {
+ return sProjection;
+ }
+
+ /**
+ * Create a draft message for a particular conversation based on supplied content
+ */
+ public static MessageData createDraftMessage(final String conversationId,
+ final String selfId, final MessageData content) {
+ final MessageData message = new MessageData();
+ message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
+ message.mProtocol = PROTOCOL_UNKNOWN;
+ message.mConversationId = conversationId;
+ message.mParticipantId = selfId;
+ message.mReceivedTimestamp = System.currentTimeMillis();
+ if (content == null) {
+ message.mParts.add(MessagePartData.createTextMessagePart(""));
+ } else {
+ if (!TextUtils.isEmpty(content.mParticipantId)) {
+ message.mParticipantId = content.mParticipantId;
+ }
+ if (!TextUtils.isEmpty(content.mMmsSubject)) {
+ message.mMmsSubject = content.mMmsSubject;
+ }
+ for (final MessagePartData part : content.getParts()) {
+ message.mParts.add(part);
+ }
+ }
+ message.mSelfId = selfId;
+ return message;
+ }
+
+ /**
+ * Create a draft sms message for a particular conversation
+ */
+ public static MessageData createDraftSmsMessage(final String conversationId,
+ final String selfId, final String messageText) {
+ final MessageData message = new MessageData();
+ message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
+ message.mProtocol = PROTOCOL_SMS;
+ message.mConversationId = conversationId;
+ message.mParticipantId = selfId;
+ message.mSelfId = selfId;
+ message.mParts.add(MessagePartData.createTextMessagePart(messageText));
+ message.mReceivedTimestamp = System.currentTimeMillis();
+ return message;
+ }
+
+ /**
+ * Create a draft mms message for a particular conversation
+ */
+ public static MessageData createDraftMmsMessage(final String conversationId,
+ final String selfId, final String messageText, final String subjectText) {
+ final MessageData message = new MessageData();
+ message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
+ message.mProtocol = PROTOCOL_MMS;
+ message.mConversationId = conversationId;
+ message.mParticipantId = selfId;
+ message.mSelfId = selfId;
+ message.mMmsSubject = subjectText;
+ message.mReceivedTimestamp = System.currentTimeMillis();
+ if (!TextUtils.isEmpty(messageText)) {
+ message.mParts.add(MessagePartData.createTextMessagePart(messageText));
+ }
+ return message;
+ }
+
+ /**
+ * Create a message received from a particular number in a particular conversation
+ */
+ public static MessageData createReceivedSmsMessage(final Uri uri, final String conversationId,
+ final String participantId, final String selfId, final String messageText,
+ final String subject, final long sent, final long recieved,
+ final boolean seen, final boolean read) {
+ final MessageData message = new MessageData();
+ message.mSmsMessageUri = uri;
+ message.mConversationId = conversationId;
+ message.mParticipantId = participantId;
+ message.mSelfId = selfId;
+ message.mProtocol = PROTOCOL_SMS;
+ message.mStatus = BUGLE_STATUS_INCOMING_COMPLETE;
+ message.mMmsSubject = subject;
+ message.mReceivedTimestamp = recieved;
+ message.mSentTimestamp = sent;
+ message.mParts.add(MessagePartData.createTextMessagePart(messageText));
+ message.mSeen = seen;
+ message.mRead = read;
+ return message;
+ }
+
+ /**
+ * Create a message not yet associated with a particular conversation
+ */
+ public static MessageData createSharedMessage(final String messageText) {
+ final MessageData message = new MessageData();
+ message.mStatus = BUGLE_STATUS_OUTGOING_DRAFT;
+ if (!TextUtils.isEmpty(messageText)) {
+ message.mParts.add(MessagePartData.createTextMessagePart(messageText));
+ }
+ return message;
+ }
+
+ /**
+ * Create a message from Sms table fields
+ */
+ public static MessageData createSmsMessage(final String messageUri, final String participantId,
+ final String selfId, final String conversationId, final int bugleStatus,
+ final boolean seen, final boolean read, final long sent,
+ final long recieved, final String messageText) {
+ final MessageData message = new MessageData();
+ message.mParticipantId = participantId;
+ message.mSelfId = selfId;
+ message.mConversationId = conversationId;
+ message.mSentTimestamp = sent;
+ message.mReceivedTimestamp = recieved;
+ message.mSeen = seen;
+ message.mRead = read;
+ message.mProtocol = PROTOCOL_SMS;
+ message.mStatus = bugleStatus;
+ message.mSmsMessageUri = Uri.parse(messageUri);
+ message.mParts.add(MessagePartData.createTextMessagePart(messageText));
+ return message;
+ }
+
+ /**
+ * Create a message from Mms table fields
+ */
+ public static MessageData createMmsMessage(final String messageUri, final String participantId,
+ final String selfId, final String conversationId, final boolean isNotification,
+ final int bugleStatus, final String contentLocation, final String transactionId,
+ final int smsPriority, final String subject, final boolean seen, final boolean read,
+ final long size, final int rawStatus, final long expiry, final long sent,
+ final long received) {
+ final MessageData message = new MessageData();
+ message.mParticipantId = participantId;
+ message.mSelfId = selfId;
+ message.mConversationId = conversationId;
+ message.mSentTimestamp = sent;
+ message.mReceivedTimestamp = received;
+ message.mMmsContentLocation = contentLocation;
+ message.mMmsTransactionId = transactionId;
+ message.mSeen = seen;
+ message.mRead = read;
+ message.mStatus = bugleStatus;
+ message.mProtocol = (isNotification ? PROTOCOL_MMS_PUSH_NOTIFICATION : PROTOCOL_MMS);
+ message.mSmsMessageUri = Uri.parse(messageUri);
+ message.mSmsPriority = smsPriority;
+ message.mSmsMessageSize = size;
+ message.mMmsSubject = subject;
+ message.mMmsExpiry = expiry;
+ message.mRawStatus = rawStatus;
+ if (bugleStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD ||
+ bugleStatus == BUGLE_STATUS_OUTGOING_RESENDING) {
+ // Set the retry start timestamp if this message is already in process of retrying
+ // Either as autodownload is starting or sending already in progress (MMS update)
+ message.mRetryStartTimestamp = received;
+ }
+ return message;
+ }
+
+ public void addPart(final MessagePartData part) {
+ if (part instanceof PendingAttachmentData) {
+ // Pending attachments may only be added to shared message data that's not associated
+ // with any particular conversation, in order to store shared images.
+ Assert.isTrue(mConversationId == null);
+ }
+ mParts.add(part);
+ }
+
+ public Iterable<MessagePartData> getParts() {
+ return mParts;
+ }
+
+ public void bind(final Cursor cursor) {
+ mMessageId = cursor.getString(INDEX_ID);
+ mConversationId = cursor.getString(INDEX_CONVERSATION_ID);
+ mParticipantId = cursor.getString(INDEX_PARTICIPANT_ID);
+ mSelfId = cursor.getString(INDEX_SELF_ID);
+ mSentTimestamp = cursor.getLong(INDEX_SENT_TIMESTAMP);
+ mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
+ mSeen = (cursor.getInt(INDEX_SEEN) != 0);
+ mRead = (cursor.getInt(INDEX_READ) != 0);
+ mProtocol = cursor.getInt(INDEX_PROTOCOL);
+ mStatus = cursor.getInt(INDEX_BUGLE_STATUS);
+ final String smsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
+ mSmsMessageUri = (smsMessageUri == null) ? null : Uri.parse(smsMessageUri);
+ mSmsPriority = cursor.getInt(INDEX_SMS_PRIORITY);
+ mSmsMessageSize = cursor.getLong(INDEX_SMS_MESSAGE_SIZE);
+ mMmsExpiry = cursor.getLong(INDEX_MMS_EXPIRY);
+ mRawStatus = cursor.getInt(INDEX_RAW_TELEPHONY_STATUS);
+ mMmsSubject = cursor.getString(INDEX_MMS_SUBJECT);
+ mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID);
+ mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION);
+ mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP);
+ }
+
+ /**
+ * Bind to the draft message data for a conversation. The conversation's self id is used as
+ * the draft's self id.
+ */
+ public void bindDraft(final Cursor cursor, final String conversationSelfId) {
+ bind(cursor);
+ mSelfId = conversationSelfId;
+ }
+
+ protected static String getParticipantId(final Cursor cursor) {
+ return cursor.getString(INDEX_PARTICIPANT_ID);
+ }
+
+ public void populate(final ContentValues values) {
+ values.put(MessageColumns.CONVERSATION_ID, mConversationId);
+ values.put(MessageColumns.SENDER_PARTICIPANT_ID, mParticipantId);
+ values.put(MessageColumns.SELF_PARTICIPANT_ID, mSelfId);
+ values.put(MessageColumns.SENT_TIMESTAMP, mSentTimestamp);
+ values.put(MessageColumns.RECEIVED_TIMESTAMP, mReceivedTimestamp);
+ values.put(MessageColumns.SEEN, mSeen ? 1 : 0);
+ values.put(MessageColumns.READ, mRead ? 1 : 0);
+ values.put(MessageColumns.PROTOCOL, mProtocol);
+ values.put(MessageColumns.STATUS, mStatus);
+ final String smsMessageUri = ((mSmsMessageUri == null) ? null : mSmsMessageUri.toString());
+ values.put(MessageColumns.SMS_MESSAGE_URI, smsMessageUri);
+ values.put(MessageColumns.SMS_PRIORITY, mSmsPriority);
+ values.put(MessageColumns.SMS_MESSAGE_SIZE, mSmsMessageSize);
+ values.put(MessageColumns.MMS_EXPIRY, mMmsExpiry);
+ values.put(MessageColumns.MMS_SUBJECT, mMmsSubject);
+ values.put(MessageColumns.MMS_TRANSACTION_ID, mMmsTransactionId);
+ values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation);
+ values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus);
+ values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp);
+ }
+
+ /**
+ * Note this is not thread safe so callers need to make sure they own the wrapper + statements
+ * while they call this and use the returned value.
+ */
+ public SQLiteStatement getInsertStatement(final DatabaseWrapper db) {
+ final SQLiteStatement insert = db.getStatementInTransaction(
+ DatabaseWrapper.INDEX_INSERT_MESSAGE, INSERT_MESSAGE_SQL);
+ insert.clearBindings();
+ insert.bindString(INDEX_CONVERSATION_ID, mConversationId);
+ insert.bindString(INDEX_PARTICIPANT_ID, mParticipantId);
+ insert.bindString(INDEX_SELF_ID, mSelfId);
+ insert.bindLong(INDEX_SENT_TIMESTAMP, mSentTimestamp);
+ insert.bindLong(INDEX_RECEIVED_TIMESTAMP, mReceivedTimestamp);
+ insert.bindLong(INDEX_SEEN, mSeen ? 1 : 0);
+ insert.bindLong(INDEX_READ, mRead ? 1 : 0);
+ insert.bindLong(INDEX_PROTOCOL, mProtocol);
+ insert.bindLong(INDEX_BUGLE_STATUS, mStatus);
+ if (mSmsMessageUri != null) {
+ insert.bindString(INDEX_SMS_MESSAGE_URI, mSmsMessageUri.toString());
+ }
+ insert.bindLong(INDEX_SMS_PRIORITY, mSmsPriority);
+ insert.bindLong(INDEX_SMS_MESSAGE_SIZE, mSmsMessageSize);
+ insert.bindLong(INDEX_MMS_EXPIRY, mMmsExpiry);
+ if (mMmsSubject != null) {
+ insert.bindString(INDEX_MMS_SUBJECT, mMmsSubject);
+ }
+ if (mMmsTransactionId != null) {
+ insert.bindString(INDEX_MMS_TRANSACTION_ID, mMmsTransactionId);
+ }
+ if (mMmsContentLocation != null) {
+ insert.bindString(INDEX_MMS_CONTENT_LOCATION, mMmsContentLocation);
+ }
+ insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus);
+ insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp);
+ return insert;
+ }
+
+ public final String getMessageId() {
+ return mMessageId;
+ }
+
+ public final String getConversationId() {
+ return mConversationId;
+ }
+
+ public final String getParticipantId() {
+ return mParticipantId;
+ }
+
+ public final String getSelfId() {
+ return mSelfId;
+ }
+
+ public final long getSentTimeStamp() {
+ return mSentTimestamp;
+ }
+
+ public final long getReceivedTimeStamp() {
+ return mReceivedTimestamp;
+ }
+
+ public final String getFormattedReceivedTimeStamp() {
+ return Dates.getMessageTimeString(mReceivedTimestamp).toString();
+ }
+
+ public final int getProtocol() {
+ return mProtocol;
+ }
+
+ public final int getStatus() {
+ return mStatus;
+ }
+
+ public final Uri getSmsMessageUri() {
+ return mSmsMessageUri;
+ }
+
+ public final int getSmsPriority() {
+ return mSmsPriority;
+ }
+
+ public final long getSmsMessageSize() {
+ return mSmsMessageSize;
+ }
+
+ public final String getMmsSubject() {
+ return mMmsSubject;
+ }
+
+ public final void setMmsSubject(final String subject) {
+ mMmsSubject = subject;
+ }
+
+ public final String getMmsContentLocation() {
+ return mMmsContentLocation;
+ }
+
+ public final String getMmsTransactionId() {
+ return mMmsTransactionId;
+ }
+
+ public final boolean getMessageSeen() {
+ return mSeen;
+ }
+
+ /**
+ * For incoming MMS messages this returns the retrieve-status value
+ * For sent MMS messages this returns the response-status value
+ * See PduHeaders.java for possible values
+ * Otherwise (SMS etc) this is RAW_TELEPHONY_STATUS_UNDEFINED
+ */
+ public final int getRawTelephonyStatus() {
+ return mRawStatus;
+ }
+
+ public final void setMessageSeen(final boolean hasSeen) {
+ mSeen = hasSeen;
+ }
+
+ public final boolean getInResendWindow(final long now) {
+ final long maxAgeToResend = BugleGservices.get().getLong(
+ BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS,
+ BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS_DEFAULT);
+ final long age = now - mRetryStartTimestamp;
+ return age < maxAgeToResend;
+ }
+
+ public final boolean getInDownloadWindow(final long now) {
+ final long maxAgeToRedownload = BugleGservices.get().getLong(
+ BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS,
+ BugleGservicesKeys.MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT);
+ final long age = now - mRetryStartTimestamp;
+ return age < maxAgeToRedownload;
+ }
+
+ static boolean getShowDownloadMessage(final int status) {
+ if (OsUtil.isSecondaryUser()) {
+ // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
+ // primary user.
+ return false;
+ }
+ // Should show option for manual download iff status is manual download or failed
+ return (status == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
+ status == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
+ // If debug is enabled, allow to download an expired or unavailable message.
+ (DebugUtils.isDebugEnabled()
+ && status == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
+ }
+
+ public boolean canDownloadMessage() {
+ if (OsUtil.isSecondaryUser()) {
+ // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
+ // primary user.
+ return false;
+ }
+ // Can download iff status is retrying auto/manual downloading
+ return (mStatus == BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD ||
+ mStatus == BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD);
+ }
+
+ public boolean canRedownloadMessage() {
+ if (OsUtil.isSecondaryUser()) {
+ // Secondary users can't download mms's. Mms's are downloaded by bugle running as the
+ // primary user.
+ return false;
+ }
+ // Can redownload iff status is manual download not started or download failed
+ return (mStatus == BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED ||
+ mStatus == BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD ||
+ // If debug is enabled, allow to download an expired or unavailable message.
+ (DebugUtils.isDebugEnabled()
+ && mStatus == BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE));
+ }
+
+ static boolean getShowResendMessage(final int status) {
+ // Should show option to resend iff status is failed
+ return (status == BUGLE_STATUS_OUTGOING_FAILED);
+ }
+
+ static boolean getOneClickResendMessage(final int status, final int rawStatus) {
+ // Should show option to resend iff status is failed
+ return (status == BUGLE_STATUS_OUTGOING_FAILED
+ && rawStatus == RAW_TELEPHONY_STATUS_UNDEFINED);
+ }
+
+ public boolean canResendMessage() {
+ // Manual retry allowed only from failed
+ return (mStatus == BUGLE_STATUS_OUTGOING_FAILED);
+ }
+
+ public boolean canSendMessage() {
+ // Sending messages must be in yet_to_send or awaiting_retry state
+ return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND ||
+ mStatus == BUGLE_STATUS_OUTGOING_AWAITING_RETRY);
+ }
+
+ public final boolean getYetToSend() {
+ return (mStatus == BUGLE_STATUS_OUTGOING_YET_TO_SEND);
+ }
+
+ public final boolean getIsMms() {
+ return mProtocol == MessageData.PROTOCOL_MMS
+ || mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION;
+ }
+
+ public static final boolean getIsMmsNotification(final int protocol) {
+ return (protocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
+ }
+
+ public final boolean getIsMmsNotification() {
+ return getIsMmsNotification(mProtocol);
+ }
+
+ public static final boolean getIsSms(final int protocol) {
+ return protocol == (MessageData.PROTOCOL_SMS);
+ }
+
+ public final boolean getIsSms() {
+ return getIsSms(mProtocol);
+ }
+
+ public static boolean getIsIncoming(final int status) {
+ return (status >= MessageData.BUGLE_STATUS_FIRST_INCOMING);
+ }
+
+ public boolean getIsIncoming() {
+ return getIsIncoming(mStatus);
+ }
+
+ public long getRetryStartTimestamp() {
+ return mRetryStartTimestamp;
+ }
+
+ public final String getMessageText() {
+ final String separator = System.getProperty("line.separator");
+ final StringBuilder text = new StringBuilder();
+ for (final MessagePartData part : mParts) {
+ if (!part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
+ if (text.length() > 0) {
+ text.append(separator);
+ }
+ text.append(part.getText());
+ }
+ }
+ return text.toString();
+ }
+
+ /**
+ * Takes all captions from attachments and adds them as a prefix to the first text part or
+ * appends a text part
+ */
+ public final void consolidateText() {
+ final String separator = System.getProperty("line.separator");
+ final StringBuilder captionText = new StringBuilder();
+ MessagePartData firstTextPart = null;
+ int firstTextPartIndex = -1;
+ for (int i = 0; i < mParts.size(); i++) {
+ final MessagePartData part = mParts.get(i);
+ if (firstTextPart == null && !part.isAttachment()) {
+ firstTextPart = part;
+ firstTextPartIndex = i;
+ }
+ if (part.isAttachment() && !TextUtils.isEmpty(part.getText())) {
+ if (captionText.length() > 0) {
+ captionText.append(separator);
+ }
+ captionText.append(part.getText());
+ }
+ }
+
+ if (captionText.length() == 0) {
+ // Nothing to consolidate
+ return;
+ }
+
+ if (firstTextPart == null) {
+ addPart(MessagePartData.createTextMessagePart(captionText.toString()));
+ } else {
+ final String partText = firstTextPart.getText();
+ if (partText.length() > 0) {
+ captionText.append(separator);
+ captionText.append(partText);
+ }
+ mParts.set(firstTextPartIndex,
+ MessagePartData.createTextMessagePart(captionText.toString()));
+ }
+ }
+
+ public final MessagePartData getFirstAttachment() {
+ for (final MessagePartData part : mParts) {
+ if (part.isAttachment()) {
+ return part;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Updates the messageId for this message.
+ * Can be used to reset the messageId prior to persisting (which will assign a new messageId)
+ * or can be called on a message that does not yet have a valid messageId to set it.
+ */
+ public void updateMessageId(final String messageId) {
+ Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
+ mMessageId = messageId;
+
+ // TODO : This should probably also call updateMessageId on the message parts. We
+ // may also want to make messages effectively immutable once they have a valid message id.
+ }
+
+ public final void updateSendingMessage(final String conversationId, final Uri messageUri,
+ final long timestamp) {
+ mConversationId = conversationId;
+ mSmsMessageUri = messageUri;
+ mRead = true;
+ mSeen = true;
+ mReceivedTimestamp = timestamp;
+ mSentTimestamp = timestamp;
+ mStatus = BUGLE_STATUS_OUTGOING_YET_TO_SEND;
+ mRetryStartTimestamp = timestamp;
+ }
+
+ public final void markMessageManualResend(final long timestamp) {
+ // Manual send updates timestamp and transitions back to initial sending status.
+ mReceivedTimestamp = timestamp;
+ mSentTimestamp = timestamp;
+ mStatus = BUGLE_STATUS_OUTGOING_SENDING;
+ }
+
+ public final void markMessageSending(final long timestamp) {
+ // Initial send
+ mStatus = BUGLE_STATUS_OUTGOING_SENDING;
+ mSentTimestamp = timestamp;
+ }
+
+ public final void markMessageResending(final long timestamp) {
+ // Auto resend of message
+ mStatus = BUGLE_STATUS_OUTGOING_RESENDING;
+ mSentTimestamp = timestamp;
+ }
+
+ public final void markMessageSent(final long timestamp) {
+ mSentTimestamp = timestamp;
+ mStatus = BUGLE_STATUS_OUTGOING_COMPLETE;
+ }
+
+ public final void markMessageFailed(final long timestamp) {
+ mSentTimestamp = timestamp;
+ mStatus = BUGLE_STATUS_OUTGOING_FAILED;
+ }
+
+ public final void markMessageFailedEmergencyNumber(final long timestamp) {
+ mSentTimestamp = timestamp;
+ mStatus = BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
+ }
+
+ public final void markMessageNotSent(final long timestamp) {
+ mSentTimestamp = timestamp;
+ mStatus = BUGLE_STATUS_OUTGOING_AWAITING_RETRY;
+ }
+
+ public final void updateSizesForImageParts() {
+ for (final MessagePartData part : getParts()) {
+ part.decodeAndSaveSizeIfImage(false /* saveToStorage */);
+ }
+ }
+
+ public final void setRetryStartTimestamp(final long timestamp) {
+ mRetryStartTimestamp = timestamp;
+ }
+
+ public final void setRawTelephonyStatus(final int rawStatus) {
+ mRawStatus = rawStatus;
+ }
+
+ public boolean hasContent() {
+ return !TextUtils.isEmpty(mMmsSubject) ||
+ getFirstAttachment() != null ||
+ !TextUtils.isEmpty(getMessageText());
+ }
+
+ public final void bindSelfId(final String selfId) {
+ mSelfId = selfId;
+ }
+
+ public final void bindParticipantId(final String participantId) {
+ mParticipantId = participantId;
+ }
+
+ protected MessageData(final Parcel in) {
+ mMessageId = in.readString();
+ mConversationId = in.readString();
+ mParticipantId = in.readString();
+ mSelfId = in.readString();
+ mSentTimestamp = in.readLong();
+ mReceivedTimestamp = in.readLong();
+ mSeen = (in.readInt() != 0);
+ mRead = (in.readInt() != 0);
+ mProtocol = in.readInt();
+ mStatus = in.readInt();
+ final String smsMessageUri = in.readString();
+ mSmsMessageUri = (smsMessageUri == null ? null : Uri.parse(smsMessageUri));
+ mSmsPriority = in.readInt();
+ mSmsMessageSize = in.readLong();
+ mMmsExpiry = in.readLong();
+ mMmsSubject = in.readString();
+ mMmsTransactionId = in.readString();
+ mMmsContentLocation = in.readString();
+ mRawStatus = in.readInt();
+ mRetryStartTimestamp = in.readLong();
+
+ // Read parts
+ mParts = new ArrayList<MessagePartData>();
+ final int partCount = in.readInt();
+ for (int i = 0; i < partCount; i++) {
+ mParts.add((MessagePartData) in.readParcelable(MessagePartData.class.getClassLoader()));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeString(mMessageId);
+ dest.writeString(mConversationId);
+ dest.writeString(mParticipantId);
+ dest.writeString(mSelfId);
+ dest.writeLong(mSentTimestamp);
+ dest.writeLong(mReceivedTimestamp);
+ dest.writeInt(mRead ? 1 : 0);
+ dest.writeInt(mSeen ? 1 : 0);
+ dest.writeInt(mProtocol);
+ dest.writeInt(mStatus);
+ final String smsMessageUri = (mSmsMessageUri == null) ? null : mSmsMessageUri.toString();
+ dest.writeString(smsMessageUri);
+ dest.writeInt(mSmsPriority);
+ dest.writeLong(mSmsMessageSize);
+ dest.writeLong(mMmsExpiry);
+ dest.writeString(mMmsSubject);
+ dest.writeString(mMmsTransactionId);
+ dest.writeString(mMmsContentLocation);
+ dest.writeInt(mRawStatus);
+ dest.writeLong(mRetryStartTimestamp);
+
+ // Write parts
+ dest.writeInt(mParts.size());
+ for (final MessagePartData messagePartData : mParts) {
+ dest.writeParcelable(messagePartData, flags);
+ }
+ }
+
+ public static final Parcelable.Creator<MessageData> CREATOR
+ = new Parcelable.Creator<MessageData>() {
+ @Override
+ public MessageData createFromParcel(final Parcel in) {
+ return new MessageData(in);
+ }
+
+ @Override
+ public MessageData[] newArray(final int size) {
+ return new MessageData[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return toString(mMessageId, mParts);
+ }
+
+ public static String toString(String messageId, List<MessagePartData> parts) {
+ StringBuilder sb = new StringBuilder();
+ if (messageId != null) {
+ sb.append(messageId);
+ sb.append(": ");
+ }
+ for (MessagePartData part : parts) {
+ sb.append(part.toString());
+ sb.append(" ");
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/MessagePartData.java b/src/com/android/messaging/datamodel/data/MessagePartData.java
new file mode 100644
index 0000000..fffaca8
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/MessagePartData.java
@@ -0,0 +1,534 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteStatement;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.GifTranscoder;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.UriUtil;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents a single message part. Messages consist of one or more parts which may contain
+ * either text or media.
+ */
+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 };
+
+ private static final String[] sProjection = {
+ PartColumns._ID,
+ PartColumns.MESSAGE_ID,
+ PartColumns.TEXT,
+ PartColumns.CONTENT_URI,
+ PartColumns.CONTENT_TYPE,
+ PartColumns.WIDTH,
+ PartColumns.HEIGHT,
+ };
+
+ private static final int INDEX_ID = 0;
+ private static final int INDEX_MESSAGE_ID = 1;
+ private static final int INDEX_TEXT = 2;
+ private static final int INDEX_CONTENT_URI = 3;
+ private static final int INDEX_CONTENT_TYPE = 4;
+ private static final int INDEX_WIDTH = 5;
+ private static final int INDEX_HEIGHT = 6;
+ // This isn't part of the projection
+ private static final int INDEX_CONVERSATION_ID = 7;
+
+ // SQL statement to insert a "complete" message part row (columns based on projection above).
+ private static final String INSERT_MESSAGE_PART_SQL =
+ "INSERT INTO " + DatabaseHelper.PARTS_TABLE + " ( "
+ + TextUtils.join(",", Arrays.copyOfRange(sProjection, 1, INDEX_CONVERSATION_ID))
+ + ", " + PartColumns.CONVERSATION_ID
+ + ") VALUES (?, ?, ?, ?, ?, ?, ?)";
+
+ // Used for stuff that's ignored or arbitrarily compressed.
+ private static final long NO_MINIMUM_SIZE = 0;
+
+ private String mPartId;
+ private String mMessageId;
+ private String mText;
+ private Uri mContentUri;
+ private String mContentType;
+ private int mWidth;
+ private int mHeight;
+ // This kind of part can only be attached once and with no other attachment
+ private boolean mSinglePartOnly;
+
+ /** Transient data: true if destroy was already called */
+ private boolean mDestroyed;
+
+ /**
+ * Create an "empty" message part
+ */
+ protected MessagePartData() {
+ this(null, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE);
+ }
+
+ /**
+ * Create a populated text message part
+ */
+ protected MessagePartData(final String messageText) {
+ this(null, messageText, ContentType.TEXT_PLAIN, null, UNSPECIFIED_SIZE, UNSPECIFIED_SIZE,
+ false /*singlePartOnly*/);
+ }
+
+ /**
+ * Create a populated attachment message part
+ */
+ protected MessagePartData(final String contentType, final Uri contentUri,
+ final int width, final int height) {
+ this(null, null, contentType, contentUri, width, height, false /*singlePartOnly*/);
+ }
+
+ /**
+ * Create a populated attachment message part, with additional caption text
+ */
+ protected MessagePartData(final String messageText, final String contentType,
+ final Uri contentUri, final int width, final int height) {
+ this(null, messageText, contentType, contentUri, width, height, false /*singlePartOnly*/);
+ }
+
+ /**
+ * Create a populated attachment message part, with additional caption text, single part only
+ */
+ protected MessagePartData(final String messageText, final String contentType,
+ final Uri contentUri, final int width, final int height, final boolean singlePartOnly) {
+ this(null, messageText, contentType, contentUri, width, height, singlePartOnly);
+ }
+
+ /**
+ * Create a populated message part
+ */
+ private MessagePartData(final String messageId, final String messageText,
+ final String contentType, final Uri contentUri, final int width, final int height,
+ final boolean singlePartOnly) {
+ mMessageId = messageId;
+ mText = messageText;
+ mContentType = contentType;
+ mContentUri = contentUri;
+ mWidth = width;
+ mHeight = height;
+ mSinglePartOnly = singlePartOnly;
+ }
+
+ /**
+ * Create a "text" message part
+ */
+ public static MessagePartData createTextMessagePart(final String messageText) {
+ return new MessagePartData(messageText);
+ }
+
+ /**
+ * Create a "media" message part
+ */
+ public static MessagePartData createMediaMessagePart(final String contentType,
+ final Uri contentUri, final int width, final int height) {
+ return new MessagePartData(contentType, contentUri, width, height);
+ }
+
+ /**
+ * Create a "media" message part with caption
+ */
+ public static MessagePartData createMediaMessagePart(final String caption,
+ final String contentType, final Uri contentUri, final int width, final int height) {
+ return new MessagePartData(null, caption, contentType, contentUri, width, height,
+ false /*singlePartOnly*/
+ );
+ }
+
+ /**
+ * Create an empty "text" message part
+ */
+ public static MessagePartData createEmptyMessagePart() {
+ return new MessagePartData("");
+ }
+
+ /**
+ * Creates a new message part reading from the cursor
+ */
+ public static MessagePartData createFromCursor(final Cursor cursor) {
+ final MessagePartData part = new MessagePartData();
+ part.bind(cursor);
+ return part;
+ }
+
+ public static String[] getProjection() {
+ return sProjection;
+ }
+
+ /**
+ * Updates the part id.
+ * Can be used to reset the partId just prior to persisting (which will assign a new partId)
+ * or can be called on a part that does not yet have a valid part id to set it.
+ */
+ public void updatePartId(final String partId) {
+ Assert.isTrue(TextUtils.isEmpty(partId) || TextUtils.isEmpty(mPartId));
+ mPartId = partId;
+ }
+
+ /**
+ * Updates the messageId for the part.
+ * Can be used to reset the messageId prior to persisting (which will assign a new messageId)
+ * or can be called on a part that does not yet have a valid messageId to set it.
+ */
+ public void updateMessageId(final String messageId) {
+ Assert.isTrue(TextUtils.isEmpty(messageId) || TextUtils.isEmpty(mMessageId));
+ mMessageId = messageId;
+ }
+
+ protected static String getMessageId(final Cursor cursor) {
+ return cursor.getString(INDEX_MESSAGE_ID);
+ }
+
+ protected void bind(final Cursor cursor) {
+ mPartId = cursor.getString(INDEX_ID);
+ mMessageId = cursor.getString(INDEX_MESSAGE_ID);
+ mText = cursor.getString(INDEX_TEXT);
+ mContentUri = UriUtil.uriFromString(cursor.getString(INDEX_CONTENT_URI));
+ mContentType = cursor.getString(INDEX_CONTENT_TYPE);
+ mWidth = cursor.getInt(INDEX_WIDTH);
+ mHeight = cursor.getInt(INDEX_HEIGHT);
+ }
+
+ public final void populate(final ContentValues values) {
+ // Must have a valid messageId on a part
+ Assert.isTrue(!TextUtils.isEmpty(mMessageId));
+ values.put(PartColumns.MESSAGE_ID, mMessageId);
+ values.put(PartColumns.TEXT, mText);
+ values.put(PartColumns.CONTENT_URI, UriUtil.stringFromUri(mContentUri));
+ values.put(PartColumns.CONTENT_TYPE, mContentType);
+ if (mWidth != UNSPECIFIED_SIZE) {
+ values.put(PartColumns.WIDTH, mWidth);
+ }
+ if (mHeight != UNSPECIFIED_SIZE) {
+ values.put(PartColumns.HEIGHT, mHeight);
+ }
+ }
+
+ /**
+ * Note this is not thread safe so callers need to make sure they own the wrapper + statements
+ * while they call this and use the returned value.
+ */
+ public SQLiteStatement getInsertStatement(final DatabaseWrapper db,
+ final String conversationId) {
+ final SQLiteStatement insert = db.getStatementInTransaction(
+ DatabaseWrapper.INDEX_INSERT_MESSAGE_PART, INSERT_MESSAGE_PART_SQL);
+ insert.clearBindings();
+ insert.bindString(INDEX_MESSAGE_ID, mMessageId);
+ if (mText != null) {
+ insert.bindString(INDEX_TEXT, mText);
+ }
+ if (mContentUri != null) {
+ insert.bindString(INDEX_CONTENT_URI, mContentUri.toString());
+ }
+ if (mContentType != null) {
+ insert.bindString(INDEX_CONTENT_TYPE, mContentType);
+ }
+ insert.bindLong(INDEX_WIDTH, mWidth);
+ insert.bindLong(INDEX_HEIGHT, mHeight);
+ insert.bindString(INDEX_CONVERSATION_ID, conversationId);
+ return insert;
+ }
+
+ public final String getPartId() {
+ return mPartId;
+ }
+
+ public final String getMessageId() {
+ return mMessageId;
+ }
+
+ public final String getText() {
+ return mText;
+ }
+
+ public final Uri getContentUri() {
+ return mContentUri;
+ }
+
+ public boolean isAttachment() {
+ return mContentUri != null;
+ }
+
+ public boolean isText() {
+ return ContentType.isTextType(mContentType);
+ }
+
+ public boolean isImage() {
+ return ContentType.isImageType(mContentType);
+ }
+
+ public boolean isMedia() {
+ return ContentType.isMediaType(mContentType);
+ }
+
+ public boolean isVCard() {
+ return ContentType.isVCardType(mContentType);
+ }
+
+ public boolean isAudio() {
+ return ContentType.isAudioType(mContentType);
+ }
+
+ public boolean isVideo() {
+ return ContentType.isVideoType(mContentType);
+ }
+
+ public final String getContentType() {
+ return mContentType;
+ }
+
+ public final int getWidth() {
+ return mWidth;
+ }
+
+ public final int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ *
+ * @return true if this part can only exist by itself, with no other attachments
+ */
+ public boolean getSinglePartOnly() {
+ return mSinglePartOnly;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ protected MessagePartData(final Parcel in) {
+ mMessageId = in.readString();
+ mText = in.readString();
+ mContentUri = UriUtil.uriFromString(in.readString());
+ mContentType = in.readString();
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ Assert.isTrue(!mDestroyed);
+ dest.writeString(mMessageId);
+ dest.writeString(mText);
+ dest.writeString(UriUtil.stringFromUri(mContentUri));
+ dest.writeString(mContentType);
+ dest.writeInt(mWidth);
+ dest.writeInt(mHeight);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof MessagePartData)) {
+ return false;
+ }
+
+ MessagePartData lhs = (MessagePartData) o;
+ return mWidth == lhs.mWidth && mHeight == lhs.mHeight &&
+ TextUtils.equals(mMessageId, lhs.mMessageId) &&
+ TextUtils.equals(mText, lhs.mText) &&
+ TextUtils.equals(mContentType, lhs.mContentType) &&
+ (mContentUri == null ? lhs.mContentUri == null
+ : mContentUri.equals(lhs.mContentUri));
+ }
+
+ @Override public int hashCode() {
+ int result = 17;
+ result = 31 * result + mWidth;
+ result = 31 * result + mHeight;
+ result = 31 * result + (mMessageId == null ? 0 : mMessageId.hashCode());
+ result = 31 * result + (mText == null ? 0 : mText.hashCode());
+ result = 31 * result + (mContentType == null ? 0 : mContentType.hashCode());
+ result = 31 * result + (mContentUri == null ? 0 : mContentUri.hashCode());
+ return result;
+ }
+
+ public static final Parcelable.Creator<MessagePartData> CREATOR
+ = new Parcelable.Creator<MessagePartData>() {
+ @Override
+ public MessagePartData createFromParcel(final Parcel in) {
+ return new MessagePartData(in);
+ }
+
+ @Override
+ public MessagePartData[] newArray(final int size) {
+ return new MessagePartData[size];
+ }
+ };
+
+ protected Uri shouldDestroy() {
+ // We should never double-destroy.
+ Assert.isTrue(!mDestroyed);
+ mDestroyed = true;
+ Uri contentUri = mContentUri;
+ mContentUri = null;
+ mContentType = null;
+ // Only destroy the image if it's staged in our scratch space.
+ if (!MediaScratchFileProvider.isMediaScratchSpaceUri(contentUri)) {
+ contentUri = null;
+ }
+ return contentUri;
+ }
+
+ /**
+ * If application owns content associated with this part delete it (on background thread)
+ */
+ public void destroyAsync() {
+ final Uri contentUri = shouldDestroy();
+ if (contentUri != null) {
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ Factory.get().getApplicationContext().getContentResolver().delete(
+ contentUri, null, null);
+ }
+ });
+ }
+ }
+
+ /**
+ * If application owns content associated with this part delete it
+ */
+ public void destroySync() {
+ final Uri contentUri = shouldDestroy();
+ if (contentUri != null) {
+ Factory.get().getApplicationContext().getContentResolver().delete(
+ contentUri, null, null);
+ }
+ }
+
+ /**
+ * If this is an image part, decode the image header and potentially save the size to the db.
+ */
+ public void decodeAndSaveSizeIfImage(final boolean saveToStorage) {
+ if (isImage()) {
+ final Rect imageSize = ImageUtils.decodeImageBounds(
+ Factory.get().getApplicationContext(), mContentUri);
+ if (imageSize.width() != ImageRequest.UNSPECIFIED_SIZE &&
+ imageSize.height() != ImageRequest.UNSPECIFIED_SIZE) {
+ mWidth = imageSize.width();
+ mHeight = imageSize.height();
+ if (saveToStorage) {
+ UpdateMessagePartSizeAction.updateSize(mPartId, mWidth, mHeight);
+ }
+ }
+ }
+ }
+
+ /**
+ * Computes the minimum size that this MessagePartData could be compressed/downsampled/encoded
+ * before sending to meet the maximum message size imposed by the carriers. This is used to
+ * determine right before sending a message whether a message could possibly be sent. If not
+ * then the user is given a chance to unselect some/all of the attachments.
+ *
+ * TODO: computing the minimum size could be expensive. Should we cache the
+ * computed value in db to be retrieved later?
+ *
+ * @return the carrier-independent minimum size, in bytes.
+ */
+ @DoesNotRunOnMainThread
+ public long getMinimumSizeInBytesForSending() {
+ Assert.isNotMainThread();
+ if (!isAttachment()) {
+ // No limit is imposed on non-attachment part (i.e. plain text), so treat it as zero.
+ return NO_MINIMUM_SIZE;
+ } else if (isImage()) {
+ // GIFs are resized by the native transcoder (exposed by GifTranscoder).
+ if (ImageUtils.isGif(mContentType, mContentUri)) {
+ final long originalImageSize = UriUtil.getContentSize(mContentUri);
+ // Wish we could save the size here, but we don't have a part id yet
+ decodeAndSaveSizeIfImage(false /* saveToStorage */);
+ return GifTranscoder.canBeTranscoded(mWidth, mHeight) ?
+ GifTranscoder.estimateFileSizeAfterTranscode(originalImageSize)
+ : originalImageSize;
+ }
+ // Other images should be arbitrarily resized by ImageResizer before sending.
+ return MmsUtils.MIN_IMAGE_BYTE_SIZE;
+ } else if (isAudio()) {
+ // Audios are already recorded with the lowest sampling settings (AMR_NB), so just
+ // return the file size as the minimum size.
+ return UriUtil.getContentSize(mContentUri);
+ } else if (isVideo()) {
+ final int mediaDurationMs = UriUtil.getMediaDurationMs(mContentUri);
+ return MmsUtils.MIN_VIDEO_BYTES_PER_SECOND * mediaDurationMs
+ / TimeUnit.SECONDS.toMillis(1);
+ } else if (isVCard()) {
+ // We can't compress vCards.
+ return UriUtil.getContentSize(mContentUri);
+ } else {
+ // This is some unknown media type that we don't know how to handle. Log an error
+ // and try sending it anyway.
+ LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "Unknown attachment type " + getContentType());
+ return NO_MINIMUM_SIZE;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (isText()) {
+ return LogUtil.sanitizePII(getText());
+ } else {
+ return getContentType() + " (" + getContentUri() + ")";
+ }
+ }
+
+ /**
+ *
+ * @return true if this part can only exist by itself, with no other attachments
+ */
+ public boolean isSinglePartOnly() {
+ return mSinglePartOnly;
+ }
+
+ public void setSinglePartOnly(final boolean isSinglePartOnly) {
+ mSinglePartOnly = isSinglePartOnly;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ParticipantData.java b/src/com/android/messaging/datamodel/data/ParticipantData.java
new file mode 100644
index 0000000..521c354
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ParticipantData.java
@@ -0,0 +1,569 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v7.mms.MmsManager;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.TextUtil;
+
+/**
+ * A class that encapsulates all of the data for a specific participant in a conversation.
+ */
+public class ParticipantData implements Parcelable {
+ // We always use -1 as default/invalid sub id although system may give us anything negative
+ public static final int DEFAULT_SELF_SUB_ID = MmsManager.DEFAULT_SUB_ID;
+
+ // This needs to be something apart from valid or DEFAULT_SELF_SUB_ID
+ public static final int OTHER_THAN_SELF_SUB_ID = DEFAULT_SELF_SUB_ID - 1;
+
+ // Active slot ids are non-negative. Using -1 to designate to inactive self participants.
+ public static final int INVALID_SLOT_ID = -1;
+
+ // TODO: may make sense to move this to common place?
+ public static final long PARTICIPANT_CONTACT_ID_NOT_RESOLVED = -1;
+ public static final long PARTICIPANT_CONTACT_ID_NOT_FOUND = -2;
+
+ public static class ParticipantsQuery {
+ public static final String[] PROJECTION = new String[] {
+ ParticipantColumns._ID,
+ ParticipantColumns.SUB_ID,
+ ParticipantColumns.SIM_SLOT_ID,
+ ParticipantColumns.NORMALIZED_DESTINATION,
+ ParticipantColumns.SEND_DESTINATION,
+ ParticipantColumns.DISPLAY_DESTINATION,
+ ParticipantColumns.FULL_NAME,
+ ParticipantColumns.FIRST_NAME,
+ ParticipantColumns.PROFILE_PHOTO_URI,
+ ParticipantColumns.CONTACT_ID,
+ ParticipantColumns.LOOKUP_KEY,
+ ParticipantColumns.BLOCKED,
+ ParticipantColumns.SUBSCRIPTION_COLOR,
+ ParticipantColumns.SUBSCRIPTION_NAME,
+ ParticipantColumns.CONTACT_DESTINATION,
+ };
+
+ public static final int INDEX_ID = 0;
+ public static final int INDEX_SUB_ID = 1;
+ public static final int INDEX_SIM_SLOT_ID = 2;
+ public static final int INDEX_NORMALIZED_DESTINATION = 3;
+ public static final int INDEX_SEND_DESTINATION = 4;
+ public static final int INDEX_DISPLAY_DESTINATION = 5;
+ public static final int INDEX_FULL_NAME = 6;
+ public static final int INDEX_FIRST_NAME = 7;
+ public static final int INDEX_PROFILE_PHOTO_URI = 8;
+ public static final int INDEX_CONTACT_ID = 9;
+ public static final int INDEX_LOOKUP_KEY = 10;
+ public static final int INDEX_BLOCKED = 11;
+ public static final int INDEX_SUBSCRIPTION_COLOR = 12;
+ public static final int INDEX_SUBSCRIPTION_NAME = 13;
+ public static final int INDEX_CONTACT_DESTINATION = 14;
+ }
+
+ /**
+ * @return The MMS unknown sender participant entity
+ */
+ public static String getUnknownSenderDestination() {
+ // This is a hard coded string rather than a localized one because we don't want it to
+ // change when you change locale.
+ return "\u02BCUNKNOWN_SENDER!\u02BC";
+ }
+
+ private String mParticipantId;
+ private int mSubId;
+ private int mSlotId;
+ private String mNormalizedDestination;
+ private String mSendDestination;
+ private String mDisplayDestination;
+ private String mContactDestination;
+ private String mFullName;
+ private String mFirstName;
+ private String mProfilePhotoUri;
+ private long mContactId;
+ private String mLookupKey;
+ private int mSubscriptionColor;
+ private String mSubscriptionName;
+ private boolean mIsEmailAddress;
+ private boolean mBlocked;
+
+ // Don't call constructor directly
+ private ParticipantData() {
+ }
+
+ public static ParticipantData getFromCursor(final Cursor cursor) {
+ final ParticipantData pd = new ParticipantData();
+ pd.mParticipantId = cursor.getString(ParticipantsQuery.INDEX_ID);
+ pd.mSubId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
+ pd.mSlotId = cursor.getInt(ParticipantsQuery.INDEX_SIM_SLOT_ID);
+ pd.mNormalizedDestination = cursor.getString(
+ ParticipantsQuery.INDEX_NORMALIZED_DESTINATION);
+ pd.mSendDestination = cursor.getString(ParticipantsQuery.INDEX_SEND_DESTINATION);
+ pd.mDisplayDestination = cursor.getString(ParticipantsQuery.INDEX_DISPLAY_DESTINATION);
+ pd.mContactDestination = cursor.getString(ParticipantsQuery.INDEX_CONTACT_DESTINATION);
+ pd.mFullName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
+ pd.mFirstName = cursor.getString(ParticipantsQuery.INDEX_FIRST_NAME);
+ pd.mProfilePhotoUri = cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
+ pd.mContactId = cursor.getLong(ParticipantsQuery.INDEX_CONTACT_ID);
+ pd.mLookupKey = cursor.getString(ParticipantsQuery.INDEX_LOOKUP_KEY);
+ pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
+ pd.mBlocked = cursor.getInt(ParticipantsQuery.INDEX_BLOCKED) != 0;
+ pd.mSubscriptionColor = cursor.getInt(ParticipantsQuery.INDEX_SUBSCRIPTION_COLOR);
+ pd.mSubscriptionName = cursor.getString(ParticipantsQuery.INDEX_SUBSCRIPTION_NAME);
+ pd.maybeSetupUnknownSender();
+ return pd;
+ }
+
+ public static ParticipantData getFromId(final DatabaseWrapper dbWrapper,
+ final String participantId) {
+ Cursor cursor = null;
+ try {
+ cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantsQuery.PROJECTION,
+ ParticipantColumns._ID + " =?",
+ new String[] { participantId }, null, null, null);
+
+ if (cursor.moveToFirst()) {
+ return ParticipantData.getFromCursor(cursor);
+ } else {
+ return null;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ public static ParticipantData getFromRecipientEntry(final RecipientEntry recipientEntry) {
+ final ParticipantData pd = new ParticipantData();
+ pd.mParticipantId = null;
+ pd.mSubId = OTHER_THAN_SELF_SUB_ID;
+ pd.mSlotId = INVALID_SLOT_ID;
+ pd.mSendDestination = TextUtil.replaceUnicodeDigits(recipientEntry.getDestination());
+ pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
+ pd.mNormalizedDestination = pd.mIsEmailAddress ?
+ pd.mSendDestination :
+ PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
+ pd.mDisplayDestination = pd.mIsEmailAddress ?
+ pd.mNormalizedDestination :
+ PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
+ pd.mFullName = recipientEntry.getDisplayName();
+ pd.mFirstName = null;
+ pd.mProfilePhotoUri = (recipientEntry.getPhotoThumbnailUri() == null) ? null :
+ recipientEntry.getPhotoThumbnailUri().toString();
+ pd.mContactId = recipientEntry.getContactId();
+ if (pd.mContactId < 0) {
+ // ParticipantData only supports real contact ids (>=0) based on faith that the contacts
+ // provider will continue to only use non-negative ids. The UI uses contactId < 0 for
+ // special handling. We convert those to 'not resolved'
+ pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
+ }
+ pd.mLookupKey = recipientEntry.getLookupKey();
+ pd.mBlocked = false;
+ pd.mSubscriptionColor = Color.TRANSPARENT;
+ pd.mSubscriptionName = null;
+ pd.maybeSetupUnknownSender();
+ return pd;
+ }
+
+ // Shared code for getFromRawPhoneBySystemLocale and getFromRawPhoneBySimLocale
+ private static ParticipantData getFromRawPhone(final String phoneNumber) {
+ Assert.isTrue(phoneNumber != null);
+ final ParticipantData pd = new ParticipantData();
+ pd.mParticipantId = null;
+ pd.mSubId = OTHER_THAN_SELF_SUB_ID;
+ pd.mSlotId = INVALID_SLOT_ID;
+ pd.mSendDestination = TextUtil.replaceUnicodeDigits(phoneNumber);
+ pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
+ pd.mFullName = null;
+ pd.mFirstName = null;
+ pd.mProfilePhotoUri = null;
+ pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
+ pd.mLookupKey = null;
+ pd.mBlocked = false;
+ pd.mSubscriptionColor = Color.TRANSPARENT;
+ pd.mSubscriptionName = null;
+ return pd;
+ }
+
+ /**
+ * Get an instance from a raw phone number and using system locale to normalize it.
+ *
+ * Use this when creating a participant that is for displaying UI and not associated
+ * with a specific SIM. For example, when creating a conversation using user entered
+ * phone number.
+ *
+ * @param phoneNumber The raw phone number
+ * @return instance
+ */
+ public static ParticipantData getFromRawPhoneBySystemLocale(final String phoneNumber) {
+ final ParticipantData pd = getFromRawPhone(phoneNumber);
+ pd.mNormalizedDestination = pd.mIsEmailAddress ?
+ pd.mSendDestination :
+ PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
+ pd.mDisplayDestination = pd.mIsEmailAddress ?
+ pd.mNormalizedDestination :
+ PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
+ pd.maybeSetupUnknownSender();
+ return pd;
+ }
+
+ /**
+ * Get an instance from a raw phone number and using SIM or system locale to normalize it.
+ *
+ * Use this when creating a participant that is associated with a specific SIM. For example,
+ * the sender of a received message or the recipient of a sending message that is already
+ * targeted at a specific SIM.
+ *
+ * @param phoneNumber The raw phone number
+ * @return instance
+ */
+ public static ParticipantData getFromRawPhoneBySimLocale(
+ final String phoneNumber, final int subId) {
+ final ParticipantData pd = getFromRawPhone(phoneNumber);
+ pd.mNormalizedDestination = pd.mIsEmailAddress ?
+ pd.mSendDestination :
+ PhoneUtils.get(subId).getCanonicalBySimLocale(pd.mSendDestination);
+ pd.mDisplayDestination = pd.mIsEmailAddress ?
+ pd.mNormalizedDestination :
+ PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
+ pd.maybeSetupUnknownSender();
+ return pd;
+ }
+
+ public static ParticipantData getSelfParticipant(final int subId) {
+ Assert.isTrue(subId != OTHER_THAN_SELF_SUB_ID);
+ final ParticipantData pd = new ParticipantData();
+ pd.mParticipantId = null;
+ pd.mSubId = subId;
+ pd.mSlotId = INVALID_SLOT_ID;
+ pd.mIsEmailAddress = false;
+ pd.mSendDestination = null;
+ pd.mNormalizedDestination = null;
+ pd.mDisplayDestination = null;
+ pd.mFullName = null;
+ pd.mFirstName = null;
+ pd.mProfilePhotoUri = null;
+ pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
+ pd.mLookupKey = null;
+ pd.mBlocked = false;
+ pd.mSubscriptionColor = Color.TRANSPARENT;
+ pd.mSubscriptionName = null;
+ return pd;
+ }
+
+ private void maybeSetupUnknownSender() {
+ if (isUnknownSender()) {
+ // Because your locale may change, we setup the display string for the unknown sender
+ // on the fly rather than relying on the version in the database.
+ final Resources resources = Factory.get().getApplicationContext().getResources();
+ mDisplayDestination = resources.getString(R.string.unknown_sender);
+ mFullName = mDisplayDestination;
+ }
+ }
+
+ public String getNormalizedDestination() {
+ return mNormalizedDestination;
+ }
+
+ public String getSendDestination() {
+ return mSendDestination;
+ }
+
+ public String getDisplayDestination() {
+ return mDisplayDestination;
+ }
+
+ public String getContactDestination() {
+ return mContactDestination;
+ }
+
+ public String getFullName() {
+ return mFullName;
+ }
+
+ public String getFirstName() {
+ return mFirstName;
+ }
+
+ public String getDisplayName(final boolean preferFullName) {
+ if (preferFullName) {
+ // Prefer full name over first name
+ if (!TextUtils.isEmpty(mFullName)) {
+ return mFullName;
+ }
+ if (!TextUtils.isEmpty(mFirstName)) {
+ return mFirstName;
+ }
+ } else {
+ // Prefer first name over full name
+ if (!TextUtils.isEmpty(mFirstName)) {
+ return mFirstName;
+ }
+ if (!TextUtils.isEmpty(mFullName)) {
+ return mFullName;
+ }
+ }
+
+ // Fallback to the display destination
+ if (!TextUtils.isEmpty(mDisplayDestination)) {
+ return mDisplayDestination;
+ }
+
+ return Factory.get().getApplicationContext().getResources().getString(
+ R.string.unknown_sender);
+ }
+
+ public String getProfilePhotoUri() {
+ return mProfilePhotoUri;
+ }
+
+ public long getContactId() {
+ return mContactId;
+ }
+
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+
+ public boolean updatePhoneNumberForSelfIfChanged() {
+ final String phoneNumber =
+ PhoneUtils.get(mSubId).getCanonicalForSelf(true/*allowOverride*/);
+ boolean changed = false;
+ if (isSelf() && !TextUtils.equals(phoneNumber, mNormalizedDestination)) {
+ mNormalizedDestination = phoneNumber;
+ mSendDestination = phoneNumber;
+ mDisplayDestination = mIsEmailAddress ?
+ phoneNumber :
+ PhoneUtils.getDefault().formatForDisplay(phoneNumber);
+ changed = true;
+ }
+ return changed;
+ }
+
+ public boolean updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo) {
+ boolean changed = false;
+ if (isSelf()) {
+ if (subscriptionInfo == null) {
+ // The subscription is inactive. Check if the participant is still active.
+ if (isActiveSubscription()) {
+ mSlotId = INVALID_SLOT_ID;
+ mSubscriptionColor = Color.TRANSPARENT;
+ mSubscriptionName = "";
+ changed = true;
+ }
+ } else {
+ final int slotId = subscriptionInfo.getSimSlotIndex();
+ final int color = subscriptionInfo.getIconTint();
+ final CharSequence name = subscriptionInfo.getDisplayName();
+ if (mSlotId != slotId || mSubscriptionColor != color || mSubscriptionName != name) {
+ mSlotId = slotId;
+ mSubscriptionColor = color;
+ mSubscriptionName = name.toString();
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ }
+
+ public void setFullName(final String fullName) {
+ mFullName = fullName;
+ }
+
+ public void setFirstName(final String firstName) {
+ mFirstName = firstName;
+ }
+
+ public void setProfilePhotoUri(final String profilePhotoUri) {
+ mProfilePhotoUri = profilePhotoUri;
+ }
+
+ public void setContactId(final long contactId) {
+ mContactId = contactId;
+ }
+
+ public void setLookupKey(final String lookupKey) {
+ mLookupKey = lookupKey;
+ }
+
+ public void setSendDestination(final String destination) {
+ mSendDestination = destination;
+ }
+
+ public void setContactDestination(final String destination) {
+ mContactDestination = destination;
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * @return whether this sub is active. Note that {@link ParticipantData#DEFAULT_SELF_SUB_ID} is
+ * is considered as active if there is any active SIM.
+ */
+ public boolean isActiveSubscription() {
+ return mSlotId != INVALID_SLOT_ID;
+ }
+
+ public boolean isDefaultSelf() {
+ return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ public int getSlotId() {
+ return mSlotId;
+ }
+
+ /**
+ * Slot IDs in the subscription manager is zero-based, but we want to show it
+ * as 1-based in UI.
+ */
+ public int getDisplaySlotId() {
+ return getSlotId() + 1;
+ }
+
+ public int getSubscriptionColor() {
+ Assert.isTrue(isActiveSubscription());
+ // Force the alpha channel to 0xff to ensure the returned color is solid.
+ return mSubscriptionColor | 0xff000000;
+ }
+
+ public String getSubscriptionName() {
+ Assert.isTrue(isActiveSubscription());
+ return mSubscriptionName;
+ }
+
+ public String getId() {
+ return mParticipantId;
+ }
+
+ public boolean isSelf() {
+ return (mSubId != OTHER_THAN_SELF_SUB_ID);
+ }
+
+ public boolean isEmail() {
+ return mIsEmailAddress;
+ }
+
+ public boolean isContactIdResolved() {
+ return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED);
+ }
+
+ public boolean isBlocked() {
+ return mBlocked;
+ }
+
+ public boolean isUnknownSender() {
+ final String unknownSender = ParticipantData.getUnknownSenderDestination();
+ return (TextUtils.equals(mSendDestination, unknownSender));
+ }
+
+ public ContentValues toContentValues() {
+ final ContentValues values = new ContentValues();
+ values.put(ParticipantColumns.SUB_ID, mSubId);
+ values.put(ParticipantColumns.SIM_SLOT_ID, mSlotId);
+ values.put(DatabaseHelper.ParticipantColumns.SEND_DESTINATION, mSendDestination);
+
+ if (!isUnknownSender()) {
+ values.put(DatabaseHelper.ParticipantColumns.DISPLAY_DESTINATION, mDisplayDestination);
+ values.put(DatabaseHelper.ParticipantColumns.NORMALIZED_DESTINATION,
+ mNormalizedDestination);
+ values.put(ParticipantColumns.FULL_NAME, mFullName);
+ values.put(ParticipantColumns.FIRST_NAME, mFirstName);
+ }
+
+ values.put(ParticipantColumns.PROFILE_PHOTO_URI, mProfilePhotoUri);
+ values.put(ParticipantColumns.CONTACT_ID, mContactId);
+ values.put(ParticipantColumns.LOOKUP_KEY, mLookupKey);
+ values.put(ParticipantColumns.BLOCKED, mBlocked);
+ values.put(ParticipantColumns.SUBSCRIPTION_COLOR, mSubscriptionColor);
+ values.put(ParticipantColumns.SUBSCRIPTION_NAME, mSubscriptionName);
+ return values;
+ }
+
+ public ParticipantData(final Parcel in) {
+ mParticipantId = in.readString();
+ mSubId = in.readInt();
+ mSlotId = in.readInt();
+ mNormalizedDestination = in.readString();
+ mSendDestination = in.readString();
+ mDisplayDestination = in.readString();
+ mFullName = in.readString();
+ mFirstName = in.readString();
+ mProfilePhotoUri = in.readString();
+ mContactId = in.readLong();
+ mLookupKey = in.readString();
+ mIsEmailAddress = in.readInt() != 0;
+ mBlocked = in.readInt() != 0;
+ mSubscriptionColor = in.readInt();
+ mSubscriptionName = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeString(mParticipantId);
+ dest.writeInt(mSubId);
+ dest.writeInt(mSlotId);
+ dest.writeString(mNormalizedDestination);
+ dest.writeString(mSendDestination);
+ dest.writeString(mDisplayDestination);
+ dest.writeString(mFullName);
+ dest.writeString(mFirstName);
+ dest.writeString(mProfilePhotoUri);
+ dest.writeLong(mContactId);
+ dest.writeString(mLookupKey);
+ dest.writeInt(mIsEmailAddress ? 1 : 0);
+ dest.writeInt(mBlocked ? 1 : 0);
+ dest.writeInt(mSubscriptionColor);
+ dest.writeString(mSubscriptionName);
+ }
+
+ public static final Parcelable.Creator<ParticipantData> CREATOR
+ = new Parcelable.Creator<ParticipantData>() {
+ @Override
+ public ParticipantData createFromParcel(final Parcel in) {
+ return new ParticipantData(in);
+ }
+
+ @Override
+ public ParticipantData[] newArray(final int size) {
+ return new ParticipantData[size];
+ }
+ };
+}
diff --git a/src/com/android/messaging/datamodel/data/ParticipantListItemData.java b/src/com/android/messaging/datamodel/data/ParticipantListItemData.java
new file mode 100644
index 0000000..f6c9b5f
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/ParticipantListItemData.java
@@ -0,0 +1,95 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.action.BugleActionToasts;
+import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
+import com.android.messaging.util.AvatarUriUtil;
+
+/**
+ * Helps visualize a ParticipantData in a PersonItemView
+ */
+public class ParticipantListItemData extends PersonItemData {
+ private final Uri mAvatarUri;
+ private final String mDisplayName;
+ private final String mDetails;
+ private final long mContactId;
+ private final String mLookupKey;
+ private final String mNormalizedDestination;
+
+ /**
+ * Constructor. Takes necessary info from the incoming ParticipantData.
+ */
+ public ParticipantListItemData(final ParticipantData participant) {
+ mAvatarUri = AvatarUriUtil.createAvatarUri(participant);
+ mContactId = participant.getContactId();
+ mLookupKey = participant.getLookupKey();
+ mNormalizedDestination = participant.getNormalizedDestination();
+ if (TextUtils.isEmpty(participant.getFullName())) {
+ mDisplayName = participant.getSendDestination();
+ mDetails = null;
+ } else {
+ mDisplayName = participant.getFullName();
+ mDetails = (participant.isUnknownSender()) ? null : participant.getSendDestination();
+ }
+ }
+
+ @Override
+ public Uri getAvatarUri() {
+ return mAvatarUri;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ @Override
+ public String getDetails() {
+ return mDetails;
+ }
+
+ @Override
+ public Intent getClickIntent() {
+ return null;
+ }
+
+ @Override
+ public long getContactId() {
+ return mContactId;
+ }
+
+ @Override
+ public String getLookupKey() {
+ return mLookupKey;
+ }
+
+ @Override
+ public String getNormalizedDestination() {
+ return mNormalizedDestination;
+ }
+
+ public void unblock(final Context context) {
+ UpdateDestinationBlockedAction.updateDestinationBlocked(
+ mNormalizedDestination, false, null,
+ BugleActionToasts.makeUpdateDestinationBlockedActionListener(context));
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/PendingAttachmentData.java b/src/com/android/messaging/datamodel/data/PendingAttachmentData.java
new file mode 100644
index 0000000..5e079f8
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/PendingAttachmentData.java
@@ -0,0 +1,176 @@
+/*
+ * 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.datamodel.data;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.UriUtil;
+
+/**
+ * Represents a "pending" message part that acts as a placeholder for the actual attachment being
+ * loaded. It handles the task to load and persist the attachment from a Uri to local scratch
+ * folder. This item is not persisted to the database.
+ */
+public class PendingAttachmentData extends MessagePartData {
+ /** The pending state. This is the initial state where we haven't started loading yet */
+ public static final int STATE_PENDING = 0;
+
+ /** The state for when we are currently loading the attachment to the scratch space */
+ public static final int STATE_LOADING = 1;
+
+ /** The attachment has been successfully loaded and no longer pending */
+ public static final int STATE_LOADED = 2;
+
+ /** The attachment failed to load */
+ public static final int STATE_FAILED = 3;
+
+ private static final int LOAD_MEDIA_TIME_LIMIT_MILLIS = 60 * 1000; // 60s
+
+ /** The current state of the pending attachment. Refer to the STATE_* states above */
+ private int mCurrentState;
+
+ /**
+ * Create a new instance of PendingAttachmentData with an output Uri.
+ * @param sourceUri the source Uri of the attachment. The Uri maybe temporary or remote,
+ * so we need to persist it to local storage.
+ */
+ protected PendingAttachmentData(final String caption, final String contentType,
+ @NonNull final Uri sourceUri, final int width, final int height,
+ final boolean onlySingleAttachment) {
+ super(caption, contentType, sourceUri, width, height, onlySingleAttachment);
+ mCurrentState = STATE_PENDING;
+ }
+
+ /**
+ * Creates a pending attachment data that is able to load from the given source uri and
+ * persist the media resource locally in the scratch folder.
+ */
+ public static PendingAttachmentData createPendingAttachmentData(final String contentType,
+ final Uri sourceUri) {
+ return createPendingAttachmentData(null, contentType, sourceUri, UNSPECIFIED_SIZE,
+ UNSPECIFIED_SIZE);
+ }
+
+ public static PendingAttachmentData createPendingAttachmentData(final String caption,
+ final String contentType, final Uri sourceUri, final int width, final int height) {
+ Assert.isTrue(ContentType.isMediaType(contentType));
+ return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
+ false /*onlySingleAttachment*/);
+ }
+
+ public static PendingAttachmentData createPendingAttachmentData(final String caption,
+ final String contentType, final Uri sourceUri, final int width, final int height,
+ final boolean onlySingleAttachment) {
+ Assert.isTrue(ContentType.isMediaType(contentType));
+ return new PendingAttachmentData(caption, contentType, sourceUri, width, height,
+ onlySingleAttachment);
+ }
+
+ public int getCurrentState() {
+ return mCurrentState;
+ }
+
+ public void loadAttachmentForDraft(final DraftMessageData draftMessageData,
+ final String bindingId) {
+ if (mCurrentState != STATE_PENDING) {
+ return;
+ }
+ mCurrentState = STATE_LOADING;
+
+ // Kick off a SafeAsyncTask to load the content of the media and persist it locally.
+ // Note: we need to persist the media locally even if it's not remote, because we
+ // want to be able to resend the media in case the message failed to send.
+ new SafeAsyncTask<Void, Void, MessagePartData>(LOAD_MEDIA_TIME_LIMIT_MILLIS,
+ true /* cancelExecutionOnTimeout */) {
+ @Override
+ protected MessagePartData doInBackgroundTimed(final Void... params) {
+ final Uri contentUri = getContentUri();
+ final Uri persistedUri = UriUtil.persistContentToScratchSpace(contentUri);
+ if (persistedUri != null) {
+ return MessagePartData.createMediaMessagePart(
+ getText(),
+ getContentType(),
+ persistedUri,
+ getWidth(),
+ getHeight());
+ }
+ return null;
+ }
+
+ @Override
+ protected void onCancelled() {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Timeout while retrieving media");
+ mCurrentState = STATE_FAILED;
+ if (draftMessageData.isBound(bindingId)) {
+ draftMessageData.removePendingAttachment(PendingAttachmentData.this);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final MessagePartData attachment) {
+ if (attachment != null) {
+ mCurrentState = STATE_LOADED;
+ if (draftMessageData.isBound(bindingId)) {
+ draftMessageData.updatePendingAttachment(attachment,
+ PendingAttachmentData.this);
+ } else {
+ // The draft message data is no longer bound, drop the loaded attachment.
+ attachment.destroyAsync();
+ }
+ } else {
+ // Media load failed. We already logged in doInBackground() so don't need to
+ // do that again.
+ mCurrentState = STATE_FAILED;
+ if (draftMessageData.isBound(bindingId)) {
+ draftMessageData.onPendingAttachmentLoadFailed(PendingAttachmentData.this);
+ draftMessageData.removePendingAttachment(PendingAttachmentData.this);
+ }
+ }
+ }
+ }.executeOnThreadPool();
+ }
+
+ protected PendingAttachmentData(final Parcel in) {
+ super(in);
+ mCurrentState = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(mCurrentState);
+ }
+
+ public static final Parcelable.Creator<PendingAttachmentData> CREATOR
+ = new Parcelable.Creator<PendingAttachmentData>() {
+ @Override
+ public PendingAttachmentData createFromParcel(final Parcel in) {
+ return new PendingAttachmentData(in);
+ }
+
+ @Override
+ public PendingAttachmentData[] newArray(final int size) {
+ return new PendingAttachmentData[size];
+ }
+ };
+}
diff --git a/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java b/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java
new file mode 100644
index 0000000..650a037
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/PeopleAndOptionsData.java
@@ -0,0 +1,210 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.messaging.datamodel.BoundCursorLoader;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.BugleActionToasts;
+import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
+import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.util.List;
+
+/**
+ * Services data needs for PeopleAndOptionsFragment.
+ */
+public class PeopleAndOptionsData extends BindableData implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+ public interface PeopleAndOptionsDataListener {
+ void onOptionsCursorUpdated(PeopleAndOptionsData data, Cursor cursor);
+ void onParticipantsListLoaded(PeopleAndOptionsData data,
+ List<ParticipantData> participants);
+ }
+
+ private static final String BINDING_ID = "bindingId";
+ private final Context mContext;
+ private final String mConversationId;
+ private final ConversationParticipantsData mParticipantData;
+ private LoaderManager mLoaderManager;
+ private PeopleAndOptionsDataListener mListener;
+
+ public PeopleAndOptionsData(final String conversationId, final Context context,
+ final PeopleAndOptionsDataListener listener) {
+ mListener = listener;
+ mContext = context;
+ mConversationId = conversationId;
+ mParticipantData = new ConversationParticipantsData();
+ }
+
+ private static final int CONVERSATION_OPTIONS_LOADER = 1;
+ private static final int PARTICIPANT_LOADER = 2;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ switch (id) {
+ case CONVERSATION_OPTIONS_LOADER: {
+ final Uri uri =
+ MessagingContentProvider.buildConversationMetadataUri(mConversationId);
+ return new BoundCursorLoader(bindingId, mContext, uri,
+ PeopleOptionsItemData.PROJECTION, null, null, null);
+ }
+
+ case PARTICIPANT_LOADER: {
+ final Uri uri =
+ MessagingContentProvider
+ .buildConversationParticipantsUri(mConversationId);
+ return new BoundCursorLoader(bindingId, mContext, uri,
+ ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
+ }
+
+ default:
+ Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding PeopleAndOptionsFragment");
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ if (isBound(cursorLoader.getBindingId())) {
+ switch (loader.getId()) {
+ case CONVERSATION_OPTIONS_LOADER:
+ mListener.onOptionsCursorUpdated(this, data);
+ break;
+
+ case PARTICIPANT_LOADER:
+ mParticipantData.bind(data);
+ mListener.onParticipantsListLoaded(this,
+ mParticipantData.getParticipantListExcludingSelf());
+ break;
+
+ default:
+ Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG,
+ "Loader finished after unbinding PeopleAndOptionsFragment");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<Cursor> loader) {
+ final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
+ if (isBound(cursorLoader.getBindingId())) {
+ switch (loader.getId()) {
+ case CONVERSATION_OPTIONS_LOADER:
+ mListener.onOptionsCursorUpdated(this, null);
+ break;
+
+ case PARTICIPANT_LOADER:
+ mParticipantData.bind(null);
+ break;
+
+ default:
+ Assert.fail("Unknown loader id for PeopleAndOptionsFragment!");
+ break;
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding PeopleAndOptionsFragment");
+ }
+ }
+
+ public void init(final LoaderManager loaderManager,
+ final BindingBase<PeopleAndOptionsData> binding) {
+ final Bundle args = new Bundle();
+ args.putString(BINDING_ID, binding.getBindingId());
+ mLoaderManager = loaderManager;
+ mLoaderManager.initLoader(CONVERSATION_OPTIONS_LOADER, args, this);
+ mLoaderManager.initLoader(PARTICIPANT_LOADER, args, this);
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+
+ // This could be null if we bind but the caller doesn't init the BindableData
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(CONVERSATION_OPTIONS_LOADER);
+ mLoaderManager.destroyLoader(PARTICIPANT_LOADER);
+ mLoaderManager = null;
+ }
+ }
+
+ public void enableConversationNotifications(final BindingBase<PeopleAndOptionsData> binding,
+ final boolean enable) {
+ final String bindingId = binding.getBindingId();
+ if (isBound(bindingId)) {
+ UpdateConversationOptionsAction.enableConversationNotifications(
+ mConversationId, enable);
+ }
+ }
+
+ public void setConversationNotificationSound(final BindingBase<PeopleAndOptionsData> binding,
+ final String ringtoneUri) {
+ final String bindingId = binding.getBindingId();
+ if (isBound(bindingId)) {
+ UpdateConversationOptionsAction.setConversationNotificationSound(mConversationId,
+ ringtoneUri);
+ }
+ }
+
+ public void enableConversationNotificationVibration(
+ final BindingBase<PeopleAndOptionsData> binding, final boolean enable) {
+ final String bindingId = binding.getBindingId();
+ if (isBound(bindingId)) {
+ UpdateConversationOptionsAction.enableVibrationForConversationNotification(
+ mConversationId, enable);
+ }
+ }
+
+ public void setDestinationBlocked(final BindingBase<PeopleAndOptionsData> binding,
+ final boolean blocked) {
+ final String bindingId = binding.getBindingId();
+ final ParticipantData participantData = mParticipantData.getOtherParticipant();
+ if (isBound(bindingId) && participantData != null) {
+ UpdateDestinationBlockedAction.updateDestinationBlocked(
+ participantData.getNormalizedDestination(),
+ blocked, mConversationId,
+ BugleActionToasts.makeUpdateDestinationBlockedActionListener(mContext));
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java b/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
new file mode 100644
index 0000000..5af6a30
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
@@ -0,0 +1,155 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+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.RingtoneUtil;
+
+public class PeopleOptionsItemData {
+ public static final String[] PROJECTION = {
+ ConversationListViewColumns.NOTIFICATION_ENABLED,
+ ConversationListViewColumns.NOTIFICATION_SOUND_URI,
+ ConversationListViewColumns.NOTIFICATION_VIBRATION,
+ };
+
+ // Column index for query projection.
+ private static final int INDEX_NOTIFICATION_ENABLED = 0;
+ private static final int INDEX_NOTIFICATION_SOUND_URI = 1;
+ private static final int INDEX_NOTIFICATION_VIBRATION = 2;
+
+ // Identification for each setting that's surfaced to the UI layer.
+ public static final int SETTING_NOTIFICATION_ENABLED = 0;
+ public static final int SETTING_NOTIFICATION_SOUND_URI = 1;
+ public static final int SETTING_NOTIFICATION_VIBRATION = 2;
+ public static final int SETTING_BLOCKED = 3;
+ public static final int SETTINGS_COUNT = 4;
+
+ // Type of UI switch to show for the toggle button.
+ public static final int TOGGLE_TYPE_CHECKBOX = 0;
+ public static final int TOGGLE_TYPE_SWITCH = 1;
+
+ private String mTitle;
+ private String mSubtitle;
+ private Uri mRingtoneUri;
+ private boolean mCheckable;
+ private boolean mChecked;
+ private boolean mEnabled;
+ private int mItemId;
+ private ParticipantData mOtherParticipant;
+
+ private final Context mContext;
+
+ public PeopleOptionsItemData(final Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Bind to a specific setting column on conversation metadata cursor. (Note
+ * that it binds to columns because it treats individual columns of the cursor as
+ * separate options to display for the conversation, e.g. notification settings).
+ */
+ public void bind(
+ final Cursor cursor, final ParticipantData otherParticipant, final int settingType) {
+ mSubtitle = null;
+ mRingtoneUri = null;
+ mCheckable = true;
+ mEnabled = true;
+ mItemId = settingType;
+ mOtherParticipant = otherParticipant;
+
+ final boolean notificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
+ switch (settingType) {
+ case SETTING_NOTIFICATION_ENABLED:
+ mTitle = mContext.getString(R.string.notifications_enabled_conversation_pref_title);
+ mChecked = notificationEnabled;
+ break;
+
+ case SETTING_NOTIFICATION_SOUND_URI:
+ mTitle = mContext.getString(R.string.notification_sound_pref_title);
+ final String ringtoneString = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
+ Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(ringtoneString);
+
+ mSubtitle = mContext.getString(R.string.silent_ringtone);
+ if (ringtoneUri != null) {
+ final Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
+ if (ringtone != null) {
+ mSubtitle = ringtone.getTitle(mContext);
+ }
+ }
+ mCheckable = false;
+ mRingtoneUri = ringtoneUri;
+ mEnabled = notificationEnabled;
+ break;
+
+ case SETTING_NOTIFICATION_VIBRATION:
+ mTitle = mContext.getString(R.string.notification_vibrate_pref_title);
+ mChecked = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
+ mEnabled = notificationEnabled;
+ break;
+
+ case SETTING_BLOCKED:
+ Assert.notNull(otherParticipant);
+ final int resourceId = otherParticipant.isBlocked() ?
+ R.string.unblock_contact_title : R.string.block_contact_title;
+ mTitle = mContext.getString(resourceId, otherParticipant.getDisplayDestination());
+ mCheckable = false;
+ break;
+
+ default:
+ Assert.fail("Unsupported conversation option type!");
+ }
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public String getSubtitle() {
+ return mSubtitle;
+ }
+
+ public boolean getCheckable() {
+ return mCheckable;
+ }
+
+ public boolean getChecked() {
+ return mChecked;
+ }
+
+ public boolean getEnabled() {
+ return mEnabled;
+ }
+
+ public int getItemId() {
+ return mItemId;
+ }
+
+ public Uri getRingtoneUri() {
+ return mRingtoneUri;
+ }
+
+ public ParticipantData getOtherParticipant() {
+ return mOtherParticipant;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/PersonItemData.java b/src/com/android/messaging/datamodel/data/PersonItemData.java
new file mode 100644
index 0000000..a0a1ce8
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/PersonItemData.java
@@ -0,0 +1,67 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import com.android.messaging.datamodel.binding.BindableData;
+
+/**
+ * Bridges between any particpant/contact related data and data displayed in the PersonItemView.
+ */
+public abstract class PersonItemData extends BindableData {
+ /**
+ * The UI component that listens for data change and update accordingly.
+ */
+ public interface PersonItemDataListener {
+ void onPersonDataUpdated(PersonItemData data);
+ void onPersonDataFailed(PersonItemData data, Exception exception);
+ }
+
+ private PersonItemDataListener mListener;
+
+ public abstract Uri getAvatarUri();
+ public abstract String getDisplayName();
+ public abstract String getDetails();
+ public abstract Intent getClickIntent();
+ public abstract long getContactId();
+ public abstract String getLookupKey();
+ public abstract String getNormalizedDestination();
+
+ public void setListener(final PersonItemDataListener listener) {
+ if (isBound()) {
+ mListener = listener;
+ }
+ }
+
+ protected void notifyDataUpdated() {
+ if (isBound() && mListener != null) {
+ mListener.onPersonDataUpdated(this);
+ }
+ }
+
+ protected void notifyDataFailed(final Exception exception) {
+ if (isBound() && mListener != null) {
+ mListener.onPersonDataFailed(this, exception);
+ }
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/SelfParticipantsData.java b/src/com/android/messaging/datamodel/data/SelfParticipantsData.java
new file mode 100644
index 0000000..43302ed
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/SelfParticipantsData.java
@@ -0,0 +1,107 @@
+/*
+ * 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.datamodel.data;
+
+import android.database.Cursor;
+import android.support.v4.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.messaging.util.OsUtil;
+
+/**
+ * A class that contains the list of all self participants potentially involved in a conversation.
+ * This class contains both active/inactive self entries when there is multi-SIM support.
+ */
+public class SelfParticipantsData {
+ /**
+ * The map from self participant ids to self-participant data entries in the participants table.
+ * This includes both active, inactive and default (with subId ==
+ * {@link ParticipantData#DEFAULT_SELF_SUB_ID}) subscriptions.
+ */
+ private final ArrayMap<String, ParticipantData> mSelfParticipantMap;
+
+ public SelfParticipantsData() {
+ mSelfParticipantMap = new ArrayMap<String, ParticipantData>();
+ }
+
+ public void bind(final Cursor cursor) {
+ mSelfParticipantMap.clear();
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ final ParticipantData newParticipant = ParticipantData.getFromCursor(cursor);
+ mSelfParticipantMap.put(newParticipant.getId(), newParticipant);
+ }
+ }
+ }
+
+ /**
+ * Gets the list of self participants for all subscriptions.
+ * @param activeOnly if set, returns active self entries only (i.e. those with SIMs plugged in).
+ */
+ public List<ParticipantData> getSelfParticipants(final boolean activeOnly) {
+ List<ParticipantData> list = new ArrayList<ParticipantData>();
+ for (final ParticipantData self : mSelfParticipantMap.values()) {
+ if (!activeOnly || self.isActiveSubscription()) {
+ list.add(self);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Gets the self participant corresponding to the given self id.
+ */
+ ParticipantData getSelfParticipantById(final String selfId) {
+ return mSelfParticipantMap.get(selfId);
+ }
+
+ /**
+ * Returns if a given self id represents the default self.
+ */
+ boolean isDefaultSelf(final String selfId) {
+ if (!OsUtil.isAtLeastL_MR1()) {
+ return true;
+ }
+ final ParticipantData self = getSelfParticipantById(selfId);
+ return self == null ? false : self.getSubId() == ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ public int getSelfParticipantsCountExcludingDefault(final boolean activeOnly) {
+ int count = 0;
+ for (final ParticipantData self : mSelfParticipantMap.values()) {
+ if (!self.isDefaultSelf() && (!activeOnly || self.isActiveSubscription())) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public ParticipantData getDefaultSelfParticipant() {
+ for (final ParticipantData self : mSelfParticipantMap.values()) {
+ if (self.isDefaultSelf()) {
+ return self;
+ }
+ }
+ return null;
+ }
+
+ boolean isLoaded() {
+ return !mSelfParticipantMap.isEmpty();
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/SettingsData.java b/src/com/android/messaging/datamodel/data/SettingsData.java
new file mode 100644
index 0000000..7474619
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/SettingsData.java
@@ -0,0 +1,223 @@
+/*
+ * 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.datamodel.data;
+
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.BoundCursorLoader;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Services SettingsFragment's data needs for loading active self participants to display
+ * the list of active subscriptions.
+ */
+public class SettingsData extends BindableData implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+ public interface SettingsDataListener {
+ void onSelfParticipantDataLoaded(SettingsData data);
+ }
+
+ public static class SettingsItem {
+ public static final int TYPE_GENERAL_SETTINGS = 1;
+ public static final int TYPE_PER_SUBSCRIPTION_SETTINGS = 2;
+
+ private final String mDisplayName;
+ private final String mDisplayDetail;
+ private final String mActivityTitle;
+ private final int mType;
+ private final int mSubId;
+
+ private SettingsItem(final String displayName, final String displayDetail,
+ final String activityTitle, final int type, final int subId) {
+ mDisplayName = displayName;
+ mDisplayDetail = displayDetail;
+ mActivityTitle = activityTitle;
+ mType = type;
+ mSubId = subId;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public String getDisplayDetail() {
+ return mDisplayDetail;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ public String getActivityTitle() {
+ return mActivityTitle;
+ }
+
+ public static SettingsItem fromSelfParticipant(final Context context,
+ final ParticipantData self) {
+ Assert.isTrue(self.isSelf());
+ Assert.isTrue(self.isActiveSubscription());
+ final String displayDetail = TextUtils.isEmpty(self.getDisplayDestination()) ?
+ context.getString(R.string.sim_settings_unknown_number) :
+ self.getDisplayDestination();
+ final String displayName = context.getString(R.string.sim_specific_settings,
+ self.getSubscriptionName());
+ return new SettingsItem(displayName, displayDetail, displayName,
+ TYPE_PER_SUBSCRIPTION_SETTINGS, self.getSubId());
+ }
+
+ public static SettingsItem createGeneralSettingsItem(final Context context) {
+ return new SettingsItem(context.getString(R.string.general_settings),
+ null, context.getString(R.string.general_settings_activity_title),
+ TYPE_GENERAL_SETTINGS, -1);
+ }
+
+ public static SettingsItem createDefaultMmsSettingsItem(final Context context,
+ final int subId) {
+ return new SettingsItem(context.getString(R.string.advanced_settings),
+ null, context.getString(R.string.advanced_settings_activity_title),
+ TYPE_PER_SUBSCRIPTION_SETTINGS, subId);
+ }
+ }
+
+ private static final String BINDING_ID = "bindingId";
+ private final Context mContext;
+ private final SelfParticipantsData mSelfParticipantsData;
+ private LoaderManager mLoaderManager;
+ private SettingsDataListener mListener;
+
+ public SettingsData(final Context context,
+ final SettingsDataListener listener) {
+ mListener = listener;
+ mContext = context;
+ mSelfParticipantsData = new SelfParticipantsData();
+ }
+
+ private static final int SELF_PARTICIPANT_LOADER = 1;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
+ Assert.equals(SELF_PARTICIPANT_LOADER, id);
+ Loader<Cursor> loader = null;
+
+ final String bindingId = args.getString(BINDING_ID);
+ // Check if data still bound to the requesting ui element
+ if (isBound(bindingId)) {
+ loader = new BoundCursorLoader(bindingId, mContext,
+ MessagingContentProvider.PARTICIPANTS_URI,
+ ParticipantData.ParticipantsQuery.PROJECTION,
+ ParticipantColumns.SUB_ID + " <> ?",
+ new String[] { String.valueOf(ParticipantData.OTHER_THAN_SELF_SUB_ID) },
+ null);
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Creating self loader after unbinding");
+ }
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<Cursor> generic, final Cursor data) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mSelfParticipantsData.bind(data);
+ mListener.onSelfParticipantDataLoaded(this);
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Self loader finished after unbinding");
+ }
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<Cursor> generic) {
+ final BoundCursorLoader loader = (BoundCursorLoader) generic;
+
+ // Check if data still bound to the requesting ui element
+ if (isBound(loader.getBindingId())) {
+ mSelfParticipantsData.bind(null);
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Self loader reset after unbinding");
+ }
+ }
+
+ public void init(final LoaderManager loaderManager,
+ final BindingBase<SettingsData> binding) {
+ final Bundle args = new Bundle();
+ args.putString(BINDING_ID, binding.getBindingId());
+ mLoaderManager = loaderManager;
+ mLoaderManager.initLoader(SELF_PARTICIPANT_LOADER, args, this);
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+
+ // This could be null if we bind but the caller doesn't init the BindableData
+ if (mLoaderManager != null) {
+ mLoaderManager.destroyLoader(SELF_PARTICIPANT_LOADER);
+ mLoaderManager = null;
+ }
+ }
+
+ public List<SettingsItem> getSettingsItems() {
+ final List<ParticipantData> selfs = mSelfParticipantsData.getSelfParticipants(true);
+ final List<SettingsItem> settingsItems = new ArrayList<SettingsItem>();
+ // First goes the general settings, followed by per-subscription settings.
+ settingsItems.add(SettingsItem.createGeneralSettingsItem(mContext));
+ // For per-subscription settings, show the actual SIM name with phone number if the
+ // platorm is at least L-MR1 and there are multiple active SIMs.
+ final int activeSubCountExcludingDefault =
+ mSelfParticipantsData.getSelfParticipantsCountExcludingDefault(true);
+ if (OsUtil.isAtLeastL_MR1() && activeSubCountExcludingDefault > 0) {
+ for (ParticipantData self : selfs) {
+ if (!self.isDefaultSelf()) {
+ if (activeSubCountExcludingDefault > 1) {
+ settingsItems.add(SettingsItem.fromSelfParticipant(mContext, self));
+ } else {
+ // This is the only active non-default SIM.
+ settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext,
+ self.getSubId()));
+ break;
+ }
+ }
+ }
+ } else {
+ // Either pre-L-MR1, or there's no active SIM, so show the default MMS settings.
+ settingsItems.add(SettingsItem.createDefaultMmsSettingsItem(mContext,
+ ParticipantData.DEFAULT_SELF_SUB_ID));
+ }
+ return settingsItems;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/SubscriptionListData.java b/src/com/android/messaging/datamodel/data/SubscriptionListData.java
new file mode 100644
index 0000000..b5d4e4b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/SubscriptionListData.java
@@ -0,0 +1,128 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This is a UI facing data model component that holds a list of
+ * {@link SubscriptionListData.SubscriptionListEntry}'s, one for each *active* subscriptions.
+ *
+ * This is used to:
+ * 1) Show a list of SIMs in the SIM Selector
+ * 2) Show the currently selected SIM in the compose message view
+ * 3) Show SIM indicators on conversation message views
+ *
+ * It builds on top of SelfParticipantsData and performs additional logic such as determining
+ * the set of icons to use for the individual Subs.
+ */
+public class SubscriptionListData {
+ /**
+ * Represents a single sub that backs UI.
+ */
+ public static class SubscriptionListEntry {
+ public final String selfParticipantId;
+ public final Uri iconUri;
+ public final Uri selectedIconUri;
+ public final String displayName;
+ public final int displayColor;
+ public final String displayDestination;
+
+ private SubscriptionListEntry(final String selfParticipantId, final Uri iconUri,
+ final Uri selectedIconUri, final String displayName, final int displayColor,
+ final String displayDestination) {
+ this.selfParticipantId = selfParticipantId;
+ this.iconUri = iconUri;
+ this.selectedIconUri = selectedIconUri;
+ this.displayName = displayName;
+ this.displayColor = displayColor;
+ this.displayDestination = displayDestination;
+ }
+
+ static SubscriptionListEntry fromSelfParticipantData(
+ final ParticipantData selfParticipantData, final Context context) {
+ Assert.isTrue(selfParticipantData.isSelf());
+ Assert.isTrue(selfParticipantData.isActiveSubscription());
+ final int slotId = selfParticipantData.getDisplaySlotId();
+ final String iconIdentifier = String.format(Locale.getDefault(), "%d", slotId);
+ final String subscriptionName = selfParticipantData.getSubscriptionName();
+ final String displayName = TextUtils.isEmpty(subscriptionName) ?
+ context.getString(R.string.sim_slot_identifier, slotId) : subscriptionName;
+ return new SubscriptionListEntry(selfParticipantData.getId(),
+ AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier,
+ false /* selected */, false /* incoming */),
+ AvatarUriUtil.createAvatarUri(selfParticipantData, iconIdentifier,
+ true /* selected */, false /* incoming */),
+ displayName, selfParticipantData.getSubscriptionColor(),
+ selfParticipantData.getDisplayDestination());
+ }
+ }
+
+ private final List<SubscriptionListEntry> mEntriesExcludingDefault;
+ private SubscriptionListEntry mDefaultEntry;
+ private final Context mContext;
+
+ public SubscriptionListData(final Context context) {
+ mEntriesExcludingDefault = new ArrayList<SubscriptionListEntry>();
+ mContext = context;
+ }
+
+ public void bind(final List<ParticipantData> subs) {
+ mEntriesExcludingDefault.clear();
+ mDefaultEntry = null;
+ for (final ParticipantData sub : subs) {
+ final SubscriptionListEntry entry =
+ SubscriptionListEntry.fromSelfParticipantData(sub, mContext);
+ if (!sub.isDefaultSelf()) {
+ mEntriesExcludingDefault.add(entry);
+ } else {
+ mDefaultEntry = entry;
+ }
+ }
+ }
+
+ public List<SubscriptionListEntry> getActiveSubscriptionEntriesExcludingDefault() {
+ return mEntriesExcludingDefault;
+ }
+
+ public SubscriptionListEntry getActiveSubscriptionEntryBySelfId(final String selfId,
+ final boolean excludeDefault) {
+ if (mDefaultEntry != null && TextUtils.equals(mDefaultEntry.selfParticipantId, selfId)) {
+ return excludeDefault ? null : mDefaultEntry;
+ }
+
+ for (final SubscriptionListEntry entry : mEntriesExcludingDefault) {
+ if (TextUtils.equals(entry.selfParticipantId, selfId)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ public boolean hasData() {
+ return !mEntriesExcludingDefault.isEmpty() || mDefaultEntry != null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/VCardContactItemData.java b/src/com/android/messaging/datamodel/data/VCardContactItemData.java
new file mode 100644
index 0000000..8abf493
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/VCardContactItemData.java
@@ -0,0 +1,185 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.media.BindableMediaRequest;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+import com.android.messaging.datamodel.media.VCardRequestDescriptor;
+import com.android.messaging.datamodel.media.VCardResource;
+import com.android.messaging.datamodel.media.VCardResourceEntry;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ContactUtil;
+
+import java.util.List;
+
+/**
+ * Data class for visualizing and loading data for a VCard contact.
+ */
+public class VCardContactItemData extends PersonItemData
+ implements MediaResourceLoadListener<VCardResource> {
+ private final Context mContext;
+ private final Uri mVCardUri;
+ private String mDetails;
+ private final Binding<BindableMediaRequest<VCardResource>> mBinding =
+ BindingBase.createBinding(this);
+ private VCardResource mVCardResource;
+
+ private static final Uri sDefaultAvatarUri =
+ AvatarUriUtil.createAvatarUri(null, null, null, null);
+
+ /**
+ * Constructor. This parses data from the given MessagePartData describing the vcard
+ */
+ public VCardContactItemData(final Context context, final MessagePartData messagePartData) {
+ this(context, messagePartData.getContentUri());
+ Assert.isTrue(messagePartData.isVCard());
+ }
+
+ /**
+ * Constructor. This parses data from the given VCard Uri
+ */
+ public VCardContactItemData(final Context context, final Uri vCardUri) {
+ mContext = context;
+ mDetails = mContext.getString(R.string.loading_vcard);
+ mVCardUri = vCardUri;
+ }
+
+ @Override
+ public Uri getAvatarUri() {
+ if (hasValidVCard()) {
+ final List<VCardResourceEntry> vcards = mVCardResource.getVCards();
+ Assert.isTrue(vcards.size() > 0);
+ if (vcards.size() == 1) {
+ return vcards.get(0).getAvatarUri();
+ }
+ }
+ return sDefaultAvatarUri;
+ }
+
+ @Override
+ public String getDisplayName() {
+ if (hasValidVCard()) {
+ final List<VCardResourceEntry> vcards = mVCardResource.getVCards();
+ Assert.isTrue(vcards.size() > 0);
+ if (vcards.size() == 1) {
+ return vcards.get(0).getDisplayName();
+ } else {
+ return mContext.getResources().getQuantityString(
+ R.plurals.vcard_multiple_display_name, vcards.size(), vcards.size());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getDetails() {
+ return mDetails;
+ }
+
+ @Override
+ public Intent getClickIntent() {
+ return null;
+ }
+
+ @Override
+ public long getContactId() {
+ return ContactUtil.INVALID_CONTACT_ID;
+ }
+
+ @Override
+ public String getLookupKey() {
+ return null;
+ }
+
+ @Override
+ public String getNormalizedDestination() {
+ return null;
+ }
+
+ public VCardResource getVCardResource() {
+ return hasValidVCard() ? mVCardResource : null;
+ }
+
+ public Uri getVCardUri() {
+ return hasValidVCard() ? mVCardUri : null;
+ }
+
+ public boolean hasValidVCard() {
+ return isBound() && mVCardResource != null;
+ }
+
+ @Override
+ public void bind(final String bindingId) {
+ super.bind(bindingId);
+
+ // Bind and request the VCard from media resource manager.
+ mBinding.bind(new VCardRequestDescriptor(mVCardUri).buildAsyncMediaRequest(mContext, this));
+ MediaResourceManager.get().requestMediaResourceAsync(mBinding.getData());
+ }
+
+ @Override
+ public void unbind(final String bindingId) {
+ super.unbind(bindingId);
+ mBinding.unbind();
+ if (mVCardResource != null) {
+ mVCardResource.release();
+ mVCardResource = null;
+ }
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof VCardContactItemData)) {
+ return false;
+ }
+
+ final VCardContactItemData lhs = (VCardContactItemData) o;
+ return mVCardUri.equals(lhs.mVCardUri);
+ }
+
+ @Override
+ public void onMediaResourceLoaded(final MediaRequest<VCardResource> request,
+ final VCardResource resource, final boolean isCached) {
+ Assert.isTrue(mVCardResource == null);
+ mBinding.ensureBound();
+ mDetails = mContext.getString(R.string.vcard_tap_hint);
+ mVCardResource = resource;
+ mVCardResource.addRef();
+ notifyDataUpdated();
+ }
+
+ @Override
+ public void onMediaResourceLoadError(final MediaRequest<VCardResource> request,
+ final Exception exception) {
+ mBinding.ensureBound();
+ mDetails = mContext.getString(R.string.failed_loading_vcard);
+ notifyDataFailed(exception);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java b/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java
new file mode 100644
index 0000000..380d93c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/AsyncMediaRequestWrapper.java
@@ -0,0 +1,74 @@
+/*
+ * 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.datamodel.media;
+
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+
+import java.util.List;
+
+/**
+ * A mix-in style class that wraps around a normal, threading-agnostic MediaRequest object with
+ * functionalities offered by {@link BindableMediaRequest} to allow for async processing.
+ */
+class AsyncMediaRequestWrapper<T extends RefCountedMediaResource> extends BindableMediaRequest<T> {
+
+ /**
+ * Create a new async media request wrapper instance given the listener.
+ */
+ public static <T extends RefCountedMediaResource> AsyncMediaRequestWrapper<T>
+ createWith(final MediaRequest<T> wrappedRequest,
+ final MediaResourceLoadListener<T> listener) {
+ return new AsyncMediaRequestWrapper<T>(listener, wrappedRequest);
+ }
+
+ private final MediaRequest<T> mWrappedRequest;
+
+ private AsyncMediaRequestWrapper(final MediaResourceLoadListener<T> listener,
+ final MediaRequest<T> wrappedRequest) {
+ super(listener);
+ mWrappedRequest = wrappedRequest;
+ }
+
+ @Override
+ public String getKey() {
+ return mWrappedRequest.getKey();
+ }
+
+ @Override
+ public MediaCache<T> getMediaCache() {
+ return mWrappedRequest.getMediaCache();
+ }
+
+ @Override
+ public int getRequestType() {
+ return mWrappedRequest.getRequestType();
+ }
+
+ @Override
+ public T loadMediaBlocking(List<MediaRequest<T>> chainedTask) throws Exception {
+ return mWrappedRequest.loadMediaBlocking(chainedTask);
+ }
+
+ @Override
+ public int getCacheId() {
+ return mWrappedRequest.getCacheId();
+ }
+
+ @Override
+ public MediaRequestDescriptor<T> getDescriptor() {
+ return mWrappedRequest.getDescriptor();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java b/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java
new file mode 100644
index 0000000..719b296
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/AvatarGroupRequestDescriptor.java
@@ -0,0 +1,159 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.graphics.RectF;
+import android.net.Uri;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class AvatarGroupRequestDescriptor extends CompositeImageRequestDescriptor {
+ private static final int MAX_GROUP_SIZE = 4;
+
+ public AvatarGroupRequestDescriptor(final Uri uri, final int desiredWidth,
+ final int desiredHeight) {
+ this(convertToDescriptor(uri, desiredWidth, desiredHeight), desiredWidth, desiredHeight);
+ }
+
+ public AvatarGroupRequestDescriptor(final List<? extends ImageRequestDescriptor> descriptors,
+ final int desiredWidth, final int desiredHeight) {
+ super(descriptors, desiredWidth, desiredHeight);
+ Assert.isTrue(descriptors.size() <= MAX_GROUP_SIZE);
+ }
+
+ private static List<? extends ImageRequestDescriptor> convertToDescriptor(final Uri uri,
+ final int desiredWidth, final int desiredHeight) {
+ final List<String> participantUriStrings = AvatarUriUtil.getGroupParticipantUris(uri);
+ final List<AvatarRequestDescriptor> avatarDescriptors =
+ new ArrayList<AvatarRequestDescriptor>(participantUriStrings.size());
+ for (final String uriString : participantUriStrings) {
+ final AvatarRequestDescriptor descriptor = new AvatarRequestDescriptor(
+ Uri.parse(uriString), desiredWidth, desiredHeight);
+ avatarDescriptors.add(descriptor);
+ }
+ return avatarDescriptors;
+ }
+
+ @Override
+ public CompositeImageRequest<?> buildBatchImageRequest(final Context context) {
+ return new CompositeImageRequest<AvatarGroupRequestDescriptor>(context, this);
+ }
+
+ @Override
+ public List<RectF> getChildRequestTargetRects() {
+ return Arrays.asList(generateDestRectArray());
+ }
+
+ /**
+ * Generates an array of {@link RectF} which represents where each of the individual avatar
+ * should be located in the final group avatar image. The location of each avatar depends on
+ * the size of the group and the size of the overall group avatar size.
+ */
+ private RectF[] generateDestRectArray() {
+ final int groupSize = mDescriptors.size();
+ final float width = desiredWidth;
+ final float height = desiredHeight;
+ final float halfWidth = width / 2F;
+ final float halfHeight = height / 2F;
+ final RectF[] destArray = new RectF[groupSize];
+ switch (groupSize) {
+ case 2:
+ /**
+ * +-------+
+ * | 0 | |
+ * +-------+
+ * | | 1 |
+ * +-------+
+ *
+ * We want two circles which touches in the center. To get this we know that the
+ * diagonal of the overall group avatar is squareRoot(2) * w We also know that the
+ * two circles touches the at the center of the overall group avatar and the
+ * distance from the center of the circle to the corner of the group avatar is
+ * radius * squareRoot(2). Therefore, the following emerges.
+ *
+ * w * squareRoot(2) = 2 (radius + radius * squareRoot(2))
+ * Solving for radius we get:
+ * d = 2 * radius = ( squareRoot(2) / (squareRoot(2) + 1)) * w
+ * d = (2 - squareRoot(2)) * w
+ */
+ final float diameter = (float) ((2 - Math.sqrt(2)) * width);
+ destArray[0] = new RectF(0, 0, diameter, diameter);
+ destArray[1] = new RectF(width - diameter, height - diameter, width, height);
+ break;
+ case 3:
+ /**
+ * +-------+
+ * | | 0 | |
+ * +-------+
+ * | 1 | 2 |
+ * +-------+
+ * i0
+ * |\
+ * a | \ c
+ * --- i2
+ * b
+ *
+ * a = radius * squareRoot(3) due to the triangle being a 30-60-90 right triangle.
+ * b = radius of circle
+ * c = 2 * radius of circle
+ *
+ * All three of the images are circles and therefore image zero will not touch
+ * image one or image two. Move image zero down so it touches image one and image
+ * two. This can be done by keeping image zero in the center and moving it down
+ * slightly. The amount to move down can be calculated by solving a right triangle.
+ * We know that the center x of image two to the center x of image zero is the
+ * radius of the circle, this is the length of edge b. Also we know that the
+ * distance from image zero to image two's center is 2 * radius, edge c. From this
+ * we know that the distance from center y of image two to center y of image one,
+ * edge a, is equal to radius * squareRoot(3) due to this triangle being a 30-60-90
+ * right triangle.
+ */
+ final float quarterWidth = width / 4F;
+ final float threeQuarterWidth = 3 * quarterWidth;
+ final float radius = height / 4F;
+ final float imageTwoCenterY = height - radius;
+ final float lengthOfEdgeA = (float) (radius * Math.sqrt(3));
+ final float imageZeroCenterY = imageTwoCenterY - lengthOfEdgeA;
+ final float imageZeroTop = imageZeroCenterY - radius;
+ final float imageZeroBottom = imageZeroCenterY + radius;
+ destArray[0] = new RectF(
+ quarterWidth, imageZeroTop, threeQuarterWidth, imageZeroBottom);
+ destArray[1] = new RectF(0, halfHeight, halfWidth, height);
+ destArray[2] = new RectF(halfWidth, halfHeight, width, height);
+ break;
+ default:
+ /**
+ * +-------+
+ * | 0 | 1 |
+ * +-------+
+ * | 2 | 3 |
+ * +-------+
+ */
+ destArray[0] = new RectF(0, 0, halfWidth, halfHeight);
+ destArray[1] = new RectF(halfWidth, 0, width, halfHeight);
+ destArray[2] = new RectF(0, halfHeight, halfWidth, height);
+ destArray[3] = new RectF(halfWidth, halfHeight, width, height);
+ break;
+ }
+ return destArray;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/AvatarRequest.java b/src/com/android/messaging/datamodel/media/AvatarRequest.java
new file mode 100644
index 0000000..22d5ccc
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/AvatarRequest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.media.ExifInterface;
+import android.net.Uri;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UriUtil;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+public class AvatarRequest extends UriImageRequest<AvatarRequestDescriptor> {
+ private static Bitmap sDefaultPersonBitmap;
+ private static Bitmap sDefaultPersonBitmapLarge;
+
+ public AvatarRequest(final Context context,
+ final AvatarRequestDescriptor descriptor) {
+ super(context, descriptor);
+ }
+
+ @Override
+ protected InputStream getInputStreamForResource() throws FileNotFoundException {
+ if (UriUtil.isLocalResourceUri(mDescriptor.uri)) {
+ return super.getInputStreamForResource();
+ } else {
+ final Uri primaryUri = AvatarUriUtil.getPrimaryUri(mDescriptor.uri);
+ Assert.isTrue(UriUtil.isLocalResourceUri(primaryUri));
+ return mContext.getContentResolver().openInputStream(primaryUri);
+ }
+ }
+
+ /**
+ * We can load multiple types of images for avatars depending on the uri. The uri should be
+ * built by {@link com.android.messaging.util.AvatarUriUtil} which will decide on
+ * what uri to build based on the available profile photo and name. Here we will check if the
+ * image is a local resource (ie profile photo uri), if the resource isn't a local one we will
+ * generate a tile with the first letter of the name.
+ */
+ @Override
+ protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTasks)
+ throws IOException {
+ Assert.isNotMainThread();
+ String avatarType = AvatarUriUtil.getAvatarType(mDescriptor.uri);
+ Bitmap bitmap = null;
+ int orientation = ExifInterface.ORIENTATION_NORMAL;
+ final boolean isLocalResourceUri = UriUtil.isLocalResourceUri(mDescriptor.uri) ||
+ AvatarUriUtil.TYPE_LOCAL_RESOURCE_URI.equals(avatarType);
+ if (isLocalResourceUri) {
+ try {
+ ImageResource imageResource = super.loadMediaInternal(chainedTasks);
+ bitmap = imageResource.getBitmap();
+ orientation = imageResource.mOrientation;
+ } catch (Exception ex) {
+ // If we encountered any exceptions trying to load the local avatar resource,
+ // fall back to generated avatar.
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "AvatarRequest: failed to load local avatar " +
+ "resource, switching to fallback rendering", ex);
+ }
+ }
+
+ final int width = mDescriptor.desiredWidth;
+ final int height = mDescriptor.desiredHeight;
+ // Check to see if we already got the bitmap. If not get a fallback avatar
+ if (bitmap == null) {
+ Uri generatedUri = mDescriptor.uri;
+ if (isLocalResourceUri) {
+ // If we are here, we just failed to load the local resource. Use the fallback Uri
+ // if possible.
+ generatedUri = AvatarUriUtil.getFallbackUri(mDescriptor.uri);
+ if (generatedUri == null) {
+ // No fallback Uri was provided, use the default avatar.
+ generatedUri = AvatarUriUtil.DEFAULT_BACKGROUND_AVATAR;
+ }
+ }
+
+ avatarType = AvatarUriUtil.getAvatarType(generatedUri);
+ if (AvatarUriUtil.TYPE_LETTER_TILE_URI.equals(avatarType)) {
+ final String name = AvatarUriUtil.getName(generatedUri);
+ bitmap = renderLetterTile(name, width, height);
+ } else {
+ bitmap = renderDefaultAvatar(width, height);
+ }
+ }
+ return new DecodedImageResource(getKey(), bitmap, orientation);
+ }
+
+ private Bitmap renderDefaultAvatar(final int width, final int height) {
+ final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height,
+ getBackgroundColor());
+ final Canvas canvas = new Canvas(bitmap);
+
+ if (sDefaultPersonBitmap == null) {
+ final BitmapDrawable defaultPerson = (BitmapDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ic_person_light);
+ sDefaultPersonBitmap = defaultPerson.getBitmap();
+ }
+ if (sDefaultPersonBitmapLarge == null) {
+ final BitmapDrawable largeDefaultPerson = (BitmapDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ic_person_light_large);
+ sDefaultPersonBitmapLarge = largeDefaultPerson.getBitmap();
+ }
+
+ Bitmap defaultPerson = null;
+ if (mDescriptor.isWearBackground) {
+ final BitmapDrawable wearDefaultPerson = (BitmapDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ic_person_wear);
+ defaultPerson = wearDefaultPerson.getBitmap();
+ } else {
+ final boolean isLargeDefault = (width > sDefaultPersonBitmap.getWidth()) ||
+ (height > sDefaultPersonBitmap.getHeight());
+ defaultPerson =
+ isLargeDefault ? sDefaultPersonBitmapLarge : sDefaultPersonBitmap;
+ }
+
+ final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ final Matrix matrix = new Matrix();
+ final RectF source = new RectF(0, 0, defaultPerson.getWidth(), defaultPerson.getHeight());
+ final RectF dest = new RectF(0, 0, width, height);
+ matrix.setRectToRect(source, dest, Matrix.ScaleToFit.FILL);
+
+ canvas.drawBitmap(defaultPerson, matrix, paint);
+
+ return bitmap;
+ }
+
+ private Bitmap renderLetterTile(final String name, final int width, final int height) {
+ final float halfWidth = width / 2;
+ final float halfHeight = height / 2;
+ final int minOfWidthAndHeight = Math.min(width, height);
+ final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height,
+ 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.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);
+
+ final String firstCharString = name.substring(0, 1).toUpperCase();
+ final Rect textBound = new Rect();
+ paint.getTextBounds(firstCharString, 0, 1, textBound);
+
+ final Canvas canvas = new Canvas(bitmap);
+ final float xOffset = halfWidth - textBound.centerX();
+ final float yOffset = halfHeight - textBound.centerY();
+ canvas.drawText(firstCharString, xOffset, yOffset, paint);
+
+ return bitmap;
+ }
+
+ private int getBackgroundColor() {
+ return mContext.getResources().getColor(R.color.primary_color);
+ }
+
+ @Override
+ public int getCacheId() {
+ return BugleMediaCacheManager.AVATAR_IMAGE_CACHE;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java b/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java
new file mode 100644
index 0000000..9afa9ad
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/AvatarRequestDescriptor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.UriUtil;
+
+public class AvatarRequestDescriptor extends UriImageRequestDescriptor {
+ final boolean isWearBackground;
+
+ public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
+ final int desiredHeight) {
+ this(uri, desiredWidth, desiredHeight, true /* cropToCircle */);
+ }
+
+ public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
+ final int desiredHeight, final boolean cropToCircle) {
+ this(uri, desiredWidth, desiredHeight, cropToCircle, false /* isWearBackground */);
+ }
+
+ public AvatarRequestDescriptor(final Uri uri, final int desiredWidth,
+ final int desiredHeight, boolean cropToCircle, boolean isWearBackground) {
+ super(uri, desiredWidth, desiredHeight, false /* allowCompression */, true /* isStatic */,
+ cropToCircle,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ Assert.isTrue(uri == null || UriUtil.isLocalResourceUri(uri) ||
+ AvatarUriUtil.isAvatarUri(uri));
+ this.isWearBackground = isWearBackground;
+ }
+
+ @Override
+ public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
+ final String avatarType = uri == null ? null : AvatarUriUtil.getAvatarType(uri);
+ if (AvatarUriUtil.TYPE_SIM_SELECTOR_URI.equals(avatarType)) {
+ return new SimSelectorAvatarRequest(context, this);
+ } else {
+ return new AvatarRequest(context, this);
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/BindableMediaRequest.java b/src/com/android/messaging/datamodel/media/BindableMediaRequest.java
new file mode 100644
index 0000000..36521d5
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/BindableMediaRequest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.datamodel.media;
+
+import com.android.messaging.datamodel.binding.BindableOnceData;
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+
+/**
+ * The {@link MediaRequest} interface is threading-model-blind, allowing the implementations to
+ * be processed synchronously or asynchronously.
+ * This is a {@link MediaRequest} implementation that includes functionalities such as binding and
+ * event callbacks for multi-threaded media request processing.
+ */
+public abstract class BindableMediaRequest<T extends RefCountedMediaResource>
+ extends BindableOnceData
+ implements MediaRequest<T>, MediaResourceLoadListener<T> {
+ private MediaResourceLoadListener<T> mListener;
+
+ public BindableMediaRequest(final MediaResourceLoadListener<T> listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Delegates the media resource callback to the listener. Performs binding check to ensure
+ * the listener is still bound to this request.
+ */
+ @Override
+ public void onMediaResourceLoaded(final MediaRequest<T> request, final T resource,
+ final boolean cached) {
+ if (isBound() && mListener != null) {
+ mListener.onMediaResourceLoaded(request, resource, cached);
+ }
+ }
+
+ /**
+ * Delegates the media resource callback to the listener. Performs binding check to ensure
+ * the listener is still bound to this request.
+ */
+ @Override
+ public void onMediaResourceLoadError(final MediaRequest<T> request, final Exception exception) {
+ if (isBound() && mListener != null) {
+ mListener.onMediaResourceLoadError(request, exception);
+ }
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ mListener = null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java b/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java
new file mode 100644
index 0000000..c41ba60
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/BugleMediaCacheManager.java
@@ -0,0 +1,54 @@
+/*
+ * 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.datamodel.media;
+
+import com.android.messaging.util.Assert;
+
+/**
+ * An implementation of {@link MediaCacheManager} that creates caches specific to Bugle's needs.
+ *
+ * To create a new type of cache, add to the list of cache ids and create a new MediaCache<>
+ * for your cache id / media resource type in createMediaCacheById().
+ */
+public class BugleMediaCacheManager extends MediaCacheManager {
+ // List of available cache ids.
+ public static final int DEFAULT_IMAGE_CACHE = 1;
+ public static final int AVATAR_IMAGE_CACHE = 2;
+ public static final int VCARD_CACHE = 3;
+
+ // VCard cache size - we compute the size by count, not by bytes.
+ private static final int VCARD_CACHE_SIZE = 5;
+ private static final int SHARED_IMAGE_CACHE_SIZE = 1024 * 10; // 10MB
+
+ @Override
+ protected MediaCache<?> createMediaCacheById(final int id) {
+ switch (id) {
+ case DEFAULT_IMAGE_CACHE:
+ return new PoolableImageCache(SHARED_IMAGE_CACHE_SIZE, id, "DefaultImageCache");
+
+ case AVATAR_IMAGE_CACHE:
+ return new PoolableImageCache(id, "AvatarImageCache");
+
+ case VCARD_CACHE:
+ return new MediaCache<VCardResource>(VCARD_CACHE_SIZE, id, "VCardCache");
+
+ default:
+ Assert.fail("BugleMediaCacheManager: unsupported cache id " + id);
+ break;
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/CompositeImageRequest.java b/src/com/android/messaging/datamodel/media/CompositeImageRequest.java
new file mode 100644
index 0000000..66f1bff
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/CompositeImageRequest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ImageUtils;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Requests a composite image resource. The composite image resource is constructed by first
+ * sequentially requesting a number of sub image resources specified by
+ * {@link CompositeImageRequestDescriptor#getChildRequestDescriptors()}. After this, the
+ * individual sub images are composed into the final image onto their respective target rects
+ * returned by {@link CompositeImageRequestDescriptor#getChildRequestTargetRects()}.
+ */
+public class CompositeImageRequest<D extends CompositeImageRequestDescriptor>
+ extends ImageRequest<D> {
+ private final Bitmap mBitmap;
+ private final Canvas mCanvas;
+ private final Paint mPaint;
+
+ public CompositeImageRequest(final Context context, final D descriptor) {
+ super(context, descriptor);
+ mBitmap = getBitmapPool().createOrReuseBitmap(
+ mDescriptor.desiredWidth, mDescriptor.desiredHeight);
+ mCanvas = new Canvas(mBitmap);
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ }
+
+ @Override
+ protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTask) {
+ final List<? extends ImageRequestDescriptor> descriptors =
+ mDescriptor.getChildRequestDescriptors();
+ final List<RectF> targetRects = mDescriptor.getChildRequestTargetRects();
+ Assert.equals(descriptors.size(), targetRects.size());
+ Assert.isTrue(descriptors.size() > 1);
+
+ for (int i = 0; i < descriptors.size(); i++) {
+ final MediaRequest<ImageResource> request =
+ descriptors.get(i).buildSyncMediaRequest(mContext);
+ // Synchronously request the child image.
+ final ImageResource resource =
+ MediaResourceManager.get().requestMediaResourceSync(request);
+ if (resource != null) {
+ try {
+ final RectF avatarDestOnGroup = targetRects.get(i);
+
+ // Draw the bitmap into a smaller size with a circle mask.
+ final Bitmap resourceBitmap = resource.getBitmap();
+ final RectF resourceRect = new RectF(
+ 0, 0, resourceBitmap.getWidth(), resourceBitmap.getHeight());
+ final Bitmap smallCircleBitmap = getBitmapPool().createOrReuseBitmap(
+ Math.round(avatarDestOnGroup.width()),
+ Math.round(avatarDestOnGroup.height()));
+ final RectF smallCircleRect = new RectF(
+ 0, 0, smallCircleBitmap.getWidth(), smallCircleBitmap.getHeight());
+ final Canvas smallCircleCanvas = new Canvas(smallCircleBitmap);
+ ImageUtils.drawBitmapWithCircleOnCanvas(resource.getBitmap(), smallCircleCanvas,
+ resourceRect, smallCircleRect, null /* bitmapPaint */,
+ false /* fillBackground */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ final Matrix matrix = new Matrix();
+ matrix.setRectToRect(smallCircleRect, avatarDestOnGroup,
+ Matrix.ScaleToFit.FILL);
+ mCanvas.drawBitmap(smallCircleBitmap, matrix, mPaint);
+ } finally {
+ resource.release();
+ }
+ }
+ }
+
+ return new DecodedImageResource(getKey(), mBitmap, ExifInterface.ORIENTATION_NORMAL);
+ }
+
+ @Override
+ public int getCacheId() {
+ return BugleMediaCacheManager.AVATAR_IMAGE_CACHE;
+ }
+
+ @Override
+ protected InputStream getInputStreamForResource() throws FileNotFoundException {
+ throw new IllegalStateException("Composite image request doesn't support input stream!");
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java
new file mode 100644
index 0000000..071130e
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/CompositeImageRequestDescriptor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.graphics.RectF;
+
+import com.google.common.base.Joiner;
+
+import java.util.List;
+
+public abstract class CompositeImageRequestDescriptor extends ImageRequestDescriptor {
+ protected final List<? extends ImageRequestDescriptor> mDescriptors;
+ private final String mKey;
+
+ public CompositeImageRequestDescriptor(final List<? extends ImageRequestDescriptor> descriptors,
+ final int desiredWidth, final int desiredHeight) {
+ super(desiredWidth, desiredHeight);
+ mDescriptors = descriptors;
+
+ final String[] keyParts = new String[descriptors.size()];
+ for (int i = 0; i < descriptors.size(); i++) {
+ keyParts[i] = descriptors.get(i).getKey();
+ }
+ mKey = Joiner.on(",").skipNulls().join(keyParts);
+ }
+
+ /**
+ * Gets a key that uniquely identify all the underlying image resource to be loaded (e.g. Uri or
+ * file path).
+ */
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ public List<? extends ImageRequestDescriptor> getChildRequestDescriptors(){
+ return mDescriptors;
+ }
+
+ public abstract List<RectF> getChildRequestTargetRects();
+ public abstract CompositeImageRequest<?> buildBatchImageRequest(final Context context);
+
+ @Override
+ public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
+ return buildBatchImageRequest(context);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/CustomVCardEntry.java b/src/com/android/messaging/datamodel/media/CustomVCardEntry.java
new file mode 100644
index 0000000..aee9fdc
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/CustomVCardEntry.java
@@ -0,0 +1,48 @@
+/*
+ * 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.datamodel.media;
+
+import android.accounts.Account;
+import android.support.v4.util.ArrayMap;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
+
+import java.util.Map;
+
+/**
+ * Class which extends VCardEntry to add support for unknown properties. Currently there is a TODO
+ * to add this in the VCardEntry code, but we have to extend it to add the needed support
+ */
+public class CustomVCardEntry extends VCardEntry {
+ // List of properties keyed by their name for easy lookup
+ private final Map<String, VCardProperty> mAllProperties;
+
+ public CustomVCardEntry(int vCardType, Account account) {
+ super(vCardType, account);
+ mAllProperties = new ArrayMap<String, VCardProperty>();
+ }
+
+ @Override
+ public void addProperty(VCardProperty property) {
+ super.addProperty(property);
+ mAllProperties.put(property.getName(), property);
+ }
+
+ public VCardProperty getProperty(String name) {
+ return mAllProperties.get(name);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java b/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java
new file mode 100644
index 0000000..06b10a3
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/CustomVCardEntryConstructor.java
@@ -0,0 +1,133 @@
+/*
+ * 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.datamodel.media;
+
+import android.accounts.Account;
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CustomVCardEntryConstructor implements VCardInterpreter {
+
+ public interface EntryHandler {
+ /**
+ * Called when the parsing started.
+ */
+ public void onStart();
+
+ /**
+ * The method called when one vCard entry is created. Children come before their parent in
+ * nested vCard files.
+ *
+ * e.g.
+ * In the following vCard, the entry for "entry2" comes before one for "entry1".
+ * <code>
+ * BEGIN:VCARD
+ * N:entry1
+ * BEGIN:VCARD
+ * N:entry2
+ * END:VCARD
+ * END:VCARD
+ * </code>
+ */
+ public void onEntryCreated(final CustomVCardEntry entry);
+
+ /**
+ * Called when the parsing ended.
+ * Able to be use this method for showing performance log, etc.
+ */
+ public void onEnd();
+ }
+
+ /**
+ * Represents current stack of VCardEntry. Used to support nested vCard (vCard 2.1).
+ */
+ private final List<CustomVCardEntry> mEntryStack = new ArrayList<CustomVCardEntry>();
+ private CustomVCardEntry mCurrentEntry;
+
+ private final int mVCardType;
+ private final Account mAccount;
+
+ private final List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
+
+ public CustomVCardEntryConstructor() {
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+ }
+
+ public CustomVCardEntryConstructor(final int vcardType) {
+ this(vcardType, null);
+ }
+
+ public CustomVCardEntryConstructor(final int vcardType, final Account account) {
+ mVCardType = vcardType;
+ mAccount = account;
+ }
+
+ public void addEntryHandler(EntryHandler entryHandler) {
+ mEntryHandlers.add(entryHandler);
+ }
+
+ @Override
+ public void onVCardStarted() {
+ for (EntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onStart();
+ }
+ }
+
+ @Override
+ public void onVCardEnded() {
+ for (EntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onEnd();
+ }
+ }
+
+ public void clear() {
+ mCurrentEntry = null;
+ mEntryStack.clear();
+ }
+
+ @Override
+ public void onEntryStarted() {
+ mCurrentEntry = new CustomVCardEntry(mVCardType, mAccount);
+ mEntryStack.add(mCurrentEntry);
+ }
+
+ @Override
+ public void onEntryEnded() {
+ mCurrentEntry.consolidateFields();
+ for (EntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onEntryCreated(mCurrentEntry);
+ }
+
+ final int size = mEntryStack.size();
+ if (size > 1) {
+ CustomVCardEntry parent = mEntryStack.get(size - 2);
+ parent.addChild(mCurrentEntry);
+ mCurrentEntry = parent;
+ } else {
+ mCurrentEntry = null;
+ }
+ mEntryStack.remove(size - 1);
+ }
+
+ @Override
+ public void onPropertyCreated(VCardProperty property) {
+ mCurrentEntry.addProperty(property);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/DecodedImageResource.java b/src/com/android/messaging/datamodel/media/DecodedImageResource.java
new file mode 100644
index 0000000..3627ba4
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/DecodedImageResource.java
@@ -0,0 +1,254 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+import com.android.messaging.ui.OrientedBitmapDrawable;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+import java.util.List;
+
+
+/**
+ * Container class for holding a bitmap resource used by the MediaResourceManager. This resource
+ * can both be cached (albeit not very storage-efficiently) and directly used by the UI.
+ */
+public class DecodedImageResource extends ImageResource {
+ private static final int BITMAP_QUALITY = 100;
+ private static final int COMPRESS_QUALITY = 50;
+
+ private Bitmap mBitmap;
+ private final int mOrientation;
+ private boolean mCacheable = true;
+
+ public DecodedImageResource(final String key, final Bitmap bitmap, int orientation) {
+ super(key, orientation);
+ mBitmap = bitmap;
+ mOrientation = orientation;
+ }
+
+ /**
+ * Gets the contained bitmap.
+ */
+ @Override
+ public Bitmap getBitmap() {
+ acquireLock();
+ try {
+ return mBitmap;
+ } finally {
+ releaseLock();
+ }
+ }
+
+ /**
+ * Attempt to reuse the bitmap in the image resource and repurpose it for something else.
+ * After this, the image resource will relinquish ownership on the bitmap resource so that
+ * it doesn't try to recycle it when getting closed.
+ */
+ @Override
+ public Bitmap reuseBitmap() {
+ acquireLock();
+ try {
+ assertSingularRefCount();
+ final Bitmap retBitmap = mBitmap;
+ mBitmap = null;
+ return retBitmap;
+ } finally {
+ releaseLock();
+ }
+ }
+
+ @Override
+ public boolean supportsBitmapReuse() {
+ return true;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ acquireLock();
+ try {
+ return ImageUtils.bitmapToBytes(mBitmap, BITMAP_QUALITY);
+ } catch (final Exception e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error trying to get the bitmap bytes " + e);
+ } finally {
+ releaseLock();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants
+ */
+ @Override
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ @Override
+ public int getMediaSize() {
+ acquireLock();
+ try {
+ Assert.notNull(mBitmap);
+ if (OsUtil.isAtLeastKLP()) {
+ return mBitmap.getAllocationByteCount();
+ } else {
+ return mBitmap.getRowBytes() * mBitmap.getHeight();
+ }
+ } finally {
+ releaseLock();
+ }
+ }
+
+ @Override
+ protected void close() {
+ acquireLock();
+ try {
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ mBitmap = null;
+ }
+ } finally {
+ releaseLock();
+ }
+ }
+
+ @Override
+ public Drawable getDrawable(Resources resources) {
+ acquireLock();
+ try {
+ Assert.notNull(mBitmap);
+ return OrientedBitmapDrawable.create(getOrientation(), resources, mBitmap);
+ } finally {
+ releaseLock();
+ }
+ }
+
+ @Override
+ boolean isCacheable() {
+ return mCacheable;
+ }
+
+ public void setCacheable(final boolean cacheable) {
+ mCacheable = cacheable;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest(
+ final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
+ Assert.isFalse(isEncoded());
+ if (getBitmap().hasAlpha()) {
+ // We can't compress images with alpha, as JPEG encoding doesn't support this.
+ return null;
+ }
+ return new EncodeImageRequest((MediaRequest<ImageResource>) originalRequest);
+ }
+
+ /**
+ * A MediaRequest that encodes the contained image resource.
+ */
+ private class EncodeImageRequest implements MediaRequest<ImageResource> {
+ private final MediaRequest<ImageResource> mOriginalImageRequest;
+
+ public EncodeImageRequest(MediaRequest<ImageResource> originalImageRequest) {
+ mOriginalImageRequest = originalImageRequest;
+ // Hold a ref onto the encoded resource before the request finishes.
+ DecodedImageResource.this.addRef();
+ }
+
+ @Override
+ public String getKey() {
+ return DecodedImageResource.this.getKey();
+ }
+
+ @Override
+ @DoesNotRunOnMainThread
+ public ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedRequests)
+ throws Exception {
+ Assert.isNotMainThread();
+ acquireLock();
+ Bitmap scaledBitmap = null;
+ try {
+ Bitmap bitmap = getBitmap();
+ Assert.isFalse(bitmap.hasAlpha());
+ final int bitmapWidth = bitmap.getWidth();
+ final int bitmapHeight = bitmap.getHeight();
+ // The original bitmap was loaded using sub-sampling which was fast in terms of
+ // loading speed, but not optimized for caching, encoding and rendering (since
+ // bitmap resizing to fit the UI image views happens on the UI thread and should
+ // be avoided if possible). Therefore, try to resize the bitmap to the exact desired
+ // size before compressing it.
+ if (bitmapWidth > 0 && bitmapHeight > 0 &&
+ mOriginalImageRequest instanceof ImageRequest<?>) {
+ final ImageRequestDescriptor descriptor =
+ ((ImageRequest<?>) mOriginalImageRequest).getDescriptor();
+ final float targetScale = Math.max(
+ (float) descriptor.desiredWidth / bitmapWidth,
+ (float) descriptor.desiredHeight / bitmapHeight);
+ final int targetWidth = (int) (bitmapWidth * targetScale);
+ final int targetHeight = (int) (bitmapHeight * targetScale);
+ // Only try to scale down the image to the desired size.
+ if (targetScale < 1.0f && targetWidth > 0 && targetHeight > 0 &&
+ targetWidth != bitmapWidth && targetHeight != bitmapHeight) {
+ scaledBitmap = bitmap =
+ Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false);
+ }
+ }
+ byte[] encodedBytes = ImageUtils.bitmapToBytes(bitmap, COMPRESS_QUALITY);
+ return new EncodedImageResource(getKey(), encodedBytes, getOrientation());
+ } catch (Exception ex) {
+ // Something went wrong during bitmap compression, fall back to just using the
+ // original bitmap.
+ LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "Error compressing bitmap", ex);
+ return DecodedImageResource.this;
+ } finally {
+ if (scaledBitmap != null && scaledBitmap != getBitmap()) {
+ scaledBitmap.recycle();
+ scaledBitmap = null;
+ }
+ releaseLock();
+ release();
+ }
+ }
+
+ @Override
+ public MediaCache<ImageResource> getMediaCache() {
+ return mOriginalImageRequest.getMediaCache();
+ }
+
+ @Override
+ public int getCacheId() {
+ return mOriginalImageRequest.getCacheId();
+ }
+
+ @Override
+ public int getRequestType() {
+ return REQUEST_ENCODE_MEDIA;
+ }
+
+ @Override
+ public MediaRequestDescriptor<ImageResource> getDescriptor() {
+ return mOriginalImageRequest.getDescriptor();
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/EncodedImageResource.java b/src/com/android/messaging/datamodel/media/EncodedImageResource.java
new file mode 100644
index 0000000..0bc94e5
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/EncodedImageResource.java
@@ -0,0 +1,162 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A cache-facing image resource that's much more compact than the raw Bitmap objects stored in
+ * {@link com.android.messaging.datamodel.media.DecodedImageResource}.
+ *
+ * This resource is created from a regular Bitmap-based ImageResource before being pushed to
+ * {@link com.android.messaging.datamodel.media.MediaCache}, if the image request
+ * allows for resource encoding/compression.
+ *
+ * During resource retrieval on cache hit,
+ * {@link #getMediaDecodingRequest(MediaRequest)} is invoked to create a async
+ * decode task, which decodes the compressed byte array back to a regular image resource to
+ * be consumed by the UI.
+ */
+public class EncodedImageResource extends ImageResource {
+ private final byte[] mImageBytes;
+
+ public EncodedImageResource(String key, byte[] imageBytes, int orientation) {
+ super(key, orientation);
+ mImageBytes = imageBytes;
+ }
+
+ @Override
+ @DoesNotRunOnMainThread
+ public Bitmap getBitmap() {
+ acquireLock();
+ try {
+ // This should only be called during the decode request.
+ Assert.isNotMainThread();
+ return BitmapFactory.decodeByteArray(mImageBytes, 0, mImageBytes.length);
+ } finally {
+ releaseLock();
+ }
+ }
+
+ @Override
+ public byte[] getBytes() {
+ acquireLock();
+ try {
+ return Arrays.copyOf(mImageBytes, mImageBytes.length);
+ } finally {
+ releaseLock();
+ }
+ }
+
+ @Override
+ public Bitmap reuseBitmap() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsBitmapReuse() {
+ return false;
+ }
+
+ @Override
+ public int getMediaSize() {
+ return mImageBytes.length;
+ }
+
+ @Override
+ protected void close() {
+ }
+
+ @Override
+ public Drawable getDrawable(Resources resources) {
+ return null;
+ }
+
+ @Override
+ boolean isEncoded() {
+ return true;
+ }
+
+ @Override
+ MediaRequest<? extends RefCountedMediaResource> getMediaDecodingRequest(
+ final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
+ Assert.isTrue(isEncoded());
+ return new DecodeImageRequest();
+ }
+
+ /**
+ * A MediaRequest that decodes the encoded image resource. This class is chained to the
+ * original media request that requested the image, so it inherits the listener and
+ * properties such as binding.
+ */
+ private class DecodeImageRequest implements MediaRequest<ImageResource> {
+ public DecodeImageRequest() {
+ // Hold a ref onto the encoded resource before the request finishes.
+ addRef();
+ }
+
+ @Override
+ public String getKey() {
+ return EncodedImageResource.this.getKey();
+ }
+
+ @Override
+ @DoesNotRunOnMainThread
+ public ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedTask)
+ throws Exception {
+ Assert.isNotMainThread();
+ acquireLock();
+ try {
+ final Bitmap decodedBitmap = BitmapFactory.decodeByteArray(mImageBytes, 0,
+ mImageBytes.length);
+ return new DecodedImageResource(getKey(), decodedBitmap, getOrientation());
+ } finally {
+ releaseLock();
+ release();
+ }
+ }
+
+ @Override
+ public MediaCache<ImageResource> getMediaCache() {
+ // Decoded resource is non-cachable, it's for UI consumption only (for now at least)
+ return null;
+ }
+
+ @Override
+ public int getCacheId() {
+ return 0;
+ }
+
+ @Override
+ public int getRequestType() {
+ return REQUEST_DECODE_MEDIA;
+ }
+
+ @Override
+ public MediaRequestDescriptor<ImageResource> getDescriptor() {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/FileImageRequest.java b/src/com/android/messaging/datamodel/media/FileImageRequest.java
new file mode 100644
index 0000000..31c053a
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/FileImageRequest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.ExifInterface;
+
+import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.LogUtil;
+
+import java.io.IOException;
+
+/**
+ * Serves file system based image requests. Since file paths can be expressed in Uri form, this
+ * extends regular UriImageRequest but performs additional optimizations such as loading thumbnails
+ * directly from Exif information.
+ */
+public class FileImageRequest extends UriImageRequest {
+ private final String mPath;
+ private final boolean mCanUseThumbnail;
+
+ public FileImageRequest(final Context context,
+ final FileImageRequestDescriptor descriptor) {
+ super(context, descriptor);
+ mPath = descriptor.path;
+ mCanUseThumbnail = descriptor.canUseThumbnail;
+ }
+
+ @Override
+ protected Bitmap loadBitmapInternal()
+ throws IOException {
+ // Before using the FileInputStream, check if the Exif has a thumbnail that we can use.
+ if (mCanUseThumbnail) {
+ byte[] thumbnail = null;
+ try {
+ final ExifInterface exif = new ExifInterface(mPath);
+ if (exif.hasThumbnail()) {
+ thumbnail = exif.getThumbnail();
+ }
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+
+ if (thumbnail != null) {
+ final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool(
+ false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */);
+ // First, check dimensions of the bitmap.
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = ImageUtils.get().calculateInSampleSize(options,
+ mDescriptor.desiredWidth, mDescriptor.desiredHeight);
+
+ options.inJustDecodeBounds = false;
+
+ // Actually decode the bitmap, optionally using the bitmap pool.
+ try {
+ // Get the orientation. We should be able to get the orientation from
+ // the thumbnail itself but at least on some phones, the thumbnail
+ // doesn't have an orientation tag. So use the outer image's orientation
+ // tag and hope for the best.
+ mOrientation = ImageUtils.getOrientation(getInputStreamForResource());
+ if (com.android.messaging.util.exif.ExifInterface.
+ getOrientationParams(mOrientation).invertDimensions) {
+ mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth);
+ } else {
+ mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight);
+ }
+ final ReusableImageResourcePool bitmapPool = getBitmapPool();
+ if (bitmapPool == null) {
+ return BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length,
+ options);
+ } else {
+ final int sampledWidth = options.outWidth / options.inSampleSize;
+ final int sampledHeight = options.outHeight / options.inSampleSize;
+ return bitmapPool.decodeByteArray(thumbnail, options, sampledWidth,
+ sampledHeight);
+ }
+ } catch (IOException ex) {
+ // If the thumbnail is broken due to IOException, this will
+ // fall back to default bitmap loading.
+ LogUtil.e(LogUtil.BUGLE_IMAGE_TAG, "FileImageRequest: failed to load " +
+ "thumbnail from Exif", ex);
+ }
+ }
+ }
+
+ // Fall back to default InputStream-based loading if no thumbnails could be retrieved.
+ return super.loadBitmapInternal();
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java
new file mode 100644
index 0000000..00105f5
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/FileImageRequestDescriptor.java
@@ -0,0 +1,68 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.UriUtil;
+
+/**
+ * Holds image request info about file system based image resource.
+ */
+public class FileImageRequestDescriptor extends UriImageRequestDescriptor {
+ public final String path;
+
+ // Can we use the thumbnail image from Exif data?
+ public final boolean canUseThumbnail;
+
+ /**
+ * Convenience constructor for when the image file's dimensions are not known.
+ */
+ public FileImageRequestDescriptor(final String path, final int desiredWidth,
+ final int desiredHeight, final boolean canUseThumbnail, final boolean canCompress,
+ final boolean isStatic) {
+ this(path, desiredWidth, desiredHeight, FileImageRequest.UNSPECIFIED_SIZE,
+ FileImageRequest.UNSPECIFIED_SIZE, canUseThumbnail, canCompress, isStatic);
+ }
+
+ /**
+ * Creates a new file image request with this descriptor. Oftentimes image file metadata
+ * has information such as the size of the image. Provide these metrics if they are known.
+ */
+ public FileImageRequestDescriptor(final String path, final int desiredWidth,
+ final int desiredHeight, final int sourceWidth, final int sourceHeight,
+ final boolean canUseThumbnail, final boolean canCompress, final boolean isStatic) {
+ super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth,
+ sourceHeight, canCompress, isStatic, false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ this.path = path;
+ this.canUseThumbnail = canUseThumbnail;
+ }
+
+ @Override
+ public String getKey() {
+ final String prefixKey = super.getKey();
+ return prefixKey == null ? null : new StringBuilder(prefixKey).append(KEY_PART_DELIMITER)
+ .append(canUseThumbnail).toString();
+ }
+
+ @Override
+ public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
+ return new FileImageRequest(context, this);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/GifImageResource.java b/src/com/android/messaging/datamodel/media/GifImageResource.java
new file mode 100644
index 0000000..d50cf47
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/GifImageResource.java
@@ -0,0 +1,110 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.media.ExifInterface;
+import android.support.rastermill.FrameSequence;
+import android.support.rastermill.FrameSequenceDrawable;
+
+import com.android.messaging.util.Assert;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class GifImageResource extends ImageResource {
+ private FrameSequence mFrameSequence;
+
+ public GifImageResource(String key, FrameSequence frameSequence) {
+ // GIF does not support exif tags
+ super(key, ExifInterface.ORIENTATION_NORMAL);
+ mFrameSequence = frameSequence;
+ }
+
+ public static GifImageResource createGifImageResource(String key, InputStream inputStream) {
+ final FrameSequence frameSequence;
+ try {
+ frameSequence = FrameSequence.decodeStream(inputStream);
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Nothing to do if we fail closing the stream
+ }
+ }
+ if (frameSequence == null) {
+ return null;
+ }
+ return new GifImageResource(key, frameSequence);
+ }
+
+ @Override
+ public Drawable getDrawable(Resources resources) {
+ return new FrameSequenceDrawable(mFrameSequence);
+ }
+
+ @Override
+ public Bitmap getBitmap() {
+ Assert.fail("GetBitmap() should never be called on a gif.");
+ return null;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ Assert.fail("GetBytes() should never be called on a gif.");
+ return null;
+ }
+
+ @Override
+ public Bitmap reuseBitmap() {
+ return null;
+ }
+
+ @Override
+ public boolean supportsBitmapReuse() {
+ // FrameSequenceDrawable a.) takes two bitmaps and thus does not fit into the current
+ // bitmap pool architecture b.) will rarely use bitmaps from one FrameSequenceDrawable to
+ // the next that are the same sizes since they are used by attachments.
+ return false;
+ }
+
+ @Override
+ public int getMediaSize() {
+ Assert.fail("GifImageResource should not be used by a media cache");
+ // Only used by the media cache, which this does not use.
+ return 0;
+ }
+
+ @Override
+ public boolean isCacheable() {
+ return false;
+ }
+
+ @Override
+ protected void close() {
+ acquireLock();
+ try {
+ if (mFrameSequence != null) {
+ mFrameSequence = null;
+ }
+ } finally {
+ releaseLock();
+ }
+ }
+
+}
diff --git a/src/com/android/messaging/datamodel/media/ImageRequest.java b/src/com/android/messaging/datamodel/media/ImageRequest.java
new file mode 100644
index 0000000..ab8880d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/ImageRequest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.exif.ExifInterface;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Base class that serves an image request for resolving, retrieving and decoding bitmap resources.
+ *
+ * Subclasses may choose to load images from different medium, such as from the file system or
+ * from the local content resolver, by overriding the abstract getInputStreamForResource() method.
+ */
+public abstract class ImageRequest<D extends ImageRequestDescriptor>
+ implements MediaRequest<ImageResource> {
+ public static final int UNSPECIFIED_SIZE = MessagePartData.UNSPECIFIED_SIZE;
+
+ protected final Context mContext;
+ protected final D mDescriptor;
+ protected int mOrientation;
+
+ /**
+ * Creates a new image request with the given descriptor.
+ */
+ public ImageRequest(final Context context, final D descriptor) {
+ mContext = context;
+ mDescriptor = descriptor;
+ }
+
+ /**
+ * Gets a key that uniquely identify the underlying image resource to be loaded (e.g. Uri or
+ * file path).
+ */
+ @Override
+ public String getKey() {
+ return mDescriptor.getKey();
+ }
+
+ /**
+ * Returns the image request descriptor attached to this request.
+ */
+ @Override
+ public D getDescriptor() {
+ return mDescriptor;
+ }
+
+ @Override
+ public int getRequestType() {
+ return MediaRequest.REQUEST_LOAD_MEDIA;
+ }
+
+ /**
+ * Allows sub classes to specify that they want us to call getBitmapForResource rather than
+ * getInputStreamForResource
+ */
+ protected boolean hasBitmapObject() {
+ return false;
+ }
+
+ protected Bitmap getBitmapForResource() throws IOException {
+ return null;
+ }
+
+ /**
+ * Retrieves an input stream from which image resource could be loaded.
+ * @throws FileNotFoundException
+ */
+ protected abstract InputStream getInputStreamForResource() throws FileNotFoundException;
+
+ /**
+ * Loads the image resource. This method is final; to override the media loading behavior
+ * the subclass should override {@link #loadMediaInternal(List)}
+ */
+ @Override
+ public final ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedTask)
+ throws IOException {
+ Assert.isNotMainThread();
+ final ImageResource loadedResource = loadMediaInternal(chainedTask);
+ return postProcessOnBitmapResourceLoaded(loadedResource);
+ }
+
+ protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTask)
+ throws IOException {
+ if (!mDescriptor.isStatic() && isGif()) {
+ final GifImageResource gifImageResource =
+ GifImageResource.createGifImageResource(getKey(), getInputStreamForResource());
+ if (gifImageResource == null) {
+ throw new RuntimeException("Error decoding gif");
+ }
+ return gifImageResource;
+ } else {
+ final Bitmap loadedBitmap = loadBitmapInternal();
+ if (loadedBitmap == null) {
+ throw new RuntimeException("failed decoding bitmap");
+ }
+ return new DecodedImageResource(getKey(), loadedBitmap, mOrientation);
+ }
+ }
+
+ protected boolean isGif() throws FileNotFoundException {
+ return ImageUtils.isGif(getInputStreamForResource());
+ }
+
+ /**
+ * The internal routine for loading the image. The caller may optionally provide the width
+ * and height of the source image if known so that we don't need to manually decode those.
+ */
+ protected Bitmap loadBitmapInternal() throws IOException {
+
+ final boolean unknownSize = mDescriptor.sourceWidth == UNSPECIFIED_SIZE ||
+ mDescriptor.sourceHeight == UNSPECIFIED_SIZE;
+
+ // If the ImageRequest has a Bitmap object rather than a stream, there's little to do here
+ if (hasBitmapObject()) {
+ final Bitmap bitmap = getBitmapForResource();
+ if (bitmap != null && unknownSize) {
+ mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight());
+ }
+ return bitmap;
+ }
+
+ mOrientation = ImageUtils.getOrientation(getInputStreamForResource());
+
+ final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool(
+ false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */);
+ // First, check dimensions of the bitmap if not already known.
+ if (unknownSize) {
+ final InputStream inputStream = getInputStreamForResource();
+ if (inputStream != null) {
+ try {
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+ // This is called when dimensions of image were unknown to allow db update
+ if (ExifInterface.getOrientationParams(mOrientation).invertDimensions) {
+ mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth);
+ } else {
+ mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight);
+ }
+ } finally {
+ inputStream.close();
+ }
+ } else {
+ throw new FileNotFoundException();
+ }
+ } else {
+ options.outWidth = mDescriptor.sourceWidth;
+ options.outHeight = mDescriptor.sourceHeight;
+ }
+
+ // Calculate inSampleSize
+ options.inSampleSize = ImageUtils.get().calculateInSampleSize(options,
+ mDescriptor.desiredWidth, mDescriptor.desiredHeight);
+ Assert.isTrue(options.inSampleSize > 0);
+
+ // Reopen the input stream and actually decode the bitmap. The initial
+ // BitmapFactory.decodeStream() reads the header portion of the bitmap stream and leave
+ // the input stream at the last read position. Since this input stream doesn't support
+ // mark() and reset(), the only viable way to reload the input stream is to re-open it.
+ // Alternatively, we could decode the bitmap into a byte array first and act on the byte
+ // array, but that also means the entire bitmap (for example a 10MB image from the gallery)
+ // without downsampling will have to be loaded into memory up front, which we don't want
+ // as it gives a much bigger possibility of OOM when handling big images. Therefore, the
+ // solution here is to close and reopen the bitmap input stream.
+ // For inline images the size is cached in DB and this hit is only taken once per image
+ final InputStream inputStream = getInputStreamForResource();
+ if (inputStream != null) {
+ try {
+ options.inJustDecodeBounds = false;
+
+ // Actually decode the bitmap, optionally using the bitmap pool.
+ final ReusableImageResourcePool bitmapPool = getBitmapPool();
+ if (bitmapPool == null) {
+ return BitmapFactory.decodeStream(inputStream, null, options);
+ } else {
+ final int sampledWidth = (options.outWidth + options.inSampleSize - 1) /
+ options.inSampleSize;
+ final int sampledHeight = (options.outHeight + options.inSampleSize - 1) /
+ options.inSampleSize;
+ return bitmapPool.decodeSampledBitmapFromInputStream(
+ inputStream, options, sampledWidth, sampledHeight);
+ }
+ } finally {
+ inputStream.close();
+ }
+ } else {
+ throw new FileNotFoundException();
+ }
+ }
+
+ private ImageResource postProcessOnBitmapResourceLoaded(final ImageResource loadedResource) {
+ if (mDescriptor.cropToCircle && loadedResource instanceof DecodedImageResource) {
+ final int width = mDescriptor.desiredWidth;
+ final int height = mDescriptor.desiredHeight;
+ final Bitmap sourceBitmap = loadedResource.getBitmap();
+ final Bitmap targetBitmap = getBitmapPool().createOrReuseBitmap(width, height);
+ final RectF dest = new RectF(0, 0, width, height);
+ final RectF source = new RectF(0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight());
+ final int backgroundColor = mDescriptor.circleBackgroundColor;
+ final int strokeColor = mDescriptor.circleStrokeColor;
+ ImageUtils.drawBitmapWithCircleOnCanvas(sourceBitmap, new Canvas(targetBitmap), source,
+ dest, null, backgroundColor == 0 ? false : true /* fillBackground */,
+ backgroundColor, strokeColor);
+ return new DecodedImageResource(getKey(), targetBitmap,
+ loadedResource.getOrientation());
+ }
+ return loadedResource;
+ }
+
+ /**
+ * Returns the bitmap pool for this image request.
+ */
+ protected ReusableImageResourcePool getBitmapPool() {
+ return MediaCacheManager.get().getOrCreateBitmapPoolForCache(getCacheId());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public MediaCache<ImageResource> getMediaCache() {
+ return (MediaCache<ImageResource>) MediaCacheManager.get().getOrCreateMediaCacheById(
+ getCacheId());
+ }
+
+ /**
+ * Returns the cache id. Subclasses may override this to use a different cache.
+ */
+ @Override
+ public int getCacheId() {
+ return BugleMediaCacheManager.DEFAULT_IMAGE_CACHE;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java
new file mode 100644
index 0000000..20cb9af
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/ImageRequestDescriptor.java
@@ -0,0 +1,113 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+
+import com.android.messaging.util.Assert;
+
+/**
+ * The base ImageRequest descriptor that describes the requirement of the requested image
+ * resource, including the desired size. It holds request info that will be consumed by
+ * ImageRequest instances. Subclasses of ImageRequest are expected to take
+ * more descriptions such as content URI or file path.
+ */
+public abstract class ImageRequestDescriptor extends MediaRequestDescriptor<ImageResource> {
+ /** Desired size for the image (if known). This is used for bitmap downsampling */
+ public final int desiredWidth;
+ public final int desiredHeight;
+
+ /** Source size of the image (if known). This is used so that we don't have to manually decode
+ * the metrics from the image resource */
+ public final int sourceWidth;
+ public final int sourceHeight;
+
+ /**
+ * A static image resource is required, even if the image format supports animation (like Gif).
+ */
+ public final boolean isStatic;
+
+ /**
+ * The loaded image will be cropped to circular shape.
+ */
+ public final boolean cropToCircle;
+
+ /**
+ * The loaded image will be cropped to circular shape with the background color.
+ */
+ public final int circleBackgroundColor;
+
+ /**
+ * The loaded image will be cropped to circular shape with a stroke color.
+ */
+ public final int circleStrokeColor;
+
+ protected static final char KEY_PART_DELIMITER = '|';
+
+ /**
+ * Creates a new image request with unspecified width and height. In this case, the full
+ * bitmap is loaded and decoded, so unless you are sure that the image will be of
+ * reasonable size, you should consider limiting at least one of the two dimensions
+ * (for example, limiting the image width to the width of the ImageView container).
+ */
+ public ImageRequestDescriptor() {
+ this(ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE,
+ ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false, false, 0, 0);
+ }
+
+ public ImageRequestDescriptor(final int desiredWidth, final int desiredHeight) {
+ this(desiredWidth, desiredHeight,
+ ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false, false, 0, 0);
+ }
+
+ public ImageRequestDescriptor(final int desiredWidth,
+ final int desiredHeight, final int sourceWidth, final int sourceHeight,
+ final boolean isStatic, final boolean cropToCircle, final int circleBackgroundColor,
+ int circleStrokeColor) {
+ Assert.isTrue(desiredWidth == ImageRequest.UNSPECIFIED_SIZE || desiredWidth > 0);
+ Assert.isTrue(desiredHeight == ImageRequest.UNSPECIFIED_SIZE || desiredHeight > 0);
+ Assert.isTrue(sourceWidth == ImageRequest.UNSPECIFIED_SIZE || sourceWidth > 0);
+ Assert.isTrue(sourceHeight == ImageRequest.UNSPECIFIED_SIZE || sourceHeight > 0);
+ this.desiredWidth = desiredWidth;
+ this.desiredHeight = desiredHeight;
+ this.sourceWidth = sourceWidth;
+ this.sourceHeight = sourceHeight;
+ this.isStatic = isStatic;
+ this.cropToCircle = cropToCircle;
+ this.circleBackgroundColor = circleBackgroundColor;
+ this.circleStrokeColor = circleStrokeColor;
+ }
+
+ public String getKey() {
+ return new StringBuilder()
+ .append(desiredWidth).append(KEY_PART_DELIMITER)
+ .append(desiredHeight).append(KEY_PART_DELIMITER)
+ .append(String.valueOf(cropToCircle)).append(KEY_PART_DELIMITER)
+ .append(String.valueOf(circleBackgroundColor)).append(KEY_PART_DELIMITER)
+ .append(String.valueOf(isStatic)).toString();
+ }
+
+ public boolean isStatic() {
+ return isStatic;
+ }
+
+ @Override
+ public abstract MediaRequest<ImageResource> buildSyncMediaRequest(Context context);
+
+ // Called once source dimensions finally determined upon loading the image
+ public void updateSourceDimensions(final int sourceWidth, final int sourceHeight) {
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/ImageResource.java b/src/com/android/messaging/datamodel/media/ImageResource.java
new file mode 100644
index 0000000..75d817d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/ImageResource.java
@@ -0,0 +1,63 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Base class for holding some form of image resource. The subclass gets to define the specific
+ * type of data format it's holding, whether it be Bitmap objects or compressed byte arrays.
+ */
+public abstract class ImageResource extends RefCountedMediaResource {
+ protected final int mOrientation;
+
+ public ImageResource(final String key, int orientation) {
+ super(key);
+ mOrientation = orientation;
+ }
+
+ /**
+ * Gets the contained image in drawable format.
+ */
+ public abstract Drawable getDrawable(Resources resources);
+
+ /**
+ * Gets the contained image in bitmap format.
+ */
+ public abstract Bitmap getBitmap();
+
+ /**
+ * Gets the contained image in byte array format.
+ */
+ public abstract byte[] getBytes();
+
+ /**
+ * Attempt to reuse the bitmap in the image resource and re-purpose it for something else.
+ * After this, the image resource will relinquish ownership on the bitmap resource so that
+ * it doesn't try to recycle it when getting closed.
+ */
+ public abstract Bitmap reuseBitmap();
+ public abstract boolean supportsBitmapReuse();
+
+ /**
+ * Gets the orientation of the image as one of the ExifInterface.ORIENTATION_* constants
+ */
+ public int getOrientation() {
+ return mOrientation;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/MediaBytes.java b/src/com/android/messaging/datamodel/media/MediaBytes.java
new file mode 100644
index 0000000..823bf27
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MediaBytes.java
@@ -0,0 +1,42 @@
+/*
+ * 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.datamodel.media;
+
+
+/**
+ * Container class for handing around media information used by the MediaResourceManager.
+ */
+public class MediaBytes extends RefCountedMediaResource {
+ private final byte[] mBytes;
+
+ public MediaBytes(final String key, final byte[] bytes) {
+ super(key);
+ mBytes = bytes;
+ }
+
+ public byte[] getMediaBytes() {
+ return mBytes;
+ }
+
+ @Override
+ public int getMediaSize() {
+ return mBytes.length;
+ }
+
+ @Override
+ protected void close() {
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/MediaCache.java b/src/com/android/messaging/datamodel/media/MediaCache.java
new file mode 100644
index 0000000..510da2d
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MediaCache.java
@@ -0,0 +1,113 @@
+/*
+ * 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.datamodel.media;
+
+import android.util.LruCache;
+
+import com.android.messaging.util.LogUtil;
+
+/**
+ * A modified LruCache that is able to hold RefCountedMediaResource instances. It releases
+ * ref on the entries as they are evicted from the cache, and it uses the media resource
+ * size in kilobytes, instead of the entry count, as the size of the cache.
+ *
+ * This class is used by the MediaResourceManager class to maintain a number of caches for
+ * holding different types of {@link RefCountedMediaResource}
+ */
+public class MediaCache<T extends RefCountedMediaResource> extends LruCache<String, T> {
+ private static final String TAG = LogUtil.BUGLE_IMAGE_TAG;
+
+ // Default memory cache size in kilobytes
+ protected static final int DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES = 1024 * 5; // 5MB
+
+ // Unique identifier for the cache.
+ private final int mId;
+ // Descriptive name given to the cache for debugging purposes.
+ private final String mName;
+
+ // Convenience constructor that uses the default cache size.
+ public MediaCache(final int id, final String name) {
+ this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name);
+ }
+
+ public MediaCache(final int maxSize, final int id, final String name) {
+ super(maxSize);
+ mId = id;
+ mName = name;
+ }
+
+ public void destroy() {
+ evictAll();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Gets a media resource from this cache. Must use this method to get resource instead of get()
+ * to ensure addRef() on the resource.
+ */
+ public synchronized T fetchResourceFromCache(final String key) {
+ final T ret = get(key);
+ if (ret != null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "cache hit in mediaCache @ " + getName() +
+ ", total cache hit = " + hitCount() +
+ ", total cache miss = " + missCount());
+ }
+ ret.addRef();
+ } else if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "cache miss in mediaCache @ " + getName() +
+ ", total cache hit = " + hitCount() +
+ ", total cache miss = " + missCount());
+ }
+ return ret;
+ }
+
+ /**
+ * Add a media resource to this cache. Must use this method to add resource instead of put()
+ * to ensure addRef() on the resource.
+ */
+ public synchronized T addResourceToCache(final String key, final T mediaResource) {
+ mediaResource.addRef();
+ return put(key, mediaResource);
+ }
+
+ /**
+ * Notify the removed entry that is no longer being cached
+ */
+ @Override
+ protected synchronized void entryRemoved(final boolean evicted, final String key,
+ final T oldValue, final T newValue) {
+ oldValue.release();
+ }
+
+ /**
+ * Measure item size in kilobytes rather than units which is more practical
+ * for a media resource cache
+ */
+ @Override
+ protected int sizeOf(final String key, final T value) {
+ final int mediaSizeInKilobytes = value.getMediaSize() / 1024;
+ // Never zero-count any resource, count as at least 1KB.
+ return mediaSizeInKilobytes == 0 ? 1 : mediaSizeInKilobytes;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/MediaCacheManager.java b/src/com/android/messaging/datamodel/media/MediaCacheManager.java
new file mode 100644
index 0000000..6e029f2
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MediaCacheManager.java
@@ -0,0 +1,69 @@
+/*
+ * 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.datamodel.media;
+
+import android.util.SparseArray;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache;
+import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
+
+/**
+ * Manages a set of media caches by id.
+ */
+public abstract class MediaCacheManager implements MemoryCache {
+ public static MediaCacheManager get() {
+ return Factory.get().getMediaCacheManager();
+ }
+
+ protected final SparseArray<MediaCache<?>> mCaches;
+
+ public MediaCacheManager() {
+ mCaches = new SparseArray<MediaCache<?>>();
+ MemoryCacheManager.get().registerMemoryCache(this);
+ }
+
+ @Override
+ public void reclaim() {
+ final int count = mCaches.size();
+ for (int i = 0; i < count; i++) {
+ mCaches.valueAt(i).destroy();
+ }
+ mCaches.clear();
+ }
+
+ public synchronized MediaCache<?> getOrCreateMediaCacheById(final int id) {
+ MediaCache<?> cache = mCaches.get(id);
+ if (cache == null) {
+ cache = createMediaCacheById(id);
+ if (cache != null) {
+ mCaches.put(id, cache);
+ }
+ }
+ return cache;
+ }
+
+ public ReusableImageResourcePool getOrCreateBitmapPoolForCache(final int cacheId) {
+ final MediaCache<?> cache = getOrCreateMediaCacheById(cacheId);
+ if (cache != null && cache instanceof PoolableImageCache) {
+ return ((PoolableImageCache) cache).asReusableBitmapPool();
+ }
+ return null;
+ }
+
+ protected abstract MediaCache<?> createMediaCacheById(final int id);
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/MediaRequest.java b/src/com/android/messaging/datamodel/media/MediaRequest.java
new file mode 100644
index 0000000..703671b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MediaRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.datamodel.media;
+
+import java.util.List;
+
+/**
+ * Keeps track of a media loading request. MediaResourceManager uses this interface to load, encode,
+ * decode, and cache different types of media resource.
+ *
+ * This interface defines a media request class that's threading-model-blind. Wrapper classes
+ * (such as {@link AsyncMediaRequestWrapper} wraps around any base media request to offer async
+ * extensions).
+ */
+public interface MediaRequest<T extends RefCountedMediaResource> {
+ public static final int REQUEST_ENCODE_MEDIA = 1;
+ public static final int REQUEST_DECODE_MEDIA = 2;
+ public static final int REQUEST_LOAD_MEDIA = 3;
+
+ /**
+ * Returns a unique key used for storing and looking up the MediaRequest.
+ */
+ String getKey();
+
+ /**
+ * This method performs the heavy-lifting work of synchronously loading the media bytes for
+ * this MediaRequest on a single threaded executor.
+ * @param chainedTask subsequent tasks to be performed after this request is complete. For
+ * example, an image request may need to compress the image resource before putting it in the
+ * cache
+ */
+ T loadMediaBlocking(List<MediaRequest<T>> chainedTask) throws Exception;
+
+ /**
+ * Returns the media cache where this MediaRequest wants to store the loaded
+ * media resource.
+ */
+ MediaCache<T> getMediaCache();
+
+ /**
+ * Returns the id of the cache where this MediaRequest wants to store the loaded
+ * media resource.
+ */
+ int getCacheId();
+
+ /**
+ * Returns the request type of this media request, i.e. one of {@link #REQUEST_ENCODE_MEDIA},
+ * {@link #REQUEST_DECODE_MEDIA}, or {@link #REQUEST_LOAD_MEDIA}. The default is
+ * {@link #REQUEST_LOAD_MEDIA}
+ */
+ int getRequestType();
+
+ /**
+ * Returns the descriptor defining the request.
+ */
+ MediaRequestDescriptor<T> getDescriptor();
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java
new file mode 100644
index 0000000..216b2a3
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MediaRequestDescriptor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+
+/**
+ * The base data holder/builder class for constructing async/sync MediaRequest objects during
+ * runtime.
+ */
+public abstract class MediaRequestDescriptor<T extends RefCountedMediaResource> {
+ public abstract MediaRequest<T> buildSyncMediaRequest(Context context);
+
+ /**
+ * Builds an async media request to be used with
+ * {@link MediaResourceManager#requestMediaResourceAsync(MediaRequest)}
+ */
+ public BindableMediaRequest<T> buildAsyncMediaRequest(final Context context,
+ final MediaResourceLoadListener<T> listener) {
+ final MediaRequest<T> syncRequest = buildSyncMediaRequest(context);
+ return AsyncMediaRequestWrapper.createWith(syncRequest, listener);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/MediaResourceManager.java b/src/com/android/messaging/datamodel/media/MediaResourceManager.java
new file mode 100644
index 0000000..13f7291
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MediaResourceManager.java
@@ -0,0 +1,325 @@
+/*
+ * 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.datamodel.media;
+
+import android.os.AsyncTask;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnAnyThread;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * <p>Loads and maintains a set of in-memory LRU caches for different types of media resources.
+ * Right now we don't utilize any disk cache as all media urls are expected to be resolved to
+ * local content.<p/>
+ *
+ * <p>The MediaResourceManager takes media loading requests through one of two ways:</p>
+ *
+ * <ol>
+ * <li>{@link #requestMediaResourceAsync(MediaRequest)} that takes a MediaRequest, which may be a
+ * regular request if the caller doesn't want to listen for events (fire-and-forget),
+ * or an async request wrapper if event callback is needed.</li>
+ * <li>{@link #requestMediaResourceSync(MediaRequest)} which takes a MediaRequest and synchronously
+ * returns the loaded result, or null if failed.</li>
+ * </ol>
+ *
+ * <p>For each media loading task, MediaResourceManager starts an AsyncTask that runs on a
+ * dedicated thread, which calls MediaRequest.loadMediaBlocking() to perform the actual media
+ * loading work. As the media resources are loaded, MediaResourceManager notifies the callers
+ * (which must implement the MediaResourceLoadListener interface) via onMediaResourceLoaded()
+ * callback. Meanwhile, MediaResourceManager also pushes the loaded resource onto its dedicated
+ * cache.</p>
+ *
+ * <p>The media resource caches ({@link MediaCache}) are maintained as a set of LRU caches. They are
+ * created on demand by the incoming MediaRequest's getCacheId() method. The implementations of
+ * MediaRequest (such as {@link ImageRequest}) get to determine the desired cache id. For Bugle,
+ * the list of available caches are in {@link BugleMediaCacheManager}</p>
+ *
+ * <p>Optionally, media loading can support on-demand media encoding and decoding.
+ * All {@link MediaRequest}'s can opt to chain additional {@link MediaRequest}'s to be executed
+ * after the completion of the main media loading task, by adding new tasks to the chained
+ * task list in {@link MediaRequest#loadMediaBlocking(List)}. One possible type of chained task is
+ * media encoding task. Loaded media will be encoded on a dedicated single threaded executor
+ * *after* the UI is notified of the loaded media. In this case, the encoded media resource will
+ * be eventually pushed to the cache, which will later be decoded before posting to the UI thread
+ * on cache hit.</p>
+ *
+ * <p><b>To add support for a new type of media resource,</b></p>
+ *
+ * <ol>
+ * <li>Create a new subclass of {@link RefCountedMediaResource} for the new resource type (example:
+ * {@link ImageResource} class).</li>
+ *
+ * <li>Implement the {@link MediaRequest} interface (example: {@link ImageRequest}). Perform the
+ * media loading work in loadMediaBlocking() and return a cache id in getCacheId().</li>
+ *
+ * <li>For the UI component that requests the media resource, let it implement
+ * {@link MediaResourceLoadListener} interface to listen for resource load callback. Let the
+ * UI component call MediaResourceManager.requestMediaResourceAsync() to request a media source.
+ * (example: {@link com.android.messaging.ui.ContactIconView}</li>
+ * </ol>
+ */
+public class MediaResourceManager {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ public static MediaResourceManager get() {
+ return Factory.get().getMediaResourceManager();
+ }
+
+ /**
+ * Listener for asynchronous callback from media loading events.
+ */
+ public interface MediaResourceLoadListener<T extends RefCountedMediaResource> {
+ void onMediaResourceLoaded(MediaRequest<T> request, T resource, boolean cached);
+ void onMediaResourceLoadError(MediaRequest<T> request, Exception exception);
+ }
+
+ // We use a fixed thread pool for handling media loading tasks. Using a cached thread pool
+ // allows for unlimited thread creation which can lead to OOMs so we limit the threads here.
+ private static final Executor MEDIA_LOADING_EXECUTOR = Executors.newFixedThreadPool(10);
+
+ // A dedicated single thread executor for performing background task after loading the resource
+ // on the media loading executor. This includes work such as encoding loaded media to be cached.
+ // These tasks are run on a single worker thread with low priority so as not to contend with the
+ // media loading tasks.
+ private static final Executor MEDIA_BACKGROUND_EXECUTOR = Executors.newSingleThreadExecutor(
+ new ThreadFactory() {
+ @Override
+ public Thread newThread(final Runnable runnable) {
+ final Thread encodingThread = new Thread(runnable);
+ encodingThread.setPriority(Thread.MIN_PRIORITY);
+ return encodingThread;
+ }
+ });
+
+ /**
+ * Requests a media resource asynchronously. Upon completion of the media loading task,
+ * the listener will be notified of success/failure iff it's still bound. A refcount on the
+ * resource is held and guaranteed for the caller for the duration of the
+ * {@link MediaResourceLoadListener#onMediaResourceLoaded(
+ * MediaRequest, RefCountedMediaResource, boolean)} callback.
+ * @param mediaRequest the media request. May be either an
+ * {@link AsyncMediaRequestWrapper} for listening for event callbacks, or a regular media
+ * request for fire-and-forget type of behavior.
+ */
+ public <T extends RefCountedMediaResource> void requestMediaResourceAsync(
+ final MediaRequest<T> mediaRequest) {
+ scheduleAsyncMediaRequest(mediaRequest, MEDIA_LOADING_EXECUTOR);
+ }
+
+ /**
+ * Requests a media resource synchronously.
+ * @return the loaded resource with a refcount reserved for the caller. The caller must call
+ * release() on the resource once it's done using it (like with Cursors).
+ */
+ public <T extends RefCountedMediaResource> T requestMediaResourceSync(
+ final MediaRequest<T> mediaRequest) {
+ Assert.isNotMainThread();
+ // Block and load media.
+ MediaLoadingResult<T> loadResult = null;
+ try {
+ loadResult = processMediaRequestInternal(mediaRequest);
+ // The loaded resource should have at least one refcount by now reserved for the caller.
+ Assert.isTrue(loadResult.loadedResource.getRefCount() > 0);
+ return loadResult.loadedResource;
+ } catch (final Exception e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Synchronous media loading failed, key=" +
+ mediaRequest.getKey(), e);
+ return null;
+ } finally {
+ if (loadResult != null) {
+ // Schedule the background requests chained to the main request.
+ loadResult.scheduleChainedRequests();
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends RefCountedMediaResource> MediaLoadingResult<T> processMediaRequestInternal(
+ final MediaRequest<T> mediaRequest)
+ throws Exception {
+ final List<MediaRequest<T>> chainedRequests = new ArrayList<>();
+ T loadedResource = null;
+ // Try fetching from cache first.
+ final T cachedResource = loadMediaFromCache(mediaRequest);
+ if (cachedResource != null) {
+ if (cachedResource.isEncoded()) {
+ // The resource is encoded, issue a decoding request.
+ final MediaRequest<T> decodeRequest = (MediaRequest<T>) cachedResource
+ .getMediaDecodingRequest(mediaRequest);
+ Assert.notNull(decodeRequest);
+ cachedResource.release();
+ loadedResource = loadMediaFromRequest(decodeRequest, chainedRequests);
+ } else {
+ // The resource is ready-to-use.
+ loadedResource = cachedResource;
+ }
+ } else {
+ // Actually load the media after cache miss.
+ loadedResource = loadMediaFromRequest(mediaRequest, chainedRequests);
+ }
+ return new MediaLoadingResult<>(loadedResource, cachedResource != null /* fromCache */,
+ chainedRequests);
+ }
+
+ private <T extends RefCountedMediaResource> T loadMediaFromCache(
+ final MediaRequest<T> mediaRequest) {
+ if (mediaRequest.getRequestType() != MediaRequest.REQUEST_LOAD_MEDIA) {
+ // Only look up in the cache if we are loading media.
+ return null;
+ }
+ final MediaCache<T> mediaCache = mediaRequest.getMediaCache();
+ if (mediaCache != null) {
+ final T mediaResource = mediaCache.fetchResourceFromCache(mediaRequest.getKey());
+ if (mediaResource != null) {
+ return mediaResource;
+ }
+ }
+ return null;
+ }
+
+ private <T extends RefCountedMediaResource> T loadMediaFromRequest(
+ final MediaRequest<T> mediaRequest, final List<MediaRequest<T>> chainedRequests)
+ throws Exception {
+ final T resource = mediaRequest.loadMediaBlocking(chainedRequests);
+ // mediaRequest.loadMediaBlocking() should never return null without
+ // throwing an exception.
+ Assert.notNull(resource);
+ // It's possible for the media to be evicted right after it's added to
+ // the cache (possibly because it's by itself too big for the cache).
+ // It's also possible that, after added to the cache, something else comes
+ // to the cache and evicts this media resource. To prevent this from
+ // recycling the underlying resource objects, make sure to add ref before
+ // adding to cache so that the caller is guaranteed a ref on the resource.
+ resource.addRef();
+ // Don't cache the media request if it is defined as non-cacheable.
+ if (resource.isCacheable()) {
+ addResourceToMemoryCache(mediaRequest, resource);
+ }
+ return resource;
+ }
+
+ /**
+ * Schedule an async media request on the given <code>executor</code>.
+ * @param mediaRequest the media request to be processed asynchronously. May be either an
+ * {@link AsyncMediaRequestWrapper} for listening for event callbacks, or a regular media
+ * request for fire-and-forget type of behavior.
+ */
+ private <T extends RefCountedMediaResource> void scheduleAsyncMediaRequest(
+ final MediaRequest<T> mediaRequest, final Executor executor) {
+ final BindableMediaRequest<T> bindableRequest =
+ (mediaRequest instanceof BindableMediaRequest<?>) ?
+ (BindableMediaRequest<T>) mediaRequest : null;
+ if (bindableRequest != null && !bindableRequest.isBound()) {
+ return; // Request is obsolete
+ }
+ // We don't use SafeAsyncTask here since it enforces the shared thread pool executor
+ // whereas we want a dedicated thread pool executor.
+ AsyncTask<Void, Void, MediaLoadingResult<T>> mediaLoadingTask =
+ new AsyncTask<Void, Void, MediaLoadingResult<T>>() {
+ private Exception mException;
+
+ @Override
+ protected MediaLoadingResult<T> doInBackground(Void... params) {
+ // Double check the request is still valid by the time we start processing it
+ if (bindableRequest != null && !bindableRequest.isBound()) {
+ return null; // Request is obsolete
+ }
+ try {
+ return processMediaRequestInternal(mediaRequest);
+ } catch (Exception e) {
+ mException = e;
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final MediaLoadingResult<T> result) {
+ if (result != null) {
+ Assert.isNull(mException);
+ Assert.isTrue(result.loadedResource.getRefCount() > 0);
+ try {
+ if (bindableRequest != null) {
+ bindableRequest.onMediaResourceLoaded(
+ bindableRequest, result.loadedResource, result.fromCache);
+ }
+ } finally {
+ result.loadedResource.release();
+ result.scheduleChainedRequests();
+ }
+ } else if (mException != null) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Asynchronous media loading failed, key=" +
+ mediaRequest.getKey(), mException);
+ if (bindableRequest != null) {
+ bindableRequest.onMediaResourceLoadError(bindableRequest, mException);
+ }
+ } else {
+ Assert.isTrue(bindableRequest == null || !bindableRequest.isBound());
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "media request not processed, no longer bound; key=" +
+ LogUtil.sanitizePII(mediaRequest.getKey()) /* key with phone# */);
+ }
+ }
+ }
+ };
+ mediaLoadingTask.executeOnExecutor(executor, (Void) null);
+ }
+
+ @VisibleForTesting
+ @RunsOnAnyThread
+ <T extends RefCountedMediaResource> void addResourceToMemoryCache(
+ final MediaRequest<T> mediaRequest, final T mediaResource) {
+ Assert.isTrue(mediaResource != null);
+ final MediaCache<T> mediaCache = mediaRequest.getMediaCache();
+ if (mediaCache != null) {
+ mediaCache.addResourceToCache(mediaRequest.getKey(), mediaResource);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "added media resource to " + mediaCache.getName() + ". key=" +
+ LogUtil.sanitizePII(mediaRequest.getKey()) /* key can contain phone# */);
+ }
+ }
+ }
+
+ private class MediaLoadingResult<T extends RefCountedMediaResource> {
+ public final T loadedResource;
+ public final boolean fromCache;
+ private final List<MediaRequest<T>> mChainedRequests;
+
+ MediaLoadingResult(final T loadedResource, final boolean fromCache,
+ final List<MediaRequest<T>> chainedRequests) {
+ this.loadedResource = loadedResource;
+ this.fromCache = fromCache;
+ mChainedRequests = chainedRequests;
+ }
+
+ /**
+ * Asynchronously schedule a list of chained requests on the background thread.
+ */
+ public void scheduleChainedRequests() {
+ for (final MediaRequest<T> mediaRequest : mChainedRequests) {
+ scheduleAsyncMediaRequest(mediaRequest, MEDIA_BACKGROUND_EXECUTOR);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java
new file mode 100644
index 0000000..1871e66
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MessagePartImageRequestDescriptor.java
@@ -0,0 +1,64 @@
+/*
+ * 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.datamodel.media;
+
+import android.net.Uri;
+
+import com.android.messaging.datamodel.action.UpdateMessagePartSizeAction;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.util.ImageUtils;
+
+/**
+ * Image descriptor attached to a message part.
+ * Once image size is determined during loading this descriptor will update the db if necessary.
+ */
+public class MessagePartImageRequestDescriptor extends UriImageRequestDescriptor {
+ private final String mMessagePartId;
+
+ /**
+ * Creates a new image request for a message part.
+ */
+ public MessagePartImageRequestDescriptor(final MessagePartData messagePart,
+ final int desiredWidth, final int desiredHeight, boolean isStatic) {
+ // Pull image parameters out of the MessagePart record
+ this(messagePart.getPartId(), messagePart.getContentUri(), desiredWidth, desiredHeight,
+ messagePart.getWidth(), messagePart.getHeight(), isStatic);
+ }
+
+ protected MessagePartImageRequestDescriptor(final String messagePartId, final Uri contentUri,
+ final int desiredWidth, final int desiredHeight, final int sourceWidth,
+ final int sourceHeight, boolean isStatic) {
+ super(contentUri, desiredWidth, desiredHeight, sourceWidth, sourceHeight,
+ true /* allowCompression */, isStatic, false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ mMessagePartId = messagePartId;
+ }
+
+ @Override
+ public void updateSourceDimensions(final int updatedWidth, final int updatedHeight) {
+ // If the dimensions of the image do not match then queue a DB update with new size.
+ // Don't update if we don't have a part id, which happens if this part is loaded as
+ // draft through actions such as share intent/message forwarding.
+ if (mMessagePartId != null &&
+ updatedWidth != MessagePartData.UNSPECIFIED_SIZE &&
+ updatedHeight != MessagePartData.UNSPECIFIED_SIZE &&
+ updatedWidth != sourceWidth && updatedHeight != sourceHeight) {
+ UpdateMessagePartSizeAction.updateSize(mMessagePartId, updatedWidth, updatedHeight);
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java
new file mode 100644
index 0000000..ff11e92
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/MessagePartVideoThumbnailRequestDescriptor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.messaging.datamodel.data.MessagePartData;
+
+public class MessagePartVideoThumbnailRequestDescriptor extends MessagePartImageRequestDescriptor {
+ public MessagePartVideoThumbnailRequestDescriptor(MessagePartData messagePart) {
+ super(messagePart, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false);
+ }
+
+ public MessagePartVideoThumbnailRequestDescriptor(Uri uri) {
+ super(null, uri, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE,
+ ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE, false);
+ }
+
+ @Override
+ public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
+ return new VideoThumbnailRequest(context, this);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java b/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java
new file mode 100644
index 0000000..642e947
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/NetworkUriImageRequest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Serves network content URI based image requests.
+ */
+public class NetworkUriImageRequest<D extends UriImageRequestDescriptor> extends
+ ImageRequest<D> {
+
+ public NetworkUriImageRequest(Context context, D descriptor) {
+ super(context, descriptor);
+ mOrientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
+ }
+
+ @Override
+ protected InputStream getInputStreamForResource() throws FileNotFoundException {
+ Assert.isNotMainThread();
+ // Since we need to have an open urlConnection to get the stream, but we don't want to keep
+ // that connection open. There is no good way to perform this method.
+ return null;
+ }
+
+ @Override
+ protected boolean isGif() throws FileNotFoundException {
+ Assert.isNotMainThread();
+
+ HttpURLConnection connection = null;
+ try {
+ final URL url = new URL(mDescriptor.uri.toString());
+ connection = (HttpURLConnection) url.openConnection();
+ connection.connect();
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ return ContentType.IMAGE_GIF.equalsIgnoreCase(connection.getContentType());
+ }
+ } catch (MalformedURLException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "MalformedUrl for image with url: "
+ + mDescriptor.uri.toString(), e);
+ } catch (IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "IOException trying to get inputStream for image with url: "
+ + mDescriptor.uri.toString(), e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Bitmap loadBitmapInternal() throws IOException {
+ Assert.isNotMainThread();
+
+ InputStream inputStream = null;
+ Bitmap bitmap = null;
+ HttpURLConnection connection = null;
+ try {
+ final URL url = new URL(mDescriptor.uri.toString());
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setDoInput(true);
+ connection.connect();
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
+ bitmap = BitmapFactory.decodeStream(connection.getInputStream());
+ }
+ } catch (MalformedURLException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "MalformedUrl for image with url: "
+ + mDescriptor.uri.toString(), e);
+ } catch (final OutOfMemoryError e) {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "OutOfMemoryError for image with url: "
+ + mDescriptor.uri.toString(), e);
+ Factory.get().reclaimMemory();
+ } catch (IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "IOException trying to get inputStream for image with url: "
+ + mDescriptor.uri.toString(), e);
+ } finally {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ return bitmap;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/PoolableImageCache.java b/src/com/android/messaging/datamodel/media/PoolableImageCache.java
new file mode 100644
index 0000000..df814ba
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/PoolableImageCache.java
@@ -0,0 +1,419 @@
+/*
+ * 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.datamodel.media;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.util.SparseArray;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+
+/**
+ * A media cache that holds image resources, which doubles as a bitmap pool that allows the
+ * consumer to optionally decode image resources using unused bitmaps stored in the cache.
+ */
+public class PoolableImageCache extends MediaCache<ImageResource> {
+ private static final int MIN_TIME_IN_POOL = 5000;
+
+ /** Encapsulates bitmap pool representation of the image cache */
+ private final ReusableImageResourcePool mReusablePoolAccessor = new ReusableImageResourcePool();
+
+ public PoolableImageCache(final int id, final String name) {
+ this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name);
+ }
+
+ public PoolableImageCache(final int maxSize, final int id, final String name) {
+ super(maxSize, id, name);
+ }
+
+ /**
+ * Creates a new BitmapFactory.Options for using the self-contained bitmap pool.
+ */
+ public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled,
+ final int inputDensity, final int targetDensity) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inScaled = scaled;
+ options.inDensity = inputDensity;
+ options.inTargetDensity = targetDensity;
+ options.inSampleSize = 1;
+ options.inJustDecodeBounds = false;
+ options.inMutable = true;
+ return options;
+ }
+
+ @Override
+ public synchronized ImageResource addResourceToCache(final String key,
+ final ImageResource imageResource) {
+ mReusablePoolAccessor.onResourceEnterCache(imageResource);
+ return super.addResourceToCache(key, imageResource);
+ }
+
+ @Override
+ protected synchronized void entryRemoved(final boolean evicted, final String key,
+ final ImageResource oldValue, final ImageResource newValue) {
+ mReusablePoolAccessor.onResourceLeaveCache(oldValue);
+ super.entryRemoved(evicted, key, oldValue, newValue);
+ }
+
+ /**
+ * Returns a representation of the image cache as a reusable bitmap pool.
+ */
+ public ReusableImageResourcePool asReusableBitmapPool() {
+ return mReusablePoolAccessor;
+ }
+
+ /**
+ * A bitmap pool representation built on top of the image cache. It treats the image resources
+ * stored in the image cache as a self-contained bitmap pool and is able to create or
+ * reclaim bitmap resource as needed.
+ */
+ public class ReusableImageResourcePool {
+ private static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF;
+ private static final int INVALID_POOL_KEY = 0;
+
+ /**
+ * Number of reuse failures to skip before reporting.
+ * For debugging purposes, change to a lower number for more frequent reporting.
+ */
+ private static final int FAILED_REPORTING_FREQUENCY = 100;
+
+ /**
+ * Count of reuse failures which have occurred.
+ */
+ private volatile int mFailedBitmapReuseCount = 0;
+
+ /**
+ * Count of reuse successes which have occurred.
+ */
+ private volatile int mSucceededBitmapReuseCount = 0;
+
+ /**
+ * A sparse array from bitmap size to a list of image cache entries that match the
+ * given size. This map is used to quickly retrieve a usable bitmap to be reused by an
+ * incoming ImageRequest. We need to ensure that this sparse array always contains only
+ * elements currently in the image cache with no other consumer.
+ */
+ private final SparseArray<LinkedList<ImageResource>> mImageListSparseArray;
+
+ public ReusableImageResourcePool() {
+ mImageListSparseArray = new SparseArray<LinkedList<ImageResource>>();
+ }
+
+ /**
+ * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce
+ * memory turnover.
+ * @param inputStream InputStream load. Cannot be null.
+ * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool().
+ * Cannot be null.
+ * @param width The width of the bitmap.
+ * @param height The height of the bitmap.
+ * @return The decoded Bitmap with the resource drawn in it.
+ * @throws IOException
+ */
+ public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream,
+ @NonNull final BitmapFactory.Options optionsTmp,
+ final int width, final int height) throws IOException {
+ if (width <= 0 || height <= 0) {
+ // This is an invalid / corrupted image of zero size.
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " +
+ "invalid size");
+ throw new IOException("Invalid size / corrupted image");
+ }
+ Assert.notNull(inputStream);
+ assignPoolBitmap(optionsTmp, width, height);
+ Bitmap b = null;
+ try {
+ b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
+ mSucceededBitmapReuseCount++;
+ } catch (final IllegalArgumentException e) {
+ // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
+ if (optionsTmp.inBitmap != null) {
+ optionsTmp.inBitmap.recycle();
+ optionsTmp.inBitmap = null;
+ b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
+ onFailedToReuse();
+ }
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream");
+ Factory.get().reclaimMemory();
+ }
+ return b;
+ }
+
+ /**
+ * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce
+ * memory turnover.
+ * @param bytes Encoded bytes to draw on the bitmap. Cannot be null.
+ * @param optionsTmp The bitmap will set here and the input should be generated from
+ * getBitmapOptionsForPool(). Cannot be null.
+ * @param width The width of the bitmap.
+ * @param height The height of the bitmap.
+ * @return A Bitmap with the encoded bytes drawn in it.
+ * @throws IOException
+ */
+ public Bitmap decodeByteArray(@NonNull final byte[] bytes,
+ @NonNull final BitmapFactory.Options optionsTmp, final int width,
+ final int height) throws OutOfMemoryError, IOException {
+ if (width <= 0 || height <= 0) {
+ // This is an invalid / corrupted image of zero size.
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " +
+ "invalid size");
+ throw new IOException("Invalid size / corrupted image");
+ }
+ Assert.notNull(bytes);
+ Assert.notNull(optionsTmp);
+ assignPoolBitmap(optionsTmp, width, height);
+ Bitmap b = null;
+ try {
+ b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
+ mSucceededBitmapReuseCount++;
+ } catch (final IllegalArgumentException e) {
+ // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
+ // (i.e. without the bitmap from the pool)
+ if (optionsTmp.inBitmap != null) {
+ optionsTmp.inBitmap.recycle();
+ optionsTmp.inBitmap = null;
+ b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
+ onFailedToReuse();
+ }
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream");
+ Factory.get().reclaimMemory();
+ }
+ return b;
+ }
+
+ /**
+ * Called when a new image resource is added to the cache. We add the resource to the
+ * pool so it's properly keyed into the pool structure.
+ */
+ void onResourceEnterCache(final ImageResource imageResource) {
+ if (getPoolKey(imageResource) != INVALID_POOL_KEY) {
+ addResourceToPool(imageResource);
+ }
+ }
+
+ /**
+ * Called when an image resource is evicted from the cache. Bitmap pool's entries are
+ * strictly tied to their presence in the image cache. Once an image is evicted from the
+ * cache, it should be removed from the pool.
+ */
+ void onResourceLeaveCache(final ImageResource imageResource) {
+ if (getPoolKey(imageResource) != INVALID_POOL_KEY) {
+ removeResourceFromPool(imageResource);
+ }
+ }
+
+ private void addResourceToPool(final ImageResource imageResource) {
+ synchronized (PoolableImageCache.this) {
+ final int poolKey = getPoolKey(imageResource);
+ Assert.isTrue(poolKey != INVALID_POOL_KEY);
+ LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey);
+ if (imageList == null) {
+ imageList = new LinkedList<ImageResource>();
+ mImageListSparseArray.put(poolKey, imageList);
+ }
+ imageList.addLast(imageResource);
+ }
+ }
+
+ private void removeResourceFromPool(final ImageResource imageResource) {
+ synchronized (PoolableImageCache.this) {
+ final int poolKey = getPoolKey(imageResource);
+ Assert.isTrue(poolKey != INVALID_POOL_KEY);
+ final LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey);
+ if (imageList != null) {
+ imageList.remove(imageResource);
+ }
+ }
+ }
+
+ /**
+ * Try to get a reusable bitmap from the pool with the given width and height. As a
+ * result of this call, the caller will assume ownership of the returned bitmap.
+ */
+ private Bitmap getReusableBitmapFromPool(final int width, final int height) {
+ synchronized (PoolableImageCache.this) {
+ final int poolKey = getPoolKey(width, height);
+ if (poolKey != INVALID_POOL_KEY) {
+ final LinkedList<ImageResource> images = mImageListSparseArray.get(poolKey);
+ if (images != null && images.size() > 0) {
+ // Try to reuse the first available bitmap from the pool list. We start from
+ // the least recently added cache entry of the given size.
+ ImageResource imageToUse = null;
+ for (int i = 0; i < images.size(); i++) {
+ final ImageResource image = images.get(i);
+ if (image.getRefCount() == 1) {
+ image.acquireLock();
+ if (image.getRefCount() == 1) {
+ // The image is only used by the cache, so it's reusable.
+ imageToUse = images.remove(i);
+ break;
+ } else {
+ // Logically, this shouldn't happen, because as soon as the
+ // cache is the only user of this resource, it will not be
+ // used by anyone else until the next cache access, but we
+ // currently hold on to the cache lock. But technically
+ // future changes may violate this assumption, so warn about
+ // this.
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Image refCount changed " +
+ "from 1 in getReusableBitmapFromPool()");
+ image.releaseLock();
+ }
+ }
+ }
+
+ if (imageToUse == null) {
+ return null;
+ }
+
+ try {
+ imageToUse.assertLockHeldByCurrentThread();
+
+ // Only reuse the bitmap if the last time we use was greater than 5s.
+ // This allows the cache a chance to reuse instead of always taking the
+ // oldest.
+ final long timeSinceLastRef = SystemClock.elapsedRealtime() -
+ imageToUse.getLastRefAddTimestamp();
+ if (timeSinceLastRef < MIN_TIME_IN_POOL) {
+ if (LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "Not reusing reusing " +
+ "first available bitmap from the pool because it " +
+ "has not been in the pool long enough. " +
+ "timeSinceLastRef=" + timeSinceLastRef);
+ }
+ // Put back the image and return no reuseable bitmap.
+ images.addLast(imageToUse);
+ return null;
+ }
+
+ // Add a temp ref on the image resource so it won't be GC'd after
+ // being removed from the cache.
+ imageToUse.addRef();
+
+ // Remove the image resource from the image cache.
+ final ImageResource removed = remove(imageToUse.getKey());
+ Assert.isTrue(removed == imageToUse);
+
+ // Try to reuse the bitmap from the image resource. This will transfer
+ // ownership of the bitmap object to the caller of this method.
+ final Bitmap reusableBitmap = imageToUse.reuseBitmap();
+
+ imageToUse.release();
+ return reusableBitmap;
+ } finally {
+ // We are either done with the reuse operation, or decided not to use
+ // the image. Either way, release the lock.
+ imageToUse.releaseLock();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Try to locate and return a reusable bitmap from the pool, or create a new bitmap.
+ * @param width desired bitmap width
+ * @param height desired bitmap height
+ * @return the created or reused mutable bitmap that has its background cleared to
+ * {@value Color#TRANSPARENT}
+ */
+ public Bitmap createOrReuseBitmap(final int width, final int height) {
+ return createOrReuseBitmap(width, height, Color.TRANSPARENT);
+ }
+
+ /**
+ * Try to locate and return a reusable bitmap from the pool, or create a new bitmap.
+ * @param width desired bitmap width
+ * @param height desired bitmap height
+ * @param backgroundColor the background color for the returned bitmap
+ * @return the created or reused mutable bitmap with the requested background color
+ */
+ public Bitmap createOrReuseBitmap(final int width, final int height,
+ final int backgroundColor) {
+ Bitmap retBitmap = null;
+ try {
+ final Bitmap poolBitmap = getReusableBitmapFromPool(width, height);
+ retBitmap = (poolBitmap != null) ? poolBitmap :
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ retBitmap.eraseColor(backgroundColor);
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache:try to createOrReuseBitmap");
+ Factory.get().reclaimMemory();
+ }
+ return retBitmap;
+ }
+
+ private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width,
+ final int height) {
+ if (optionsTmp.inJustDecodeBounds) {
+ return;
+ }
+ optionsTmp.inBitmap = getReusableBitmapFromPool(width, height);
+ }
+
+ /**
+ * @return The pool key for the provided image dimensions or 0 if either width or height is
+ * greater than the max supported image dimension.
+ */
+ private int getPoolKey(final int width, final int height) {
+ if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) {
+ return INVALID_POOL_KEY;
+ }
+ return (width << 16) | height;
+ }
+
+ /**
+ * @return the pool key for a given image resource.
+ */
+ private int getPoolKey(final ImageResource imageResource) {
+ if (imageResource.supportsBitmapReuse()) {
+ final Bitmap bitmap = imageResource.getBitmap();
+ if (bitmap != null && bitmap.isMutable()) {
+ final int width = bitmap.getWidth();
+ final int height = bitmap.getHeight();
+ if (width > 0 && height > 0) {
+ return getPoolKey(width, height);
+ }
+ }
+ }
+ return INVALID_POOL_KEY;
+ }
+
+ /**
+ * Called when bitmap reuse fails. Conditionally report the failure with statistics.
+ */
+ private void onFailedToReuse() {
+ mFailedBitmapReuseCount++;
+ if (mFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG,
+ "Pooled bitmap consistently not being reused. Failure count = " +
+ mFailedBitmapReuseCount + ", success count = " +
+ mSucceededBitmapReuseCount);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java b/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java
new file mode 100644
index 0000000..c21f477
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/RefCountedMediaResource.java
@@ -0,0 +1,164 @@
+/*
+ * 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.datamodel.media;
+
+import android.os.SystemClock;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.google.common.base.Throwables;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A ref-counted class that holds loaded media resource, be it bitmaps or media bytes.
+ * Subclasses must implement the close() method to release any resources (such as bitmaps)
+ * when it's no longer used.
+ *
+ * Instances of the subclasses are:
+ * 1. Loaded by their corresponding MediaRequest classes.
+ * 2. Maintained by MediaResourceManager in its MediaCache pool.
+ * 3. Used by the UI (such as ContactIconViews) to present the content.
+ *
+ * Note: all synchronized methods in this class (e.g. addRef()) should not attempt to make outgoing
+ * calls that could potentially acquire media cache locks due to the potential deadlock this can
+ * cause. To synchronize read/write access to shared resource, {@link #acquireLock()} and
+ * {@link #releaseLock()} must be used, instead of using synchronized keyword.
+ */
+public abstract class RefCountedMediaResource {
+ private final String mKey;
+ private int mRef = 0;
+ private long mLastRefAddTimestamp;
+
+ // Set DEBUG to true to enable detailed stack trace for each addRef() and release() operation
+ // to find out where each ref change happens.
+ private static final boolean DEBUG = false;
+ private static final String TAG = "bugle_media_ref_history";
+ private final ArrayList<String> mRefHistory = new ArrayList<String>();
+
+ // A lock that guards access to shared members in this class (and all its subclasses).
+ private final ReentrantLock mLock = new ReentrantLock();
+
+ public RefCountedMediaResource(final String key) {
+ mKey = key;
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public void addRef() {
+ acquireLock();
+ try {
+ if (DEBUG) {
+ mRefHistory.add("Added ref current ref = " + mRef);
+ mRefHistory.add(Throwables.getStackTraceAsString(new Exception()));
+ }
+
+ mRef++;
+ mLastRefAddTimestamp = SystemClock.elapsedRealtime();
+ } finally {
+ releaseLock();
+ }
+ }
+
+ public void release() {
+ acquireLock();
+ try {
+ if (DEBUG) {
+ mRefHistory.add("Released ref current ref = " + mRef);
+ mRefHistory.add(Throwables.getStackTraceAsString(new Exception()));
+ }
+
+ mRef--;
+ if (mRef == 0) {
+ close();
+ } else if (mRef < 0) {
+ if (DEBUG) {
+ LogUtil.i(TAG, "Unwinding ref count history for RefCountedMediaResource "
+ + this);
+ for (final String ref : mRefHistory) {
+ LogUtil.i(TAG, ref);
+ }
+ }
+ Assert.fail("RefCountedMediaResource has unbalanced ref. Refcount=" + mRef);
+ }
+ } finally {
+ releaseLock();
+ }
+ }
+
+ public int getRefCount() {
+ acquireLock();
+ try {
+ return mRef;
+ } finally {
+ releaseLock();
+ }
+ }
+
+ public long getLastRefAddTimestamp() {
+ acquireLock();
+ try {
+ return mLastRefAddTimestamp;
+ } finally {
+ releaseLock();
+ }
+ }
+
+ public void assertSingularRefCount() {
+ acquireLock();
+ try {
+ Assert.equals(1, mRef);
+ } finally {
+ releaseLock();
+ }
+ }
+
+ void acquireLock() {
+ mLock.lock();
+ }
+
+ void releaseLock() {
+ mLock.unlock();
+ }
+
+ void assertLockHeldByCurrentThread() {
+ Assert.isTrue(mLock.isHeldByCurrentThread());
+ }
+
+ boolean isEncoded() {
+ return false;
+ }
+
+ boolean isCacheable() {
+ return true;
+ }
+
+ MediaRequest<? extends RefCountedMediaResource> getMediaDecodingRequest(
+ final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
+ return null;
+ }
+
+ MediaRequest<? extends RefCountedMediaResource> getMediaEncodingRequest(
+ final MediaRequest<? extends RefCountedMediaResource> originalRequest) {
+ return null;
+ }
+
+ public abstract int getMediaSize();
+ protected abstract void close();
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java b/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java
new file mode 100644
index 0000000..e4f0334
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/SimSelectorAvatarRequest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.media.ExifInterface;
+import android.text.TextUtils;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+
+import java.io.IOException;
+import java.util.List;
+
+public class SimSelectorAvatarRequest extends AvatarRequest {
+ private static Bitmap sRegularSimIcon;
+
+ public SimSelectorAvatarRequest(final Context context,
+ final AvatarRequestDescriptor descriptor) {
+ super(context, descriptor);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTasks)
+ throws IOException {
+ Assert.isNotMainThread();
+ final String avatarType = AvatarUriUtil.getAvatarType(mDescriptor.uri);
+ if (AvatarUriUtil.TYPE_SIM_SELECTOR_URI.equals(avatarType)){
+ final int width = mDescriptor.desiredWidth;
+ final int height = mDescriptor.desiredHeight;
+ final String identifier = AvatarUriUtil.getIdentifier(mDescriptor.uri);
+ final boolean simSelected = AvatarUriUtil.getSimSelected(mDescriptor.uri);
+ final int simColor = AvatarUriUtil.getSimColor(mDescriptor.uri);
+ final boolean incoming = AvatarUriUtil.getSimIncoming(mDescriptor.uri);
+ return renderSimAvatarInternal(identifier, width, height, simColor, simSelected,
+ incoming);
+ }
+ return super.loadMediaInternal(chainedTasks);
+ }
+
+ private ImageResource renderSimAvatarInternal(final String identifier, final int width,
+ final int height, final int subColor, final boolean selected, final boolean incoming) {
+ final Resources resources = mContext.getResources();
+ final float halfWidth = width / 2;
+ final float halfHeight = height / 2;
+ final int minOfWidthAndHeight = Math.min(width, height);
+ final int backgroundColor = selected ? subColor : Color.WHITE;
+ final int textColor = selected ? subColor : Color.WHITE;
+ final int simColor = selected ? Color.WHITE : subColor;
+ final Bitmap bitmap = getBitmapPool().createOrReuseBitmap(width, height, backgroundColor);
+ final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ final Canvas canvas = new Canvas(bitmap);
+
+ if (sRegularSimIcon == null) {
+ final BitmapDrawable regularSim = (BitmapDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ic_sim_card_send);
+ sRegularSimIcon = regularSim.getBitmap();
+ }
+
+ paint.setColorFilter(new PorterDuffColorFilter(simColor, PorterDuff.Mode.SRC_ATOP));
+ paint.setAlpha(0xff);
+ canvas.drawBitmap(sRegularSimIcon, halfWidth - sRegularSimIcon.getWidth() / 2,
+ halfHeight - sRegularSimIcon.getHeight() / 2, paint);
+ paint.setColorFilter(null);
+ paint.setAlpha(0xff);
+
+ if (!TextUtils.isEmpty(identifier)) {
+ paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
+ paint.setColor(textColor);
+ final float letterToTileRatio =
+ resources.getFraction(R.dimen.sim_identifier_to_tile_ratio, 1, 1);
+ paint.setTextSize(letterToTileRatio * minOfWidthAndHeight);
+
+ final String firstCharString = identifier.substring(0, 1).toUpperCase();
+ final Rect textBound = new Rect();
+ paint.getTextBounds(firstCharString, 0, 1, textBound);
+
+ final float xOffset = halfWidth - textBound.centerX();
+ final float yOffset = halfHeight - textBound.centerY();
+ canvas.drawText(firstCharString, xOffset, yOffset, paint);
+ }
+
+ return new DecodedImageResource(getKey(), bitmap, ExifInterface.ORIENTATION_NORMAL);
+ }
+
+ @Override
+ public int getCacheId() {
+ return BugleMediaCacheManager.AVATAR_IMAGE_CACHE;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/UriImageRequest.java b/src/com/android/messaging/datamodel/media/UriImageRequest.java
new file mode 100644
index 0000000..b4934ca
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/UriImageRequest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Serves local content URI based image requests.
+ */
+public class UriImageRequest<D extends UriImageRequestDescriptor> extends ImageRequest<D> {
+ public UriImageRequest(final Context context, final D descriptor) {
+ super(context, descriptor);
+ }
+
+ @Override
+ protected InputStream getInputStreamForResource() throws FileNotFoundException {
+ return mContext.getContentResolver().openInputStream(mDescriptor.uri);
+ }
+
+ @Override
+ protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTasks)
+ throws IOException {
+ final ImageResource resource = super.loadMediaInternal(chainedTasks);
+ // Check if the caller asked for compression. If so, chain an encoding task if possible.
+ if (mDescriptor.allowCompression && chainedTasks != null) {
+ @SuppressWarnings("unchecked")
+ final MediaRequest<ImageResource> chainedTask = (MediaRequest<ImageResource>)
+ resource.getMediaEncodingRequest(this);
+ if (chainedTask != null) {
+ chainedTasks.add(chainedTask);
+ // Don't cache decoded image resource since we'll perform compression and cache
+ // the compressed resource.
+ if (resource instanceof DecodedImageResource) {
+ ((DecodedImageResource) resource).setCacheable(false);
+ }
+ }
+ }
+ return resource;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java b/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
new file mode 100644
index 0000000..c5685d1
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/UriImageRequestDescriptor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.messaging.util.UriUtil;
+
+public class UriImageRequestDescriptor extends ImageRequestDescriptor {
+ public final Uri uri;
+ public final boolean allowCompression;
+
+ public UriImageRequestDescriptor(final Uri uri) {
+ this(uri, UriImageRequest.UNSPECIFIED_SIZE, UriImageRequest.UNSPECIFIED_SIZE, false, false,
+ false, 0, 0);
+ }
+
+ public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, final int desiredHeight)
+ {
+ this(uri, desiredWidth, desiredHeight, false, false, false, 0, 0);
+ }
+
+ public UriImageRequestDescriptor(final Uri uri, final int desiredWidth, final int desiredHeight,
+ final boolean cropToCircle, final int circleBackgroundColor, int circleStrokeColor)
+ {
+ this(uri, desiredWidth, desiredHeight, false,
+ false, cropToCircle, circleBackgroundColor, circleStrokeColor);
+ }
+
+ public UriImageRequestDescriptor(final Uri uri, final int desiredWidth,
+ final int desiredHeight, final boolean allowCompression, boolean isStatic,
+ boolean cropToCircle, int circleBackgroundColor, int circleStrokeColor) {
+ this(uri, desiredWidth, desiredHeight, UriImageRequest.UNSPECIFIED_SIZE,
+ UriImageRequest.UNSPECIFIED_SIZE, allowCompression, isStatic, cropToCircle,
+ circleBackgroundColor, circleStrokeColor);
+ }
+
+ /**
+ * Creates a new Uri-based image request.
+ * @param uri the content Uri. Currently Bugle only supports local resources Uri (i.e. it has
+ * to begin with content: or android.resource:
+ * @param circleStrokeColor
+ */
+ public UriImageRequestDescriptor(final Uri uri, final int desiredWidth,
+ final int desiredHeight, final int sourceWidth, final int sourceHeight,
+ final boolean allowCompression, final boolean isStatic, final boolean cropToCircle,
+ final int circleBackgroundColor, int circleStrokeColor) {
+ super(desiredWidth, desiredHeight, sourceWidth, sourceHeight, isStatic,
+ cropToCircle, circleBackgroundColor, circleStrokeColor);
+ this.uri = uri;
+ this.allowCompression = allowCompression;
+ }
+
+ @Override
+ public String getKey() {
+ if (uri != null) {
+ final String key = super.getKey();
+ if (key != null) {
+ return new StringBuilder()
+ .append(uri).append(KEY_PART_DELIMITER)
+ .append(String.valueOf(allowCompression)).append(KEY_PART_DELIMITER)
+ .append(key).toString();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public MediaRequest<ImageResource> buildSyncMediaRequest(final Context context) {
+ if (uri == null || UriUtil.isLocalUri(uri)) {
+ return new UriImageRequest<UriImageRequestDescriptor>(context, this);
+ } else {
+ return new NetworkUriImageRequest<UriImageRequestDescriptor>(context, this);
+ }
+ }
+
+ /** ID of the resource in MediaStore or null if this resource didn't come from MediaStore */
+ public Long getMediaStoreId() {
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/datamodel/media/VCardRequest.java b/src/com/android/messaging/datamodel/media/VCardRequest.java
new file mode 100644
index 0000000..d6e992c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/VCardRequest.java
@@ -0,0 +1,328 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.UriUtil;
+import com.android.vcard.VCardConfig;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardInterpreter;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.VCardSourceDetector;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardNestedException;
+import com.android.vcard.exception.VCardNotSupportedException;
+import com.android.vcard.exception.VCardVersionException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Requests and parses VCard data. In Bugle, we need to display VCard details in the conversation
+ * view such as avatar icon and name, which can be expensive if we parse VCard every time.
+ * Therefore, we'd like to load the vcard once and cache it in our media cache using the
+ * MediaResourceManager component. To load the VCard, we use framework's VCard support to
+ * interpret the VCard content, which gives us information such as phone and email list, which
+ * we'll put in VCardResource object to be cached.
+ *
+ * Some particular attention is needed for the avatar icon. If the VCard contains avatar icon,
+ * it's in byte array form that can't easily be cached/persisted. Therefore, we persist the
+ * image bytes to the scratch directory and generate a content Uri for it, so that ContactIconView
+ * may use this Uri to display and cache the image if needed.
+ */
+public class VCardRequest implements MediaRequest<VCardResource> {
+ private final Context mContext;
+ private final VCardRequestDescriptor mDescriptor;
+ private final List<VCardResourceEntry> mLoadedVCards;
+ private VCardResource mLoadedResource;
+ private static final int VCARD_LOADING_TIMEOUT_MILLIS = 10000; // 10s
+ private static final String DEFAULT_VCARD_TYPE = "default";
+
+ VCardRequest(final Context context, final VCardRequestDescriptor descriptor) {
+ mDescriptor = descriptor;
+ mContext = context;
+ mLoadedVCards = new ArrayList<VCardResourceEntry>();
+ }
+
+ @Override
+ public String getKey() {
+ return mDescriptor.vCardUri.toString();
+ }
+
+ @Override
+ @DoesNotRunOnMainThread
+ public VCardResource loadMediaBlocking(List<MediaRequest<VCardResource>> chainedTask)
+ throws Exception {
+ Assert.isNotMainThread();
+ Assert.isTrue(mLoadedResource == null);
+ Assert.equals(0, mLoadedVCards.size());
+
+ // The VCard library doesn't support synchronously loading the media resource. Therefore,
+ // We have to burn the thread waiting for the result to come back.
+ final CountDownLatch signal = new CountDownLatch(1);
+ if (!parseVCard(mDescriptor.vCardUri, signal)) {
+ // Directly fail without actually going through the interpreter, return immediately.
+ throw new VCardException("Invalid vcard");
+ }
+
+ signal.await(VCARD_LOADING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ if (mLoadedResource == null) {
+ // Maybe null if failed or timeout.
+ throw new VCardException("Failure or timeout loading vcard");
+ }
+ return mLoadedResource;
+ }
+
+ @Override
+ public int getCacheId() {
+ return BugleMediaCacheManager.VCARD_CACHE;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public MediaCache<VCardResource> getMediaCache() {
+ return (MediaCache<VCardResource>) MediaCacheManager.get().getOrCreateMediaCacheById(
+ getCacheId());
+ }
+
+ @DoesNotRunOnMainThread
+ private boolean parseVCard(final Uri targetUri, final CountDownLatch signal) {
+ Assert.isNotMainThread();
+ final VCardEntryCounter counter = new VCardEntryCounter();
+ final VCardSourceDetector detector = new VCardSourceDetector();
+ boolean result;
+ try {
+ // We don't know which type should be used to parse the Uri.
+ // It is possible to misinterpret the vCard, but we expect the parser
+ // lets VCardSourceDetector detect the type before the misinterpretation.
+ result = readOneVCardFile(targetUri, VCardConfig.VCARD_TYPE_UNKNOWN,
+ detector, true, null);
+ } catch (final VCardNestedException e) {
+ try {
+ final int estimatedVCardType = detector.getEstimatedType();
+ // Assume that VCardSourceDetector was able to detect the source.
+ // Try again with the detector.
+ result = readOneVCardFile(targetUri, estimatedVCardType,
+ counter, false, null);
+ } catch (final VCardNestedException e2) {
+ result = false;
+ LogUtil.e(LogUtil.BUGLE_TAG, "Must not reach here. " + e2);
+ }
+ }
+
+ if (!result) {
+ // Load failure.
+ return false;
+ }
+
+ return doActuallyReadOneVCard(targetUri, true, detector, null, signal);
+ }
+
+ @DoesNotRunOnMainThread
+ private boolean doActuallyReadOneVCard(final Uri uri, final boolean showEntryParseProgress,
+ final VCardSourceDetector detector, final List<String> errorFileNameList,
+ final CountDownLatch signal) {
+ Assert.isNotMainThread();
+ int vcardType = detector.getEstimatedType();
+ if (vcardType == VCardConfig.VCARD_TYPE_UNKNOWN) {
+ vcardType = VCardConfig.getVCardTypeFromString(DEFAULT_VCARD_TYPE);
+ }
+ final CustomVCardEntryConstructor builder =
+ new CustomVCardEntryConstructor(vcardType, null);
+ builder.addEntryHandler(new ContactVCardEntryHandler(signal));
+
+ try {
+ if (!readOneVCardFile(uri, vcardType, builder, false, null)) {
+ return false;
+ }
+ } catch (final VCardNestedException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Must not reach here. " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @DoesNotRunOnMainThread
+ private boolean readOneVCardFile(final Uri uri, final int vcardType,
+ final VCardInterpreter interpreter,
+ final boolean throwNestedException, final List<String> errorFileNameList)
+ throws VCardNestedException {
+ Assert.isNotMainThread();
+ final ContentResolver resolver = mContext.getContentResolver();
+ VCardParser vCardParser;
+ InputStream is;
+ try {
+ is = resolver.openInputStream(uri);
+ vCardParser = new VCardParser_V21(vcardType);
+ vCardParser.addInterpreter(interpreter);
+
+ try {
+ vCardParser.parse(is);
+ } catch (final VCardVersionException e1) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ // Do nothing.
+ }
+ if (interpreter instanceof CustomVCardEntryConstructor) {
+ // Let the object clean up internal temporal objects,
+ ((CustomVCardEntryConstructor) interpreter).clear();
+ }
+
+ is = resolver.openInputStream(uri);
+
+ try {
+ vCardParser = new VCardParser_V30(vcardType);
+ vCardParser.addInterpreter(interpreter);
+ vCardParser.parse(is);
+ } catch (final VCardVersionException e2) {
+ throw new VCardException("vCard with unspported version.");
+ }
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "IOException was emitted: " + e.getMessage());
+
+ if (errorFileNameList != null) {
+ errorFileNameList.add(uri.toString());
+ }
+ return false;
+ } catch (final VCardNotSupportedException e) {
+ if ((e instanceof VCardNestedException) && throwNestedException) {
+ throw (VCardNestedException) e;
+ }
+ if (errorFileNameList != null) {
+ errorFileNameList.add(uri.toString());
+ }
+ return false;
+ } catch (final VCardException e) {
+ if (errorFileNameList != null) {
+ errorFileNameList.add(uri.toString());
+ }
+ return false;
+ }
+ return true;
+ }
+
+ class ContactVCardEntryHandler implements CustomVCardEntryConstructor.EntryHandler {
+ final CountDownLatch mSignal;
+
+ public ContactVCardEntryHandler(final CountDownLatch signal) {
+ mSignal = signal;
+ }
+
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ @DoesNotRunOnMainThread
+ public void onEntryCreated(final CustomVCardEntry entry) {
+ Assert.isNotMainThread();
+ final String displayName = entry.getDisplayName();
+ final List<VCardEntry.PhotoData> photos = entry.getPhotoList();
+ Uri avatarUri = null;
+ if (photos != null && photos.size() > 0) {
+ // The photo data is in bytes form, so we need to persist it in our temp directory
+ // so that ContactIconView can load it and display it later
+ // (and cache it, of course).
+ for (final VCardEntry.PhotoData photo : photos) {
+ final byte[] photoBytes = photo.getBytes();
+ if (photoBytes != null) {
+ final InputStream inputStream = new ByteArrayInputStream(photoBytes);
+ try {
+ avatarUri = UriUtil.persistContentToScratchSpace(inputStream);
+ if (avatarUri != null) {
+ // Just load the first avatar and be done. Want more? wait for V2.
+ break;
+ }
+ } finally {
+ try {
+ inputStream.close();
+ } catch (final IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+ }
+ }
+
+ // Fall back to generated avatar.
+ if (avatarUri == null) {
+ String destination = null;
+ final List<VCardEntry.PhoneData> phones = entry.getPhoneList();
+ if (phones != null && phones.size() > 0) {
+ destination = PhoneUtils.getDefault().getCanonicalBySystemLocale(
+ phones.get(0).getNumber());
+ }
+
+ if (destination == null) {
+ final List<VCardEntry.EmailData> emails = entry.getEmailList();
+ if (emails != null && emails.size() > 0) {
+ destination = emails.get(0).getAddress();
+ }
+ }
+ avatarUri = AvatarUriUtil.createAvatarUri(null, displayName, destination, null);
+ }
+
+ // Add the loaded vcard to the list.
+ mLoadedVCards.add(new VCardResourceEntry(entry, avatarUri));
+ }
+
+ @Override
+ public void onEnd() {
+ // Finished loading all vCard entries, signal the loading thread to proceed with the
+ // result.
+ if (mLoadedVCards.size() > 0) {
+ mLoadedResource = new VCardResource(getKey(), mLoadedVCards);
+ }
+ mSignal.countDown();
+ }
+ }
+
+ @Override
+ public int getRequestType() {
+ return MediaRequest.REQUEST_LOAD_MEDIA;
+ }
+
+ @Override
+ public MediaRequestDescriptor<VCardResource> getDescriptor() {
+ return mDescriptor;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java
new file mode 100644
index 0000000..4084851
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/VCardRequestDescriptor.java
@@ -0,0 +1,35 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.messaging.util.Assert;
+
+public class VCardRequestDescriptor extends MediaRequestDescriptor<VCardResource> {
+ public final Uri vCardUri;
+
+ public VCardRequestDescriptor(final Uri vCardUri) {
+ Assert.notNull(vCardUri);
+ this.vCardUri = vCardUri;
+ }
+
+ @Override
+ public MediaRequest<VCardResource> buildSyncMediaRequest(Context context) {
+ return new VCardRequest(context, this);
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/VCardResource.java b/src/com/android/messaging/datamodel/media/VCardResource.java
new file mode 100644
index 0000000..edf5e88
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/VCardResource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.datamodel.media;
+
+import java.util.List;
+
+/**
+ * Holds cached information of VCard contact info.
+ * The temporarily persisted avatar icon Uri is tied to the VCardResource. As a result, whenever
+ * the VCardResource is no longer used (i.e. close() is called), we need to asynchronously
+ * delete the avatar image from temp storage since no one will have reference to the avatar Uri
+ * again. The next time the same VCard is displayed, since the old resource has been evicted from
+ * the memory cache, we'll load and persist the avatar icon again.
+ */
+public class VCardResource extends RefCountedMediaResource {
+ private final List<VCardResourceEntry> mVCards;
+
+ public VCardResource(final String key, final List<VCardResourceEntry> vcards) {
+ super(key);
+ mVCards = vcards;
+ }
+
+ public List<VCardResourceEntry> getVCards() {
+ return mVCards;
+ }
+
+ @Override
+ public int getMediaSize() {
+ // Instead of track VCards by size in kilobytes, we track them by count.
+ return 0;
+ }
+
+ @Override
+ protected void close() {
+ for (final VCardResourceEntry vcard : mVCards) {
+ vcard.close();
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/VCardResourceEntry.java b/src/com/android/messaging/datamodel/media/VCardResourceEntry.java
new file mode 100644
index 0000000..f76b796
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/VCardResourceEntry.java
@@ -0,0 +1,389 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.data.PersonItemData;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntry.EmailData;
+import com.android.vcard.VCardEntry.ImData;
+import com.android.vcard.VCardEntry.NoteData;
+import com.android.vcard.VCardEntry.OrganizationData;
+import com.android.vcard.VCardEntry.PhoneData;
+import com.android.vcard.VCardEntry.PostalData;
+import com.android.vcard.VCardEntry.WebsiteData;
+import com.android.vcard.VCardProperty;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holds one entry item (i.e. a single contact) within a VCard resource. It is able to take
+ * a VCardEntry and extract relevant information from it.
+ */
+public class VCardResourceEntry {
+ public static final String PROPERTY_KIND = "KIND";
+
+ public static final String KIND_LOCATION = "location";
+
+ private final List<VCardResourceEntry.VCardResourceEntryDestinationItem> mContactInfo;
+ private final Uri mAvatarUri;
+ private final String mDisplayName;
+ private final CustomVCardEntry mVCard;
+
+ public VCardResourceEntry(final CustomVCardEntry vcard, final Uri avatarUri) {
+ mContactInfo = getContactInfoFromVCardEntry(vcard);
+ mDisplayName = getDisplayNameFromVCardEntry(vcard);
+ mAvatarUri = avatarUri;
+ mVCard = vcard;
+ }
+
+ void close() {
+ // If the avatar image was temporarily saved in the scratch folder, remove that.
+ if (MediaScratchFileProvider.isMediaScratchSpaceUri(mAvatarUri)) {
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ Factory.get().getApplicationContext().getContentResolver().delete(
+ mAvatarUri, null, null);
+ }
+ });
+ }
+ }
+
+ public String getKind() {
+ VCardProperty kindProperty = mVCard.getProperty(PROPERTY_KIND);
+ return kindProperty == null ? null : kindProperty.getRawValue();
+ }
+
+ public Uri getAvatarUri() {
+ return mAvatarUri;
+ }
+
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ public String getDisplayAddress() {
+ List<PostalData> postalList = mVCard.getPostalList();
+ if (postalList == null || postalList.size() < 1) {
+ return null;
+ }
+
+ return formatAddress(postalList.get(0));
+ }
+
+ public String getNotes() {
+ List<NoteData> notes = mVCard.getNotes();
+ if (notes == null || notes.size() == 0) {
+ return null;
+ }
+ StringBuilder noteBuilder = new StringBuilder();
+ for (NoteData note : notes) {
+ noteBuilder.append(note.getNote());
+ }
+ return noteBuilder.toString();
+ }
+
+ /**
+ * Returns a UI-facing representation that can be bound and consumed by the UI layer to display
+ * this VCard resource entry.
+ */
+ public PersonItemData getDisplayItem() {
+ return new PersonItemData() {
+ @Override
+ public Uri getAvatarUri() {
+ return VCardResourceEntry.this.getAvatarUri();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return VCardResourceEntry.this.getDisplayName();
+ }
+
+ @Override
+ public String getDetails() {
+ return null;
+ }
+
+ @Override
+ public Intent getClickIntent() {
+ return null;
+ }
+
+ @Override
+ public long getContactId() {
+ return ContactUtil.INVALID_CONTACT_ID;
+ }
+
+ @Override
+ public String getLookupKey() {
+ return null;
+ }
+
+ @Override
+ public String getNormalizedDestination() {
+ return null;
+ }
+ };
+ }
+
+ public List<VCardResourceEntry.VCardResourceEntryDestinationItem> getContactInfo() {
+ return mContactInfo;
+ }
+
+ private static List<VCardResourceEntryDestinationItem> getContactInfoFromVCardEntry(
+ final VCardEntry vcard) {
+ final Resources resources = Factory.get().getApplicationContext().getResources();
+ final List<VCardResourceEntry.VCardResourceEntryDestinationItem> retList =
+ new ArrayList<VCardResourceEntry.VCardResourceEntryDestinationItem>();
+ if (vcard.getPhoneList() != null) {
+ for (final PhoneData phone : vcard.getPhoneList()) {
+ final Intent intent = new Intent(Intent.ACTION_DIAL);
+ intent.setData(Uri.parse("tel:" + phone.getNumber()));
+ retList.add(new VCardResourceEntryDestinationItem(phone.getNumber(),
+ Phone.getTypeLabel(resources, phone.getType(), phone.getLabel()).toString(),
+ intent));
+ }
+ }
+
+ if (vcard.getEmailList() != null) {
+ for (final EmailData email : vcard.getEmailList()) {
+ final Intent intent = new Intent(Intent.ACTION_SENDTO);
+ intent.setData(Uri.parse("mailto:"));
+ intent.putExtra(Intent.EXTRA_EMAIL, new String[] { email.getAddress() });
+ retList.add(new VCardResourceEntryDestinationItem(email.getAddress(),
+ Phone.getTypeLabel(resources, email.getType(),
+ email.getLabel()).toString(), intent));
+ }
+ }
+
+ if (vcard.getPostalList() != null) {
+ for (final PostalData postalData : vcard.getPostalList()) {
+ String type;
+ try {
+ type = resources.
+ getStringArray(android.R.array.postalAddressTypes)
+ [postalData.getType() - 1];
+ } catch (final NotFoundException ex) {
+ type = resources.getStringArray(android.R.array.postalAddressTypes)[2];
+ } catch (final Exception e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem postal Exception:" + e);
+ type = resources.getStringArray(android.R.array.postalAddressTypes)[2];
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ final String address = formatAddress(postalData);
+ try {
+ intent.setData(Uri.parse("geo:0,0?q=" + URLEncoder.encode(address, "UTF-8")));
+ } catch (UnsupportedEncodingException e) {
+ intent = null;
+ }
+
+ retList.add(new VCardResourceEntryDestinationItem(address, type, intent));
+ }
+ }
+
+ if (vcard.getImList() != null) {
+ for (final ImData imData : vcard.getImList()) {
+ String type = null;
+ try {
+ type = resources.
+ getString(Im.getProtocolLabelResource(imData.getProtocol()));
+ } catch (final NotFoundException ex) {
+ // Do nothing since this implies an empty label.
+ }
+ retList.add(new VCardResourceEntryDestinationItem(imData.getAddress(), type, null));
+ }
+ }
+
+ if (vcard.getOrganizationList() != null) {
+ for (final OrganizationData organtization : vcard.getOrganizationList()) {
+ String type = null;
+ try {
+ type = resources.getString(Organization.getTypeLabelResource(
+ organtization.getType()));
+ } catch (final NotFoundException ex) {
+ //set other kind as "other"
+ type = resources.getStringArray(android.R.array.organizationTypes)[1];
+ } catch (final Exception e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "createContactItem org Exception:" + e);
+ type = resources.getStringArray(android.R.array.organizationTypes)[1];
+ }
+ retList.add(new VCardResourceEntryDestinationItem(
+ organtization.getOrganizationName(), type, null));
+ }
+ }
+
+ if (vcard.getWebsiteList() != null) {
+ for (final WebsiteData web : vcard.getWebsiteList()) {
+ if (web != null && TextUtils.isGraphic(web.getWebsite())){
+ String website = web.getWebsite();
+ if (!website.startsWith("http://") && !website.startsWith("https://")) {
+ // Prefix required for parsing to end up with a scheme and result in
+ // navigation
+ website = "http://" + website;
+ }
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(website));
+ retList.add(new VCardResourceEntryDestinationItem(web.getWebsite(), null,
+ intent));
+ }
+ }
+ }
+
+ if (vcard.getBirthday() != null) {
+ final String birthday = vcard.getBirthday();
+ if (TextUtils.isGraphic(birthday)){
+ retList.add(new VCardResourceEntryDestinationItem(birthday,
+ resources.getString(R.string.vcard_detail_birthday_label), null));
+ }
+ }
+
+ if (vcard.getNotes() != null) {
+ for (final NoteData note : vcard.getNotes()) {
+ final ArrayMap<String, String> curChildMap = new ArrayMap<String, String>();
+ if (TextUtils.isGraphic(note.getNote())){
+ retList.add(new VCardResourceEntryDestinationItem(note.getNote(),
+ resources.getString(R.string.vcard_detail_notes_label), null));
+ }
+ }
+ }
+ return retList;
+ }
+
+ private static String formatAddress(final PostalData postalData) {
+ final StringBuilder sb = new StringBuilder();
+ final String poBox = postalData.getPobox();
+ if (!TextUtils.isEmpty(poBox)) {
+ sb.append(poBox);
+ sb.append(" ");
+ }
+ final String extendedAddress = postalData.getExtendedAddress();
+ if (!TextUtils.isEmpty(extendedAddress)) {
+ sb.append(extendedAddress);
+ sb.append(" ");
+ }
+ final String street = postalData.getStreet();
+ if (!TextUtils.isEmpty(street)) {
+ sb.append(street);
+ sb.append(" ");
+ }
+ final String localty = postalData.getLocalty();
+ if (!TextUtils.isEmpty(localty)) {
+ sb.append(localty);
+ sb.append(" ");
+ }
+ final String region = postalData.getRegion();
+ if (!TextUtils.isEmpty(region)) {
+ sb.append(region);
+ sb.append(" ");
+ }
+ final String postalCode = postalData.getPostalCode();
+ if (!TextUtils.isEmpty(postalCode)) {
+ sb.append(postalCode);
+ sb.append(" ");
+ }
+ final String country = postalData.getCountry();
+ if (!TextUtils.isEmpty(country)) {
+ sb.append(country);
+ }
+ return sb.toString();
+ }
+
+ private static String getDisplayNameFromVCardEntry(final VCardEntry vcard) {
+ String name = vcard.getDisplayName();
+ if (name == null) {
+ vcard.consolidateFields();
+ name = vcard.getDisplayName();
+ }
+ return name;
+ }
+
+ /**
+ * Represents one entry line (e.g. phone number and phone label) for a single contact. Each
+ * VCardResourceEntry may hold one or more VCardResourceEntryDestinationItem's.
+ */
+ public static class VCardResourceEntryDestinationItem {
+ private final String mDisplayDestination;
+ private final String mDestinationType;
+ private final Intent mClickIntent;
+ public VCardResourceEntryDestinationItem(final String displayDestination,
+ final String destinationType, final Intent clickIntent) {
+ mDisplayDestination = displayDestination;
+ mDestinationType = destinationType;
+ mClickIntent = clickIntent;
+ }
+
+ /**
+ * Returns a UI-facing representation that can be bound and consumed by the UI layer to
+ * display this VCard resource destination entry.
+ */
+ public PersonItemData getDisplayItem() {
+ return new PersonItemData() {
+ @Override
+ public Uri getAvatarUri() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return mDisplayDestination;
+ }
+
+ @Override
+ public String getDetails() {
+ return mDestinationType;
+ }
+
+ @Override
+ public Intent getClickIntent() {
+ return mClickIntent;
+ }
+
+ @Override
+ public long getContactId() {
+ return ContactUtil.INVALID_CONTACT_ID;
+ }
+
+ @Override
+ public String getLookupKey() {
+ return null;
+ }
+
+ @Override
+ public String getNormalizedDestination() {
+ return null;
+ }
+ };
+ }
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
new file mode 100644
index 0000000..f17591c
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/VideoThumbnailRequest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.provider.MediaStore.Video.Thumbnails;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.MediaMetadataRetrieverWrapper;
+import com.android.messaging.util.OsUtil;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Class to request a video thumbnail.
+ * Users of this class as responsible for checking {@link #shouldShowIncomingVideoThumbnails}
+ */
+public class VideoThumbnailRequest extends ImageRequest<UriImageRequestDescriptor> {
+
+ public VideoThumbnailRequest(final Context context,
+ final UriImageRequestDescriptor descriptor) {
+ super(context, descriptor);
+ }
+
+ public static boolean shouldShowIncomingVideoThumbnails() {
+ return OsUtil.isAtLeastM();
+ }
+
+ @Override
+ protected InputStream getInputStreamForResource() throws FileNotFoundException {
+ return null;
+ }
+
+ @Override
+ protected boolean hasBitmapObject() {
+ return true;
+ }
+
+ @Override
+ protected Bitmap getBitmapForResource() throws IOException {
+ final Long mediaId = mDescriptor.getMediaStoreId();
+ Bitmap bitmap = null;
+ if (mediaId != null) {
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ bitmap = Thumbnails.getThumbnail(cr, mediaId, Thumbnails.MICRO_KIND, null);
+ } else {
+ final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
+ try {
+ retriever.setDataSource(mDescriptor.uri);
+ bitmap = retriever.getFrameAtTime();
+ } finally {
+ retriever.release();
+ }
+ }
+ if (bitmap != null) {
+ mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight());
+ }
+ return bitmap;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
new file mode 100644
index 0000000..907bb8f
--- /dev/null
+++ b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.Context;
+
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.UriUtil;
+
+public class VideoThumbnailRequestDescriptor extends UriImageRequestDescriptor {
+ protected final long mMediaId;
+ public VideoThumbnailRequestDescriptor(final long id, String path, int desiredWidth,
+ int desiredHeight, int sourceWidth, int sourceHeight) {
+ super(UriUtil.getUriForResourceFile(path), desiredWidth, desiredHeight, sourceWidth,
+ sourceHeight, false /* canCompress */, false /* isStatic */,
+ false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ mMediaId = id;
+ }
+
+ @Override
+ public MediaRequest<ImageResource> buildSyncMediaRequest(Context context) {
+ return new VideoThumbnailRequest(context, this);
+ }
+
+ @Override
+ public Long getMediaStoreId() {
+ return mMediaId;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/Downloads.java b/src/com/android/messaging/mmslib/Downloads.java
new file mode 100644
index 0000000..9afc48c
--- /dev/null
+++ b/src/com/android/messaging/mmslib/Downloads.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2008 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.mmslib;
+
+import android.app.DownloadManager;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+/**
+ * The Download Manager
+ *
+ * @pending
+ */
+public final class Downloads {
+ private Downloads() {}
+
+ /**
+ * Implementation details
+ *
+ * Exposes constants used to interact with the download manager's
+ * content provider.
+ * The constants URI ... STATUS are the names of columns in the downloads table.
+ *
+ * @hide
+ */
+ public static final class Impl implements BaseColumns {
+ private Impl() {}
+
+ /**
+ * The permission to access the download manager
+ */
+ public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
+
+ /**
+ * The permission to access the download manager's advanced functions
+ */
+ public static final String PERMISSION_ACCESS_ADVANCED =
+ "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
+
+ /**
+ * The permission to access the all the downloads in the manager.
+ */
+ public static final String PERMISSION_ACCESS_ALL =
+ "android.permission.ACCESS_ALL_DOWNLOADS";
+
+ /**
+ * The permission to directly access the download manager's cache
+ * directory
+ */
+ public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
+
+ /**
+ * The permission to send broadcasts on download completion
+ */
+ public static final String PERMISSION_SEND_INTENTS =
+ "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
+
+ /**
+ * The permission to download files to the cache partition that won't be automatically
+ * purged when space is needed.
+ */
+ public static final String PERMISSION_CACHE_NON_PURGEABLE =
+ "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
+
+ /**
+ * The permission to download files without any system notification being shown.
+ */
+ public static final String PERMISSION_NO_NOTIFICATION =
+ "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
+
+ /**
+ * The content:// URI to access downloads owned by the caller's UID.
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://downloads/my_downloads");
+
+ /**
+ * The content URI for accessing all downloads across all UIDs (requires the
+ * ACCESS_ALL_DOWNLOADS permission).
+ */
+ public static final Uri ALL_DOWNLOADS_CONTENT_URI =
+ Uri.parse("content://downloads/all_downloads");
+
+ /** URI segment to access a publicly accessible downloaded file */
+ public static final String PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT = "public_downloads";
+
+ /**
+ * The content URI for accessing publicly accessible downloads (i.e., it requires no
+ * permissions to access this downloaded file)
+ */
+ public static final Uri PUBLICLY_ACCESSIBLE_DOWNLOADS_URI =
+ Uri.parse("content://downloads/" + PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT);
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when that download completes. The
+ * download's content: uri is specified in the intent's data.
+ */
+ public static final String ACTION_DOWNLOAD_COMPLETED =
+ "android.intent.action.DOWNLOAD_COMPLETED";
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when the user selects the notification
+ * associated with that download. The download's content: uri is specified
+ * in the intent's data if the click is associated with a single download,
+ * or Downloads.CONTENT_URI if the notification is associated with
+ * multiple downloads.
+ * Note: this is not currently sent for downloads that have completed
+ * successfully.
+ */
+ public static final String ACTION_NOTIFICATION_CLICKED =
+ "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
+
+ /**
+ * The name of the column containing the URI of the data being downloaded.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_URI = "uri";
+
+ /**
+ * The name of the column containing application-specific data.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_APP_DATA = "entity";
+
+ /**
+ * The name of the column containing the flags that indicates whether
+ * the initiating application is capable of verifying the integrity of
+ * the downloaded file. When this flag is set, the download manager
+ * performs downloads and reports success even in some situations where
+ * it can't guarantee that the download has completed (e.g. when doing
+ * a byte-range request without an ETag, or when it can't determine
+ * whether a download fully completed).
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NO_INTEGRITY = "no_integrity";
+
+ /**
+ * The name of the column containing the filename that the initiating
+ * application recommends. When possible, the download manager will attempt
+ * to use this filename, or a variation, as the actual name for the file.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_FILE_NAME_HINT = "hint";
+
+ /**
+ * The name of the column containing the filename where the downloaded data
+ * was actually stored.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String _DATA = "_data";
+
+ /**
+ * The name of the column containing the MIME type of the downloaded data.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_MIME_TYPE = "mimetype";
+
+ /**
+ * The name of the column containing the flag that controls the destination
+ * of the download. See the DESTINATION_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_DESTINATION = "destination";
+
+ /**
+ * The name of the column containing the flags that controls whether the
+ * download is displayed by the UI. See the VISIBILITY_* constants for
+ * a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_VISIBILITY = "visibility";
+
+ /**
+ * The name of the column containing the current control state of the download.
+ * Applications can write to this to control (pause/resume) the download.
+ * the CONTROL_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_CONTROL = "control";
+
+ /**
+ * The name of the column containing the current status of the download.
+ * Applications can read this to follow the progress of each download. See
+ * the STATUS_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_STATUS = "status";
+
+ /**
+ * The name of the column containing the date at which some interesting
+ * status changed in the download. Stored as a System.currentTimeMillis()
+ * value.
+ * <P>Type: BIGINT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_LAST_MODIFICATION = "lastmod";
+
+ /**
+ * The name of the column containing the package name of the application
+ * that initiating the download. The download manager will send
+ * notifications to a component in this package when the download completes.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
+
+ /**
+ * The name of the column containing the component name of the class that
+ * will receive notifications associated with the download. The
+ * package/class combination is passed to
+ * Intent.setClassName(String,String).
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
+
+ /**
+ * If extras are specified when requesting a download they will be provided in the intent
+ * that is sent to the specified class and package when a download has finished.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
+
+ /**
+ * The name of the column contain the values of the cookie to be used for
+ * the download. This is used directly as the value for the Cookie: HTTP
+ * header that gets sent with the request.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_COOKIE_DATA = "cookiedata";
+
+ /**
+ * The name of the column containing the user agent that the initiating
+ * application wants the download manager to use for this download.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_USER_AGENT = "useragent";
+
+ /**
+ * The name of the column containing the referer (sic) that the initiating
+ * application wants the download manager to use for this download.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_REFERER = "referer";
+
+ /**
+ * The name of the column containing the total size of the file being
+ * downloaded.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_TOTAL_BYTES = "total_bytes";
+
+ /**
+ * The name of the column containing the size of the part of the file that
+ * has been downloaded so far.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_CURRENT_BYTES = "current_bytes";
+
+ /**
+ * The name of the column where the initiating application can provide the
+ * UID of another application that is allowed to access this download. If
+ * multiple applications share the same UID, all those applications will be
+ * allowed to access this download. This column can be updated after the
+ * download is initiated. This requires the permission
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_OTHER_UID = "otheruid";
+
+ /**
+ * The name of the column where the initiating application can provided the
+ * title of this download. The title will be displayed ito the user in the
+ * list of downloads.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The name of the column where the initiating application can provide the
+ * description of this download. The description will be displayed to the
+ * user in the list of downloads.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ /**
+ * The name of the column indicating whether the download was requesting through the public
+ * API. This controls some differences in behavior.
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_IS_PUBLIC_API = "is_public_api";
+
+ /**
+ * The name of the column holding a bitmask of allowed network types. This is only used for
+ * public API downloads.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
+
+ /**
+ * The name of the column indicating whether roaming connections can be used. This is only
+ * used for public API downloads.
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_ALLOW_ROAMING = "allow_roaming";
+
+ /**
+ * The name of the column indicating whether metered connections can be used. This is only
+ * used for public API downloads.
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_ALLOW_METERED = "allow_metered";
+
+ /**
+ * Whether or not this download should be displayed in the system's Downloads UI. Defaults
+ * to true.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui";
+
+ /**
+ * If true, the user has confirmed that this download can proceed over the mobile network
+ * even though it exceeds the recommended maximum size.
+ * <P>Type: BOOLEAN</P>
+ */
+ public static final String COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT =
+ "bypass_recommended_size_limit";
+
+ /**
+ * Set to true if this download is deleted. It is completely removed from the database
+ * when MediaProvider database also deletes the metadata asociated with this downloaded
+ * file.
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_DELETED = "deleted";
+
+ /**
+ * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
+ * used to delete the entries from MediaProvider database when it is deleted from the
+ * downloaded list.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
+
+ /**
+ * The column that is used to remember whether the media scanner was invoked.
+ * It can take the values: null or 0(not scanned), 1(scanned), 2 (not scannable).
+ * <P>Type: TEXT</P>
+ */
+ public static final String COLUMN_MEDIA_SCANNED = "scanned";
+
+ /**
+ * The column with errorMsg for a failed downloaded.
+ * Used only for debugging purposes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String COLUMN_ERROR_MSG = "errorMsg";
+
+ /**
+ * This column stores the source of the last update to this row.
+ * This column is only for internal use.
+ * Valid values are indicated by LAST_UPDATESRC_* constants.
+ * <P>Type: INT</P>
+ */
+ public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc";
+
+ /** The column that is used to count retries */
+ public static final String COLUMN_FAILED_CONNECTIONS = "numfailed";
+
+ /**
+ * default value for {@link #COLUMN_LAST_UPDATESRC}.
+ * This value is used when this column's value is not relevant.
+ */
+ public static final int LAST_UPDATESRC_NOT_RELEVANT = 0;
+
+ /**
+ * One of the values taken by {@link #COLUMN_LAST_UPDATESRC}.
+ * This value is used when the update is NOT to be relayed to the DownloadService
+ * (and thus spare DownloadService from scanning the database when this change occurs)
+ */
+ public static final int LAST_UPDATESRC_DONT_NOTIFY_DOWNLOADSVC = 1;
+
+ /*
+ * Lists the destinations that an application can specify for a download.
+ */
+
+ /**
+ * This download will be saved to the external storage. This is the
+ * default behavior, and should be used for any file that the user
+ * can freely access, copy, delete. Even with that destination,
+ * unencrypted DRM files are saved in secure internal storage.
+ * Downloads to the external destination only write files for which
+ * there is a registered handler. The resulting files are accessible
+ * by filename to all applications.
+ */
+ public static final int DESTINATION_EXTERNAL = 0;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition. This is the behavior used by applications that want to
+ * download private files that are used and deleted soon after they
+ * get downloaded. All file types are allowed, and only the initiating
+ * application can access the file (indirectly through a content
+ * provider). This requires the
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
+ */
+ public static final int DESTINATION_CACHE_PARTITION = 1;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition and will be purged as necessary to make space. This is
+ * for private files (similar to CACHE_PARTITION) that aren't deleted
+ * immediately after they are used, and are kept around by the download
+ * manager as long as space is available.
+ */
+ public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition, as with DESTINATION_CACHE_PARTITION, but the download
+ * will not proceed if the user is on a roaming data connection.
+ */
+ public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
+
+ /**
+ * This download will be saved to the location given by the file URI in
+ * {@link #COLUMN_FILE_NAME_HINT}.
+ */
+ public static final int DESTINATION_FILE_URI = 4;
+
+ /**
+ * This download will be saved to the system cache ("/cache")
+ * partition. This option is only used by system apps and so it requires
+ * android.permission.ACCESS_CACHE_FILESYSTEM permission.
+ */
+ public static final int DESTINATION_SYSTEMCACHE_PARTITION = 5;
+
+ /**
+ * This download was completed by the caller (i.e., NOT downloadmanager)
+ * and caller wants to have this download displayed in Downloads App.
+ */
+ public static final int DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD = 6;
+
+ /**
+ * This download is allowed to run.
+ */
+ public static final int CONTROL_RUN = 0;
+
+ /**
+ * This download must pause at the first opportunity.
+ */
+ public static final int CONTROL_PAUSED = 1;
+
+ /*
+ * Lists the states that the download manager can set on a download
+ * to notify applications of the download progress.
+ * The codes follow the HTTP families:<br>
+ * 1xx: informational<br>
+ * 2xx: success<br>
+ * 3xx: redirects (not used by the download manager)<br>
+ * 4xx: client errors<br>
+ * 5xx: server errors
+ */
+
+ /**
+ * Returns whether the status is informational (i.e. 1xx).
+ */
+ public static boolean isStatusInformational(int status) {
+ return (status >= 100 && status < 200);
+ }
+
+ /**
+ * Returns whether the status is a success (i.e. 2xx).
+ */
+ public static boolean isStatusSuccess(int status) {
+ return (status >= 200 && status < 300);
+ }
+
+ /**
+ * Returns whether the status is an error (i.e. 4xx or 5xx).
+ */
+ public static boolean isStatusError(int status) {
+ return (status >= 400 && status < 600);
+ }
+
+ /**
+ * Returns whether the status is a client error (i.e. 4xx).
+ */
+ public static boolean isStatusClientError(int status) {
+ return (status >= 400 && status < 500);
+ }
+
+ /**
+ * Returns whether the status is a server error (i.e. 5xx).
+ */
+ public static boolean isStatusServerError(int status) {
+ return (status >= 500 && status < 600);
+ }
+
+ /**
+ * this method determines if a notification should be displayed for a
+ * given {@link #COLUMN_VISIBILITY} value
+ * @param visibility the value of {@link #COLUMN_VISIBILITY}.
+ * @return true if the notification should be displayed. false otherwise.
+ */
+ public static boolean isNotificationToBeDisplayed(int visibility) {
+ return visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED ||
+ visibility == DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION;
+ }
+
+ /**
+ * Returns whether the download has completed (either with success or
+ * error).
+ */
+ public static boolean isStatusCompleted(int status) {
+ return (status >= 200 && status < 300) || (status >= 400 && status < 600);
+ }
+
+ /**
+ * This download hasn't stated yet
+ */
+ public static final int STATUS_PENDING = 190;
+
+ /**
+ * This download has started
+ */
+ public static final int STATUS_RUNNING = 192;
+
+ /**
+ * This download has been paused by the owning app.
+ */
+ public static final int STATUS_PAUSED_BY_APP = 193;
+
+ /**
+ * This download encountered some network error and is waiting before retrying the request.
+ */
+ public static final int STATUS_WAITING_TO_RETRY = 194;
+
+ /**
+ * This download is waiting for network connectivity to proceed.
+ */
+ public static final int STATUS_WAITING_FOR_NETWORK = 195;
+
+ /**
+ * This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
+ * connection to proceed.
+ */
+ public static final int STATUS_QUEUED_FOR_WIFI = 196;
+
+ /**
+ * This download couldn't be completed due to insufficient storage
+ * space. Typically, this is because the SD card is full.
+ */
+ public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 198;
+
+ /**
+ * This download couldn't be completed because no external storage
+ * device was found. Typically, this is because the SD card is not
+ * mounted.
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 199;
+
+ /**
+ * This download has successfully completed.
+ * Warning: there might be other status values that indicate success
+ * in the future.
+ * Use isSucccess() to capture the entire category.
+ */
+ public static final int STATUS_SUCCESS = 200;
+
+ /**
+ * This request couldn't be parsed. This is also used when processing
+ * requests with unknown/unsupported URI schemes.
+ */
+ public static final int STATUS_BAD_REQUEST = 400;
+
+ /**
+ * This download can't be performed because the content type cannot be
+ * handled.
+ */
+ public static final int STATUS_NOT_ACCEPTABLE = 406;
+
+ /**
+ * This download cannot be performed because the length cannot be
+ * determined accurately. This is the code for the HTTP error "Length
+ * Required", which is typically used when making requests that require
+ * a content length but don't have one, and it is also used in the
+ * client when a response is received whose length cannot be determined
+ * accurately (therefore making it impossible to know when a download
+ * completes).
+ */
+ public static final int STATUS_LENGTH_REQUIRED = 411;
+
+ /**
+ * This download was interrupted and cannot be resumed.
+ * This is the code for the HTTP error "Precondition Failed", and it is
+ * also used in situations where the client doesn't have an ETag at all.
+ */
+ public static final int STATUS_PRECONDITION_FAILED = 412;
+
+ /**
+ * The lowest-valued error status that is not an actual HTTP status code.
+ */
+ public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
+
+ /**
+ * The requested destination file already exists.
+ */
+ public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
+
+ /**
+ * Some possibly transient error occurred, but we can't resume the download.
+ */
+ public static final int STATUS_CANNOT_RESUME = 489;
+
+ /**
+ * This download was canceled
+ */
+ public static final int STATUS_CANCELED = 490;
+
+ /**
+ * This download has completed with an error.
+ * Warning: there will be other status values that indicate errors in
+ * the future. Use isStatusError() to capture the entire category.
+ */
+ public static final int STATUS_UNKNOWN_ERROR = 491;
+
+ /**
+ * This download couldn't be completed because of a storage issue.
+ * Typically, that's because the filesystem is missing or full.
+ * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
+ * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
+ */
+ public static final int STATUS_FILE_ERROR = 492;
+
+ /**
+ * This download couldn't be completed because of an HTTP
+ * redirect response that the download manager couldn't
+ * handle.
+ */
+ public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+ /**
+ * This download couldn't be completed because of an
+ * unspecified unhandled HTTP code.
+ */
+ public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
+
+ /**
+ * This download couldn't be completed because of an
+ * error receiving or processing data at the HTTP level.
+ */
+ public static final int STATUS_HTTP_DATA_ERROR = 495;
+
+ /**
+ * This download couldn't be completed because of an
+ * HttpException while setting up the request.
+ */
+ public static final int STATUS_HTTP_EXCEPTION = 496;
+
+ /**
+ * This download couldn't be completed because there were
+ * too many redirects.
+ */
+ public static final int STATUS_TOO_MANY_REDIRECTS = 497;
+
+ /**
+ * This download has failed because requesting application has been
+ * blocked by {@link NetworkPolicyManager}.
+ *
+ * @hide
+ * @deprecated since behavior now uses
+ * {@link #STATUS_WAITING_FOR_NETWORK}
+ */
+ @Deprecated
+ public static final int STATUS_BLOCKED = 498;
+
+ /** {@hide} */
+ public static String statusToString(int status) {
+ switch (status) {
+ case STATUS_PENDING: return "PENDING";
+ case STATUS_RUNNING: return "RUNNING";
+ case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP";
+ case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY";
+ case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
+ case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI";
+ case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR";
+ case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR";
+ case STATUS_SUCCESS: return "SUCCESS";
+ case STATUS_BAD_REQUEST: return "BAD_REQUEST";
+ case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE";
+ case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED";
+ case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED";
+ case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR";
+ case STATUS_CANNOT_RESUME: return "CANNOT_RESUME";
+ case STATUS_CANCELED: return "CANCELED";
+ case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR";
+ case STATUS_FILE_ERROR: return "FILE_ERROR";
+ case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT";
+ case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE";
+ case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR";
+ case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION";
+ case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS";
+ case STATUS_BLOCKED: return "BLOCKED";
+ default: return Integer.toString(status);
+ }
+ }
+
+ /**
+ * This download is visible but only shows in the notifications
+ * while it's in progress.
+ */
+ public static final int VISIBILITY_VISIBLE = DownloadManager.Request.VISIBILITY_VISIBLE;
+
+ /**
+ * This download is visible and shows in the notifications while
+ * in progress and after completion.
+ */
+ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED =
+ DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
+
+ /**
+ * This download doesn't show in the UI or in the notifications.
+ */
+ public static final int VISIBILITY_HIDDEN = DownloadManager.Request.VISIBILITY_HIDDEN;
+
+ /**
+ * Constants related to HTTP request headers associated with each download.
+ */
+ public static class RequestHeaders {
+ public static final String HEADERS_DB_TABLE = "request_headers";
+ public static final String COLUMN_DOWNLOAD_ID = "download_id";
+ public static final String COLUMN_HEADER = "header";
+ public static final String COLUMN_VALUE = "value";
+
+ /**
+ * Path segment to add to a download URI to retrieve request headers
+ */
+ public static final String URI_SEGMENT = "headers";
+
+ /**
+ * Prefix for ContentValues keys that contain HTTP header lines, to be passed to
+ * DownloadProvider.insert().
+ */
+ public static final String INSERT_KEY_PREFIX = "http_header_";
+ }
+ }
+
+ /**
+ * Query where clause for general querying.
+ */
+ private static final String QUERY_WHERE_CLAUSE = Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND "
+ + Impl.COLUMN_NOTIFICATION_CLASS + "=?";
+
+ /**
+ * Delete all the downloads for a package/class pair.
+ */
+ public static final void removeAllDownloadsByPackage(
+ Context context, String notificationPackage, String notificationClass) {
+ context.getContentResolver().delete(Impl.CONTENT_URI, QUERY_WHERE_CLAUSE,
+ new String[] { notificationPackage, notificationClass });
+ }
+}
diff --git a/src/com/android/messaging/mmslib/InvalidHeaderValueException.java b/src/com/android/messaging/mmslib/InvalidHeaderValueException.java
new file mode 100644
index 0000000..c141591
--- /dev/null
+++ b/src/com/android/messaging/mmslib/InvalidHeaderValueException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+ private static final long serialVersionUID = -2053384496042052262L;
+
+ /**
+ * Constructs an InvalidHeaderValueException with no detailed message.
+ */
+ public InvalidHeaderValueException() {
+ super();
+ }
+
+ /**
+ * Constructs an InvalidHeaderValueException with the specified detailed message.
+ *
+ * @param message the detailed message.
+ */
+ public InvalidHeaderValueException(String message) {
+ super(message);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/MmsException.java b/src/com/android/messaging/mmslib/MmsException.java
new file mode 100644
index 0000000..173511d
--- /dev/null
+++ b/src/com/android/messaging/mmslib/MmsException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+ private static final long serialVersionUID = -7323249827281485390L;
+
+ /**
+ * Creates a new MmsException.
+ */
+ public MmsException() {
+ super();
+ }
+
+ /**
+ * Creates a new MmsException with the specified detail message.
+ *
+ * @param message the detail message.
+ */
+ public MmsException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new MmsException with the specified cause.
+ *
+ * @param cause the cause.
+ */
+ public MmsException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a new MmsException with the specified detail message and cause.
+ *
+ * @param message the detail message.
+ * @param cause the cause.
+ */
+ public MmsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/SqliteWrapper.java b/src/com/android/messaging/mmslib/SqliteWrapper.java
new file mode 100644
index 0000000..8ef0e6f
--- /dev/null
+++ b/src/com/android/messaging/mmslib/SqliteWrapper.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 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.mmslib;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+
+import com.android.messaging.util.LogUtil;
+
+// Wrapper around content resolver methods to catch exceptions
+public final class SqliteWrapper {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private SqliteWrapper() {
+ // Forbidden being instantiated.
+ }
+
+ public static Cursor query(Context context, ContentResolver resolver, Uri uri,
+ String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ try {
+ return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ } catch (SQLiteException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when query", e);
+ return null;
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when query", e);
+ return null;
+ }
+ }
+
+ public static int update(Context context, ContentResolver resolver, Uri uri,
+ ContentValues values, String where, String[] selectionArgs) {
+ try {
+ return resolver.update(uri, values, where, selectionArgs);
+ } catch (SQLiteException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when update", e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when update", e);
+ return -1;
+ }
+ }
+
+ public static int delete(Context context, ContentResolver resolver, Uri uri,
+ String where, String[] selectionArgs) {
+ try {
+ return resolver.delete(uri, where, selectionArgs);
+ } catch (SQLiteException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when delete", e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when delete", e);
+ return -1;
+ }
+ }
+
+ public static Uri insert(Context context, ContentResolver resolver,
+ Uri uri, ContentValues values) {
+ try {
+ return resolver.insert(uri, values);
+ } catch (SQLiteException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when insert", e);
+ return null;
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(TAG, "SqliteWrapper: catch an exception when insert", e);
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java b/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..05494c0
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/AcknowledgeInd.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-Acknowledge.ind pdu.
+ *
+ * @param mmsVersion current viersion of mms
+ * @param transactionId the transaction-id value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * @throws NullPointerException if transactionId is null.
+ */
+ public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+ throws InvalidHeaderValueException {
+ super();
+
+ setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ AcknowledgeInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Report-Allowed field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getReportAllowed() {
+ return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Report-Allowed field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReportAllowed(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/Base64.java b/src/com/android/messaging/mmslib/pdu/Base64.java
new file mode 100644
index 0000000..2f27117
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/Base64.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+public class Base64 {
+ /**
+ * Used to get the number of Quadruples.
+ */
+ static final int FOURBYTE = 4;
+
+ /**
+ * Byte used to pad output.
+ */
+ static final byte PAD = (byte) '=';
+
+ /**
+ * The base length.
+ */
+ static final int BASELENGTH = 255;
+
+ // Create arrays to hold the base64 characters
+ private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+ // Populating the character arrays
+ static {
+ for (int i = 0; i < BASELENGTH; i++) {
+ base64Alphabet[i] = (byte) -1;
+ }
+ for (int i = 'Z'; i >= 'A'; i--) {
+ base64Alphabet[i] = (byte) (i - 'A');
+ }
+ for (int i = 'z'; i >= 'a'; i--) {
+ base64Alphabet[i] = (byte) (i - 'a' + 26);
+ }
+ for (int i = '9'; i >= '0'; i--) {
+ base64Alphabet[i] = (byte) (i - '0' + 52);
+ }
+
+ base64Alphabet['+'] = 62;
+ base64Alphabet['/'] = 63;
+ }
+
+ /**
+ * Decodes Base64 data into octects
+ *
+ * @param base64Data Byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ public static byte[] decodeBase64(byte[] base64Data) {
+ // RFC 2045 requires that we discard ALL non-Base64 characters
+ base64Data = discardNonBase64(base64Data);
+
+ // handle the edge case, so we don't have to worry about it later
+ if (base64Data.length == 0) {
+ return new byte[0];
+ }
+
+ int numberQuadruple = base64Data.length / FOURBYTE;
+ byte decodedData[] = null;
+ byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+ // Throw away anything not in base64Data
+
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ {
+ // this sizes the output array properly - rlw
+ int lastData = base64Data.length;
+ // ignore the '=' padding
+ while (base64Data[lastData - 1] == PAD) {
+ if (--lastData == 0) {
+ return new byte[0];
+ }
+ }
+ decodedData = new byte[lastData - numberQuadruple];
+ }
+
+ for (int i = 0; i < numberQuadruple; i++) {
+ dataIndex = i * 4;
+ marker0 = base64Data[dataIndex + 2];
+ marker1 = base64Data[dataIndex + 3];
+
+ b1 = base64Alphabet[base64Data[dataIndex]];
+ b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+ if (marker0 != PAD && marker1 != PAD) {
+ //No PAD e.g 3cQl
+ b3 = base64Alphabet[marker0];
+ b4 = base64Alphabet[marker1];
+
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] =
+ (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+ } else if (marker0 == PAD) {
+ //Two PAD e.g. 3c[Pad][Pad]
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ } else if (marker1 == PAD) {
+ //One PAD e.g. 3cQ[Pad]
+ b3 = base64Alphabet[marker0];
+
+ decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex + 1] =
+ (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ }
+ encodedIndex += 3;
+ }
+ return decodedData;
+ }
+
+ /**
+ * Check octect wheter it is a base64 encoding.
+ *
+ * @param octect to be checked byte
+ * @return ture if it is base64 encoding, false otherwise.
+ */
+ private static boolean isBase64(byte octect) {
+ if (octect == PAD) {
+ return true;
+ } else if (base64Alphabet[octect] == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Discards any characters outside of the base64 alphabet, per
+ * the requirements on page 25 of RFC 2045 - "Any characters
+ * outside of the base64 alphabet are to be ignored in base64
+ * encoded data."
+ *
+ * @param data The base-64 encoded data to groom
+ * @return The data, less non-base64 characters (see RFC 2045).
+ */
+ static byte[] discardNonBase64(byte[] data) {
+ byte groomedData[] = new byte[data.length];
+ int bytesCopied = 0;
+
+ for (int i = 0; i < data.length; i++) {
+ if (isBase64(data[i])) {
+ groomedData[bytesCopied++] = data[i];
+ }
+ }
+
+ byte packedData[] = new byte[bytesCopied];
+
+ System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+ return packedData;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/CharacterSets.java b/src/com/android/messaging/mmslib/pdu/CharacterSets.java
new file mode 100644
index 0000000..0a9099b
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/CharacterSets.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import android.support.v4.util.SimpleArrayMap;
+import android.util.SparseArray;
+
+import java.io.UnsupportedEncodingException;
+
+public class CharacterSets {
+ /**
+ * IANA assigned MIB enum numbers.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * Any-charset = <Octet 128>
+ * Equivalent to the special RFC2616 charset value "*"
+ *
+ * Link to all the charsets: http://www.iana.org/assignments/character-sets/character-sets.xhtml
+ * Use Charset.availableCharsets() to see if a potential charset is supported in java:
+ * private void dumpCharsets() {
+ * logvv("dumpCharsets");
+ * SortedMap<String, Charset> charsets = Charset.availableCharsets();
+ * for (Entry<String, Charset> entry : charsets.entrySet()) {
+ * String key = entry.getKey();
+ * Charset value = entry.getValue();
+ * logvv("charset key: " + key + " value: " + value);
+ * }
+ * }
+ *
+ * As of March 21, 2014, here is the list from dumpCharsets:
+ * Adobe-Standard-Encoding value: java.nio.charset.CharsetICU[Adobe-Standard-Encoding]
+ * Big5 value: java.nio.charset.CharsetICU[Big5]
+ * Big5-HKSCS value: java.nio.charset.CharsetICU[Big5-HKSCS]
+ * BOCU-1 value: java.nio.charset.CharsetICU[BOCU-1]
+ * CESU-8 value: java.nio.charset.CharsetICU[CESU-8]
+ * cp1363 value: java.nio.charset.CharsetICU[cp1363]
+ * cp851 value: java.nio.charset.CharsetICU[cp851]
+ * cp864 value: java.nio.charset.CharsetICU[cp864]
+ * EUC-JP value: java.nio.charset.CharsetICU[EUC-JP]
+ * EUC-KR value: java.nio.charset.CharsetICU[EUC-KR]
+ * GB18030 value: java.nio.charset.CharsetICU[GB18030]
+ * GBK value: java.nio.charset.CharsetICU[GBK]
+ * hp-roman8 value: java.nio.charset.CharsetICU[hp-roman8]
+ * HZ-GB-2312 value: java.nio.charset.CharsetICU[HZ-GB-2312]
+ * IBM-Thai value: java.nio.charset.CharsetICU[IBM-Thai]
+ * IBM00858 value: java.nio.charset.CharsetICU[IBM00858]
+ * IBM01140 value: java.nio.charset.CharsetICU[IBM01140]
+ * IBM01141 value: java.nio.charset.CharsetICU[IBM01141]
+ * IBM01142 value: java.nio.charset.CharsetICU[IBM01142]
+ * IBM01143 value: java.nio.charset.CharsetICU[IBM01143]
+ * IBM01144 value: java.nio.charset.CharsetICU[IBM01144]
+ * IBM01145 value: java.nio.charset.CharsetICU[IBM01145]
+ * IBM01146 value: java.nio.charset.CharsetICU[IBM01146]
+ * IBM01147 value: java.nio.charset.CharsetICU[IBM01147]
+ * IBM01148 value: java.nio.charset.CharsetICU[IBM01148]
+ * IBM01149 value: java.nio.charset.CharsetICU[IBM01149]
+ * IBM037 value: java.nio.charset.CharsetICU[IBM037]
+ * IBM1026 value: java.nio.charset.CharsetICU[IBM1026]
+ * IBM1047 value: java.nio.charset.CharsetICU[IBM1047]
+ * IBM273 value: java.nio.charset.CharsetICU[IBM273]
+ * IBM277 value: java.nio.charset.CharsetICU[IBM277]
+ * IBM278 value: java.nio.charset.CharsetICU[IBM278]
+ * IBM280 value: java.nio.charset.CharsetICU[IBM280]
+ * IBM284 value: java.nio.charset.CharsetICU[IBM284]
+ * IBM285 value: java.nio.charset.CharsetICU[IBM285]
+ * IBM290 value: java.nio.charset.CharsetICU[IBM290]
+ * IBM297 value: java.nio.charset.CharsetICU[IBM297]
+ * IBM420 value: java.nio.charset.CharsetICU[IBM420]
+ * IBM424 value: java.nio.charset.CharsetICU[IBM424]
+ * IBM437 value: java.nio.charset.CharsetICU[IBM437]
+ * IBM500 value: java.nio.charset.CharsetICU[IBM500]
+ * IBM775 value: java.nio.charset.CharsetICU[IBM775]
+ * IBM850 value: java.nio.charset.CharsetICU[IBM850]
+ * IBM852 value: java.nio.charset.CharsetICU[IBM852]
+ * IBM855 value: java.nio.charset.CharsetICU[IBM855]
+ * IBM857 value: java.nio.charset.CharsetICU[IBM857]
+ * IBM860 value: java.nio.charset.CharsetICU[IBM860]
+ * IBM861 value: java.nio.charset.CharsetICU[IBM861]
+ * IBM862 value: java.nio.charset.CharsetICU[IBM862]
+ * IBM863 value: java.nio.charset.CharsetICU[IBM863]
+ * IBM865 value: java.nio.charset.CharsetICU[IBM865]
+ * IBM866 value: java.nio.charset.CharsetICU[IBM866]
+ * IBM868 value: java.nio.charset.CharsetICU[IBM868]
+ * IBM869 value: java.nio.charset.CharsetICU[IBM869]
+ * IBM870 value: java.nio.charset.CharsetICU[IBM870]
+ * IBM871 value: java.nio.charset.CharsetICU[IBM871]
+ * IBM918 value: java.nio.charset.CharsetICU[IBM918]
+ * ISO-2022-CN value: java.nio.charset.CharsetICU[ISO-2022-CN]
+ * ISO-2022-CN-EXT value: java.nio.charset.CharsetICU[ISO-2022-CN-EXT]
+ * ISO-2022-JP value: java.nio.charset.CharsetICU[ISO-2022-JP]
+ * ISO-2022-JP-1 value: java.nio.charset.CharsetICU[ISO-2022-JP-1]
+ * ISO-2022-JP-2 value: java.nio.charset.CharsetICU[ISO-2022-JP-2]
+ * ISO-2022-KR value: java.nio.charset.CharsetICU[ISO-2022-KR]
+ * ISO-8859-1 value: java.nio.charset.CharsetICU[ISO-8859-1]
+ * ISO-8859-10 value: java.nio.charset.CharsetICU[ISO-8859-10]
+ * ISO-8859-13 value: java.nio.charset.CharsetICU[ISO-8859-13]
+ * ISO-8859-14 value: java.nio.charset.CharsetICU[ISO-8859-14]
+ * ISO-8859-15 value: java.nio.charset.CharsetICU[ISO-8859-15]
+ * ISO-8859-2 value: java.nio.charset.CharsetICU[ISO-8859-2]
+ * ISO-8859-3 value: java.nio.charset.CharsetICU[ISO-8859-3]
+ * ISO-8859-4 value: java.nio.charset.CharsetICU[ISO-8859-4]
+ * ISO-8859-5 value: java.nio.charset.CharsetICU[ISO-8859-5]
+ * ISO-8859-6 value: java.nio.charset.CharsetICU[ISO-8859-6]
+ * ISO-8859-7 value: java.nio.charset.CharsetICU[ISO-8859-7]
+ * ISO-8859-8 value: java.nio.charset.CharsetICU[ISO-8859-8]
+ * ISO-8859-9 value: java.nio.charset.CharsetICU[ISO-8859-9]
+ * KOI8-R value: java.nio.charset.CharsetICU[KOI8-R]
+ * KOI8-U value: java.nio.charset.CharsetICU[KOI8-U]
+ * macintosh value: java.nio.charset.CharsetICU[macintosh]
+ * SCSU value: java.nio.charset.CharsetICU[SCSU]
+ * Shift_JIS value: java.nio.charset.CharsetICU[Shift_JIS]
+ * TIS-620 value: java.nio.charset.CharsetICU[TIS-620]
+ * US-ASCII value: java.nio.charset.CharsetICU[US-ASCII]
+ * UTF-16 value: java.nio.charset.CharsetICU[UTF-16]
+ * UTF-16BE value: java.nio.charset.CharsetICU[UTF-16BE]
+ * UTF-16LE value: java.nio.charset.CharsetICU[UTF-16LE]
+ * UTF-32 value: java.nio.charset.CharsetICU[UTF-32]
+ * UTF-32BE value: java.nio.charset.CharsetICU[UTF-32BE]
+ * UTF-32LE value: java.nio.charset.CharsetICU[UTF-32LE]
+ * UTF-7 value: java.nio.charset.CharsetICU[UTF-7]
+ * UTF-8 value: java.nio.charset.CharsetICU[UTF-8]
+ * windows-1250 value: java.nio.charset.CharsetICU[windows-1250]
+ * windows-1251 value: java.nio.charset.CharsetICU[windows-1251]
+ * windows-1252 value: java.nio.charset.CharsetICU[windows-1252]
+ * windows-1253 value: java.nio.charset.CharsetICU[windows-1253]
+ * windows-1254 value: java.nio.charset.CharsetICU[windows-1254]
+ * windows-1255 value: java.nio.charset.CharsetICU[windows-1255]
+ * windows-1256 value: java.nio.charset.CharsetICU[windows-1256]
+ * windows-1257 value: java.nio.charset.CharsetICU[windows-1257]
+ * windows-1258 value: java.nio.charset.CharsetICU[windows-1258]
+ * x-compound-text value: java.nio.charset.CharsetICU[x-compound-text]
+ * x-ebcdic-xml-us value: java.nio.charset.CharsetICU[x-ebcdic-xml-us]
+ * x-gsm-03.38-2000 value: java.nio.charset.CharsetICU[x-gsm-03.38-2000]
+ * x-ibm-1047-s390 value: java.nio.charset.CharsetICU[x-ibm-1047-s390]
+ * x-ibm-1125_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1125_P100-1997]
+ * x-ibm-1129_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1129_P100-1997]
+ * x-ibm-1130_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1130_P100-1997]
+ * x-ibm-1131_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1131_P100-1997]
+ * x-ibm-1132_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-1132_P100-1998]
+ * x-ibm-1133_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1133_P100-1997]
+ * x-ibm-1137_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1137_P100-1999]
+ * x-ibm-1140-s390 value: java.nio.charset.CharsetICU[x-ibm-1140-s390]
+ * x-ibm-1141-s390 value: java.nio.charset.CharsetICU[x-ibm-1141-s390]
+ * x-ibm-1142-s390 value: java.nio.charset.CharsetICU[x-ibm-1142-s390]
+ * x-ibm-1143-s390 value: java.nio.charset.CharsetICU[x-ibm-1143-s390]
+ * x-ibm-1144-s390 value: java.nio.charset.CharsetICU[x-ibm-1144-s390]
+ * x-ibm-1145-s390 value: java.nio.charset.CharsetICU[x-ibm-1145-s390]
+ * x-ibm-1146-s390 value: java.nio.charset.CharsetICU[x-ibm-1146-s390]
+ * x-ibm-1147-s390 value: java.nio.charset.CharsetICU[x-ibm-1147-s390]
+ * x-ibm-1148-s390 value: java.nio.charset.CharsetICU[x-ibm-1148-s390]
+ * x-ibm-1149-s390 value: java.nio.charset.CharsetICU[x-ibm-1149-s390]
+ * x-ibm-1153-s390 value: java.nio.charset.CharsetICU[x-ibm-1153-s390]
+ * x-ibm-1154_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1154_P100-1999]
+ * x-ibm-1155_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1155_P100-1999]
+ * x-ibm-1156_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1156_P100-1999]
+ * x-ibm-1157_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1157_P100-1999]
+ * x-ibm-1158_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1158_P100-1999]
+ * x-ibm-1160_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1160_P100-1999]
+ * x-ibm-1162_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1162_P100-1999]
+ * x-ibm-1164_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-1164_P100-1999]
+ * x-ibm-1250_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1250_P100-1995]
+ * x-ibm-1251_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1251_P100-1995]
+ * x-ibm-1252_P100-2000 value: java.nio.charset.CharsetICU[x-ibm-1252_P100-2000]
+ * x-ibm-1253_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1253_P100-1995]
+ * x-ibm-1254_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1254_P100-1995]
+ * x-ibm-1255_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1255_P100-1995]
+ * x-ibm-1256_P110-1997 value: java.nio.charset.CharsetICU[x-ibm-1256_P110-1997]
+ * x-ibm-1257_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-1257_P100-1995]
+ * x-ibm-1258_P100-1997 value: java.nio.charset.CharsetICU[x-ibm-1258_P100-1997]
+ * x-ibm-12712-s390 value: java.nio.charset.CharsetICU[x-ibm-12712-s390]
+ * x-ibm-12712_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-12712_P100-1998]
+ * x-ibm-1373_P100-2002 value: java.nio.charset.CharsetICU[x-ibm-1373_P100-2002]
+ * x-ibm-1383_P110-1999 value: java.nio.charset.CharsetICU[x-ibm-1383_P110-1999]
+ * x-ibm-1386_P100-2001 value: java.nio.charset.CharsetICU[x-ibm-1386_P100-2001]
+ * x-ibm-16684_P110-2003 value: java.nio.charset.CharsetICU[x-ibm-16684_P110-2003]
+ * x-ibm-16804-s390 value: java.nio.charset.CharsetICU[x-ibm-16804-s390]
+ * x-ibm-16804_X110-1999 value: java.nio.charset.CharsetICU[x-ibm-16804_X110-1999]
+ * x-ibm-25546 value: java.nio.charset.CharsetICU[x-ibm-25546]
+ * x-ibm-33722_P12A_P12A-2009_U2 value:
+ * java.nio.charset.CharsetICU[x-ibm-33722_P12A_P12A-2009_U2]
+ * x-ibm-37-s390 value: java.nio.charset.CharsetICU[x-ibm-37-s390]
+ * x-ibm-4517_P100-2005 value: java.nio.charset.CharsetICU[x-ibm-4517_P100-2005]
+ * x-ibm-4899_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-4899_P100-1998]
+ * x-ibm-4909_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-4909_P100-1999]
+ * x-ibm-4971_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-4971_P100-1999]
+ * x-ibm-5123_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-5123_P100-1999]
+ * x-ibm-5351_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5351_P100-1998]
+ * x-ibm-5352_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5352_P100-1998]
+ * x-ibm-5353_P100-1998 value: java.nio.charset.CharsetICU[x-ibm-5353_P100-1998]
+ * x-ibm-5478_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-5478_P100-1995]
+ * x-ibm-803_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-803_P100-1999]
+ * x-ibm-813_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-813_P100-1995]
+ * x-ibm-8482_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-8482_P100-1999]
+ * x-ibm-901_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-901_P100-1999]
+ * x-ibm-902_P100-1999 value: java.nio.charset.CharsetICU[x-ibm-902_P100-1999]
+ * x-ibm-9067_X100-2005 value: java.nio.charset.CharsetICU[x-ibm-9067_X100-2005]
+ * x-ibm-916_P100-1995 value: java.nio.charset.CharsetICU[x-ibm-916_P100-1995]
+ * x-IBM1006 value: java.nio.charset.CharsetICU[x-IBM1006]
+ * x-IBM1025 value: java.nio.charset.CharsetICU[x-IBM1025]
+ * x-IBM1097 value: java.nio.charset.CharsetICU[x-IBM1097]
+ * x-IBM1098 value: java.nio.charset.CharsetICU[x-IBM1098]
+ * x-IBM1112 value: java.nio.charset.CharsetICU[x-IBM1112]
+ * x-IBM1122 value: java.nio.charset.CharsetICU[x-IBM1122]
+ * x-IBM1123 value: java.nio.charset.CharsetICU[x-IBM1123]
+ * x-IBM1124 value: java.nio.charset.CharsetICU[x-IBM1124]
+ * x-IBM1153 value: java.nio.charset.CharsetICU[x-IBM1153]
+ * x-IBM1363 value: java.nio.charset.CharsetICU[x-IBM1363]
+ * x-IBM1364 value: java.nio.charset.CharsetICU[x-IBM1364]
+ * x-IBM1371 value: java.nio.charset.CharsetICU[x-IBM1371]
+ * x-IBM1388 value: java.nio.charset.CharsetICU[x-IBM1388]
+ * x-IBM1390 value: java.nio.charset.CharsetICU[x-IBM1390]
+ * x-IBM1399 value: java.nio.charset.CharsetICU[x-IBM1399]
+ * x-IBM33722 value: java.nio.charset.CharsetICU[x-IBM33722]
+ * x-IBM720 value: java.nio.charset.CharsetICU[x-IBM720]
+ * x-IBM737 value: java.nio.charset.CharsetICU[x-IBM737]
+ * x-IBM856 value: java.nio.charset.CharsetICU[x-IBM856]
+ * x-IBM867 value: java.nio.charset.CharsetICU[x-IBM867]
+ * x-IBM875 value: java.nio.charset.CharsetICU[x-IBM875]
+ * x-IBM922 value: java.nio.charset.CharsetICU[x-IBM922]
+ * x-IBM930 value: java.nio.charset.CharsetICU[x-IBM930]
+ * x-IBM933 value: java.nio.charset.CharsetICU[x-IBM933]
+ * x-IBM935 value: java.nio.charset.CharsetICU[x-IBM935]
+ * x-IBM937 value: java.nio.charset.CharsetICU[x-IBM937]
+ * x-IBM939 value: java.nio.charset.CharsetICU[x-IBM939]
+ * x-IBM942 value: java.nio.charset.CharsetICU[x-IBM942]
+ * x-IBM943 value: java.nio.charset.CharsetICU[x-IBM943]
+ * x-IBM949 value: java.nio.charset.CharsetICU[x-IBM949]
+ * x-IBM949C value: java.nio.charset.CharsetICU[x-IBM949C]
+ * x-IBM950 value: java.nio.charset.CharsetICU[x-IBM950]
+ * x-IBM954 value: java.nio.charset.CharsetICU[x-IBM954]
+ * x-IBM964 value: java.nio.charset.CharsetICU[x-IBM964]
+ * x-IBM970 value: java.nio.charset.CharsetICU[x-IBM970]
+ * x-IBM971 value: java.nio.charset.CharsetICU[x-IBM971]
+ * x-IMAP-mailbox-name value: java.nio.charset.CharsetICU[x-IMAP-mailbox-name]
+ * x-iscii-be value: java.nio.charset.CharsetICU[x-iscii-be]
+ * x-iscii-gu value: java.nio.charset.CharsetICU[x-iscii-gu]
+ * x-iscii-ka value: java.nio.charset.CharsetICU[x-iscii-ka]
+ * x-iscii-ma value: java.nio.charset.CharsetICU[x-iscii-ma]
+ * x-iscii-or value: java.nio.charset.CharsetICU[x-iscii-or]
+ * x-iscii-pa value: java.nio.charset.CharsetICU[x-iscii-pa]
+ * x-iscii-ta value: java.nio.charset.CharsetICU[x-iscii-ta]
+ * x-iscii-te value: java.nio.charset.CharsetICU[x-iscii-te]
+ * x-ISCII91 value: java.nio.charset.CharsetICU[x-ISCII91]
+ * x-ISO-2022-CN-CNS value: java.nio.charset.CharsetICU[x-ISO-2022-CN-CNS]
+ * x-iso-8859-11 value: java.nio.charset.CharsetICU[x-iso-8859-11]
+ * x-JavaUnicode value: java.nio.charset.CharsetICU[x-JavaUnicode]
+ * x-JavaUnicode2 value: java.nio.charset.CharsetICU[x-JavaUnicode2]
+ * x-JIS7 value: java.nio.charset.CharsetICU[x-JIS7]
+ * x-JIS8 value: java.nio.charset.CharsetICU[x-JIS8]
+ * x-LMBCS-1 value: java.nio.charset.CharsetICU[x-LMBCS-1]
+ * x-mac-centraleurroman value: java.nio.charset.CharsetICU[x-mac-centraleurroman]
+ * x-mac-cyrillic value: java.nio.charset.CharsetICU[x-mac-cyrillic]
+ * x-mac-greek value: java.nio.charset.CharsetICU[x-mac-greek]
+ * x-mac-turkish value: java.nio.charset.CharsetICU[x-mac-turkish]
+ * x-MS950-HKSCS value: java.nio.charset.CharsetICU[x-MS950-HKSCS]
+ * x-UnicodeBig value: java.nio.charset.CharsetICU[x-UnicodeBig]
+ * x-UTF-16LE-BOM value: java.nio.charset.CharsetICU[x-UTF-16LE-BOM]
+ * x-UTF16_OppositeEndian value: java.nio.charset.CharsetICU[x-UTF16_OppositeEndian]
+ * x-UTF16_PlatformEndian value: java.nio.charset.CharsetICU[x-UTF16_PlatformEndian]
+ * x-UTF32_OppositeEndian value: java.nio.charset.CharsetICU[x-UTF32_OppositeEndian]
+ * x-UTF32_PlatformEndian value: java.nio.charset.CharsetICU[x-UTF32_PlatformEndian]
+ *
+ */
+ public static final int ANY_CHARSET = 0x00;
+ public static final int US_ASCII = 0x03;
+ public static final int ISO_8859_1 = 0x04;
+ public static final int ISO_8859_2 = 0x05;
+ public static final int ISO_8859_3 = 0x06;
+ public static final int ISO_8859_4 = 0x07;
+ public static final int ISO_8859_5 = 0x08;
+ public static final int ISO_8859_6 = 0x09;
+ public static final int ISO_8859_7 = 0x0A;
+ public static final int ISO_8859_8 = 0x0B;
+ public static final int ISO_8859_9 = 0x0C;
+ public static final int SHIFT_JIS = 0x11;
+ public static final int EUC_JP = 0x12;
+ public static final int EUC_KR = 0x26;
+ public static final int ISO_2022_JP = 0x27;
+ public static final int ISO_2022_JP_2 = 0x28;
+ public static final int UTF_8 = 0x6A;
+ public static final int GBK = 0x71;
+ public static final int GB18030 = 0x72;
+ public static final int GB2312 = 0x07E9;
+ public static final int BIG5 = 0x07EA;
+ public static final int UCS2 = 0x03E8;
+ public static final int UTF_16 = 0x03F7;
+ public static final int HZ_GB_2312 = 0x0825;
+
+ /**
+ * If the encoding of given data is unsupported, use UTF_8 to decode it.
+ */
+ public static final int DEFAULT_CHARSET = UTF_8;
+
+ /**
+ * Array of MIB enum numbers.
+ */
+ private static final int[] MIBENUM_NUMBERS = {
+ ANY_CHARSET,
+ US_ASCII,
+ ISO_8859_1,
+ ISO_8859_2,
+ ISO_8859_3,
+ ISO_8859_4,
+ ISO_8859_5,
+ ISO_8859_6,
+ ISO_8859_7,
+ ISO_8859_8,
+ ISO_8859_9,
+ SHIFT_JIS,
+ EUC_JP,
+ EUC_KR,
+ ISO_2022_JP,
+ ISO_2022_JP_2,
+ UTF_8,
+ GBK,
+ GB18030,
+ GB2312,
+ BIG5,
+ UCS2,
+ UTF_16,
+ HZ_GB_2312,
+ };
+
+ /**
+ * The Well-known-charset Mime name.
+ */
+ public static final String MIMENAME_ANY_CHARSET = "*";
+ public static final String MIMENAME_US_ASCII = "us-ascii";
+ public static final String MIMENAME_ISO_8859_1 = "iso-8859-1";
+ public static final String MIMENAME_ISO_8859_2 = "iso-8859-2";
+ public static final String MIMENAME_ISO_8859_3 = "iso-8859-3";
+ public static final String MIMENAME_ISO_8859_4 = "iso-8859-4";
+ public static final String MIMENAME_ISO_8859_5 = "iso-8859-5";
+ public static final String MIMENAME_ISO_8859_6 = "iso-8859-6";
+ public static final String MIMENAME_ISO_8859_7 = "iso-8859-7";
+ public static final String MIMENAME_ISO_8859_8 = "iso-8859-8";
+ public static final String MIMENAME_ISO_8859_9 = "iso-8859-9";
+ public static final String MIMENAME_SHIFT_JIS = "shift_JIS";
+ public static final String MIMENAME_EUC_JP = "euc-jp";
+ public static final String MIMENAME_EUC_KR = "euc-kr";
+ public static final String MIMENAME_ISO_2022_JP = "iso-2022-jp";
+ public static final String MIMENAME_ISO_2022_JP_2 = "iso-2022-jp-2";
+ public static final String MIMENAME_UTF_8 = "utf-8";
+ public static final String MIMENAME_GBK = "gbk";
+ public static final String MIMENAME_GB18030 = "gb18030";
+ public static final String MIMENAME_GB2312 = "gb2312";
+ public static final String MIMENAME_BIG5 = "big5";
+ public static final String MIMENAME_UCS2 = "iso-10646-ucs-2";
+ public static final String MIMENAME_UTF_16 = "utf-16";
+ public static final String MIMENAME_HZ_GB_2312 = "hz-gb-2312";
+
+ public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+ /**
+ * Array of the names of character sets.
+ */
+ private static final String[] MIME_NAMES = {
+ MIMENAME_ANY_CHARSET,
+ MIMENAME_US_ASCII,
+ MIMENAME_ISO_8859_1,
+ MIMENAME_ISO_8859_2,
+ MIMENAME_ISO_8859_3,
+ MIMENAME_ISO_8859_4,
+ MIMENAME_ISO_8859_5,
+ MIMENAME_ISO_8859_6,
+ MIMENAME_ISO_8859_7,
+ MIMENAME_ISO_8859_8,
+ MIMENAME_ISO_8859_9,
+ MIMENAME_SHIFT_JIS,
+ MIMENAME_EUC_JP,
+ MIMENAME_EUC_KR,
+ MIMENAME_ISO_2022_JP,
+ MIMENAME_ISO_2022_JP_2,
+ MIMENAME_UTF_8,
+ MIMENAME_GBK,
+ MIMENAME_GB18030,
+ MIMENAME_GB2312,
+ MIMENAME_BIG5,
+ MIMENAME_UCS2,
+ MIMENAME_UTF_16,
+ MIMENAME_HZ_GB_2312,
+ };
+
+ private static final SparseArray<String> MIBENUM_TO_NAME_MAP;
+
+ private static final SimpleArrayMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+ static {
+ // Create the HashMaps.
+ MIBENUM_TO_NAME_MAP = new SparseArray<String>();
+ NAME_TO_MIBENUM_MAP = new SimpleArrayMap<String, Integer>();
+ assert (MIBENUM_NUMBERS.length == MIME_NAMES.length);
+ final int count = MIBENUM_NUMBERS.length - 1;
+ for (int i = 0; i <= count; i++) {
+ MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+ NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+ }
+ }
+
+ private CharacterSets() {
+ } // Non-instantiatable
+
+ /**
+ * Map an MIBEnum number to the name of the charset which this number
+ * is assigned to by IANA.
+ *
+ * @param mibEnumValue An IANA assigned MIBEnum number.
+ * @return The name string of the charset.
+ */
+ public static String getMimeName(final int mibEnumValue)
+ throws UnsupportedEncodingException {
+ final String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+ if (name == null) {
+ throw new UnsupportedEncodingException();
+ }
+ return name;
+ }
+
+ /**
+ * Map a well-known charset name to its assigned MIBEnum number.
+ *
+ * @param mimeName The charset name.
+ * @return The MIBEnum number assigned by IANA for this charset.
+ */
+ public static int getMibEnumValue(final String mimeName)
+ throws UnsupportedEncodingException {
+ if (null == mimeName) {
+ return -1;
+ }
+
+ final Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+ if (mibEnumValue == null) {
+ throw new UnsupportedEncodingException();
+ }
+ return mibEnumValue;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/DeliveryInd.java b/src/com/android/messaging/mmslib/pdu/DeliveryInd.java
new file mode 100644
index 0000000..5e1d873
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/DeliveryInd.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public DeliveryInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ DeliveryInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value, should not be null
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get Status value.
+ *
+ * @return the value
+ */
+ public int getStatus() {
+ return mPduHeaders.getOctet(PduHeaders.STATUS);
+ }
+
+ /**
+ * Set Status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.STATUS);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public EncodedStringValue getStatusText() {return null;}
+ * public void setStatusText(EncodedStringValue value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java b/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java
new file mode 100644
index 0000000..1d3c9ea
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/EncodedStringValue.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.mmslib.pdu;
+
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+ private static final String TAG = "EncodedStringValue";
+ private static final boolean DEBUG = false;
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * The Char-set value.
+ */
+ private int mCharacterSet;
+
+ /**
+ * The Text-string value.
+ */
+ private byte[] mData;
+
+ /**
+ * Constructor.
+ *
+ * @param charset the Char-set value
+ * @param data the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public EncodedStringValue(int charset, byte[] data) {
+ // TODO: CharSet needs to be validated against MIBEnum.
+ if (null == data) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null.");
+ }
+
+ mCharacterSet = charset;
+ mData = new byte[data.length];
+ System.arraycopy(data, 0, mData, 0, data.length);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param data the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public EncodedStringValue(byte[] data) {
+ this(CharacterSets.DEFAULT_CHARSET, data);
+ }
+
+ public EncodedStringValue(String data) {
+ this(CharacterSets.DEFAULT_CHARSET, data);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param charset
+ * @param data The text in Java String
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public EncodedStringValue(int charset, String data) {
+ if (null == data) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null");
+ }
+ mCharacterSet = charset;
+ try {
+ mData = data.getBytes(CharacterSets.getMimeName(charset));
+ } catch (UnsupportedEncodingException e) {
+ Log.e(TAG, "Input encoding " + charset + " must be supported.", e);
+ mData = data.getBytes();
+ }
+ }
+
+ /**
+ * Get Char-set value.
+ *
+ * @return the value
+ */
+ public int getCharacterSet() {
+ return mCharacterSet;
+ }
+
+ /**
+ * Set Char-set value.
+ *
+ * @param charset the Char-set value
+ */
+ public void setCharacterSet(int charset) {
+ // TODO: CharSet needs to be validated against MIBEnum.
+ mCharacterSet = charset;
+ }
+
+ /**
+ * Get Text-string value.
+ *
+ * @return the value
+ */
+ public byte[] getTextString() {
+ byte[] byteArray = new byte[mData.length];
+
+ System.arraycopy(mData, 0, byteArray, 0, mData.length);
+ return byteArray;
+ }
+
+ /**
+ * Set Text-string value.
+ *
+ * @param textString the Text-string value
+ * @throws NullPointerException if Text-string value is null.
+ */
+ public void setTextString(byte[] textString) {
+ if (null == textString) {
+ throw new NullPointerException("EncodedStringValue: Text-string is null.");
+ }
+
+ mData = new byte[textString.length];
+ System.arraycopy(textString, 0, mData, 0, textString.length);
+ }
+
+ /**
+ * Convert this object to a {@link java.lang.String}. If the encoding of
+ * the EncodedStringValue is null or unsupported, it will be
+ * treated as iso-8859-1 encoding.
+ *
+ * @return The decoded String.
+ */
+ public String getString() {
+ if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+ return new String(mData); // system default encoding.
+ } else {
+ try {
+ String name = CharacterSets.getMimeName(mCharacterSet);
+ return new String(mData, name);
+ } catch (UnsupportedEncodingException e) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, e.getMessage(), e);
+ }
+ try {
+ return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (UnsupportedEncodingException exception) {
+ return new String(mData); // system default encoding.
+ }
+ }
+ }
+ }
+
+ /**
+ * Append to Text-string.
+ *
+ * @param textString the textString to append
+ * @throws NullPointerException if the text String is null
+ * or an IOException occured.
+ */
+ public void appendTextString(byte[] textString) {
+ if (null == textString) {
+ throw new NullPointerException("Text-string is null.");
+ }
+
+ if (null == mData) {
+ mData = new byte[textString.length];
+ System.arraycopy(textString, 0, mData, 0, textString.length);
+ } else {
+ ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+ try {
+ newTextString.write(mData);
+ newTextString.write(textString);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new NullPointerException(
+ "appendTextString: failed when write a new Text-string");
+ }
+
+ mData = newTextString.toByteArray();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ super.clone();
+ int len = mData.length;
+ byte[] dstBytes = new byte[len];
+ System.arraycopy(mData, 0, dstBytes, 0, len);
+
+ try {
+ return new EncodedStringValue(mCharacterSet, dstBytes);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+ e.printStackTrace();
+ throw new CloneNotSupportedException(e.getMessage());
+ }
+ }
+
+ /**
+ * Split this encoded string around matches of the given pattern.
+ *
+ * @param pattern the delimiting pattern
+ * @return the array of encoded strings computed by splitting this encoded
+ * string around matches of the given pattern
+ */
+ public EncodedStringValue[] split(String pattern) {
+ String[] temp = getString().split(pattern);
+ EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+ for (int i = 0; i < ret.length; ++i) {
+ try {
+ ret[i] = new EncodedStringValue(mCharacterSet,
+ temp[i].getBytes());
+ } catch (NullPointerException exception) {
+ // Can't arrive here
+ return null;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Extract an EncodedStringValue[] from a given String.
+ */
+ public static EncodedStringValue[] extract(String src) {
+ String[] values = src.split(";");
+
+ ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].length() > 0) {
+ list.add(new EncodedStringValue(values[i]));
+ }
+ }
+
+ int len = list.size();
+ if (len > 0) {
+ return list.toArray(new EncodedStringValue[len]);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Concatenate an EncodedStringValue[] into a single String.
+ */
+ public static String concat(EncodedStringValue[] addr) {
+ StringBuilder sb = new StringBuilder();
+ int maxIndex = addr.length - 1;
+ for (int i = 0; i <= maxIndex; i++) {
+ sb.append(addr[i].getString());
+ if (i < maxIndex) {
+ sb.append(";");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static EncodedStringValue copy(EncodedStringValue value) {
+ if (value == null) {
+ return null;
+ }
+
+ return new EncodedStringValue(value.mCharacterSet, value.mData);
+ }
+
+ public static EncodedStringValue[] encodeStrings(String[] array) {
+ int count = array.length;
+ if (count > 0) {
+ EncodedStringValue[] encodedArray = new EncodedStringValue[count];
+ for (int i = 0; i < count; i++) {
+ encodedArray[i] = new EncodedStringValue(array[i]);
+ }
+ return encodedArray;
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/GenericPdu.java b/src/com/android/messaging/mmslib/pdu/GenericPdu.java
new file mode 100644
index 0000000..178dc5a
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/GenericPdu.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+public class GenericPdu {
+ /**
+ * The headers of pdu.
+ */
+ PduHeaders mPduHeaders = null;
+
+ /**
+ * Constructor.
+ */
+ public GenericPdu() {
+ mPduHeaders = new PduHeaders();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param headers Headers for this PDU.
+ */
+ GenericPdu(PduHeaders headers) {
+ mPduHeaders = headers;
+ }
+
+ /**
+ * Get the headers of this PDU.
+ *
+ * @return A PduHeaders of this PDU.
+ */
+ PduHeaders getPduHeaders() {
+ return mPduHeaders;
+ }
+
+ /**
+ * Get X-Mms-Message-Type field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getMessageType() {
+ return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+ }
+
+ /**
+ * Set X-Mms-Message-Type field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * @throws RuntimeException if field's value is not Octet.
+ */
+ public void setMessageType(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+ }
+
+ /**
+ * Get X-Mms-MMS-Version field value.
+ *
+ * @return the X-Mms-MMS-Version value
+ */
+ public int getMmsVersion() {
+ return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+ }
+
+ /**
+ * Set X-Mms-MMS-Version field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * @throws RuntimeException if field's value is not Octet.
+ */
+ public void setMmsVersion(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java b/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..cb8c8c7
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu {
+ /**
+ * The body.
+ */
+ private PduBody mMessageBody;
+
+ /**
+ * Constructor.
+ */
+ public MultimediaMessagePdu() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param header the header of this PDU
+ * @param body the body of this PDU
+ */
+ public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+ super(header);
+ mMessageBody = body;
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ MultimediaMessagePdu(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get body of the PDU.
+ *
+ * @return the body
+ */
+ public PduBody getBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * Set body of the PDU.
+ *
+ * @param body the body
+ */
+ public void setBody(PduBody body) {
+ mMessageBody = body;
+ }
+
+ /**
+ * Get subject.
+ *
+ * @return the value
+ */
+ public EncodedStringValue getSubject() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Set subject.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setSubject(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Add a "To" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addTo(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-Mms-Priority value.
+ *
+ * @return the value
+ */
+ public int getPriority() {
+ return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+ }
+
+ /**
+ * Set X-Mms-Priority value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setPriority(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value in seconds.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get the message size
+ *
+ * @return the size of the message
+ */
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/NotificationInd.java b/src/com/android/messaging/mmslib/pdu/NotificationInd.java
new file mode 100644
index 0000000..4cbbd30
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/NotificationInd.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public NotificationInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ NotificationInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Content-Class Value.
+ *
+ * @return the value
+ */
+ public int getContentClass() {
+ return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Content-Class Value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setContentClass(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Content-Location value.
+ * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+ * Content-location-value = Uri-value
+ *
+ * @return the value
+ */
+ public byte[] getContentLocation() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+ }
+
+ /**
+ * Set X-Mms-Content-Location value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setContentLocation(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+ }
+
+ /**
+ * Get X-Mms-Expiry value.
+ *
+ * Expiry-value = Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ *
+ * @return the value
+ */
+ public long getExpiry() {
+ return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Set X-Mms-Expiry value.
+ *
+ * @param value the value
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setExpiry(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Message-Size value.
+ * Message-size-value = Long-integer
+ *
+ * @return the value
+ */
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Set X-Mms-Message-Size value.
+ *
+ * @param value the value
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setMessageSize(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Get subject.
+ *
+ * @return the value
+ */
+ public EncodedStringValue getSubject() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Set subject.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setSubject(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id.
+ *
+ * @return the value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report Value.
+ *
+ * @return the value
+ */
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report Value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public byte getDistributionIndicator() {return 0x00;}
+ * public void setDistributionIndicator(byte value) {}
+ *
+ * public ElementDescriptorValue getElementDescriptor() {return null;}
+ * public void getElementDescriptor(ElementDescriptorValue value) {}
+ *
+ * public byte getPriority() {return 0x00;}
+ * public void setPriority(byte value) {}
+ *
+ * public byte getRecommendedRetrievalMode() {return 0x00;}
+ * public void setRecommendedRetrievalMode(byte value) {}
+ *
+ * public byte getRecommendedRetrievalModeText() {return 0x00;}
+ * public void setRecommendedRetrievalModeText(byte value) {}
+ *
+ * public byte[] getReplaceId() {return 0x00;}
+ * public void setReplaceId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ *
+ * public byte getStored() {return 0x00;}
+ * public void setStored(byte value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java b/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..7dabd89
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/NotifyRespInd.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-NotifyResp.ind pdu.
+ *
+ * @param mmsVersion current version of mms
+ * @param transactionId the transaction-id value
+ * @param status the status value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * @throws NullPointerException if transactionId is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public NotifyRespInd(int mmsVersion,
+ byte[] transactionId,
+ int status) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ setStatus(status);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ NotifyRespInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get X-Mms-Report-Allowed field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public int getReportAllowed() {
+ return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Report-Allowed field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setReportAllowed(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+ }
+
+ /**
+ * Set X-Mms-Status field value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.STATUS);
+ }
+
+ /**
+ * GetX-Mms-Status field value.
+ *
+ * @return the X-Mms-Status value
+ */
+ public int getStatus() {
+ return mPduHeaders.getOctet(PduHeaders.STATUS);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ * @throws RuntimeException if an undeclared error occurs.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/PduBody.java b/src/com/android/messaging/mmslib/pdu/PduBody.java
new file mode 100644
index 0000000..8cbb2e6
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduBody.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import java.util.Vector;
+
+public class PduBody {
+ private Vector<PduPart> mParts = null;
+
+ /**
+ * Constructor.
+ */
+ public PduBody() {
+ mParts = new Vector<PduPart>();
+ }
+
+ /**
+ * Appends the specified part to the end of this body.
+ *
+ * @param part part to be appended
+ * @return true when success, false when fail
+ * @throws NullPointerException when part is null
+ */
+ public boolean addPart(PduPart part) {
+ if (null == part) {
+ throw new NullPointerException();
+ }
+
+ return mParts.add(part);
+ }
+
+ /**
+ * Inserts the specified part at the specified position.
+ *
+ * @param index index at which the specified part is to be inserted
+ * @param part part to be inserted
+ * @throws NullPointerException when part is null
+ */
+ public void addPart(int index, PduPart part) {
+ if (null == part) {
+ throw new NullPointerException();
+ }
+
+ mParts.add(index, part);
+ }
+
+ /**
+ * Remove all of the parts.
+ */
+ public void removeAll() {
+ mParts.clear();
+ }
+
+ /**
+ * Get the part at the specified position.
+ *
+ * @param index index of the part to return
+ * @return part at the specified index
+ */
+ public PduPart getPart(int index) {
+ return mParts.get(index);
+ }
+
+ /**
+ * Get the number of parts.
+ *
+ * @return the number of parts
+ */
+ public int getPartsNum() {
+ return mParts.size();
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/PduComposer.java b/src/com/android/messaging/mmslib/pdu/PduComposer.java
new file mode 100644
index 0000000..d05a198
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduComposer.java
@@ -0,0 +1,1260 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.mmslib.pdu;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.support.v4.util.SimpleArrayMap;
+import android.text.TextUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+public class PduComposer {
+ /**
+ * Address type.
+ */
+ private static final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
+ private static final int PDU_EMAIL_ADDRESS_TYPE = 2;
+ private static final int PDU_IPV4_ADDRESS_TYPE = 3;
+ private static final int PDU_IPV6_ADDRESS_TYPE = 4;
+ private static final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
+
+ /**
+ * Address regular expression string.
+ */
+ static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
+
+ static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
+ "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
+
+ static final String REGEXP_IPV6_ADDRESS_TYPE =
+ "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+ "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+ "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
+
+ static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
+ "[0-9]{1,3}\\.{1}[0-9]{1,3}";
+
+ /**
+ * The postfix strings of address.
+ */
+ static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
+ static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
+ static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
+
+ /**
+ * Error values.
+ */
+ private static final int PDU_COMPOSE_SUCCESS = 0;
+ private static final int PDU_COMPOSE_CONTENT_ERROR = 1;
+ private static final int PDU_COMPOSE_FIELD_NOT_SET = 2;
+ private static final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
+
+ /**
+ * WAP values defined in WSP spec.
+ */
+ private static final int QUOTED_STRING_FLAG = 34;
+ private static final int END_STRING_FLAG = 0;
+ private static final int LENGTH_QUOTE = 31;
+ private static final int TEXT_MAX = 127;
+ private static final int SHORT_INTEGER_MAX = 127;
+ private static final int LONG_INTEGER_LENGTH_MAX = 8;
+
+ /**
+ * Block size when read data from InputStream.
+ */
+ private static final int PDU_COMPOSER_BLOCK_SIZE = 1024;
+
+ /**
+ * The output message.
+ */
+ protected ByteArrayOutputStream mMessage = null;
+
+ /**
+ * The PDU.
+ */
+ private GenericPdu mPdu = null;
+
+ /**
+ * Current visiting position of the mMessage.
+ */
+ protected int mPosition = 0;
+
+ /**
+ * Message compose buffer stack.
+ */
+ private BufferStack mStack = null;
+
+ /**
+ * Content resolver.
+ */
+ private final ContentResolver mResolver;
+
+ /**
+ * Header of this pdu.
+ */
+ private PduHeaders mPduHeader = null;
+
+ /**
+ * Map of all content type
+ */
+ private static SimpleArrayMap<String, Integer> mContentTypeMap = null;
+
+ static {
+ mContentTypeMap = new SimpleArrayMap<String, Integer>();
+
+ int i;
+ for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
+ mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context the context
+ * @param pdu the pdu to be composed
+ */
+ public PduComposer(final Context context, final GenericPdu pdu) {
+ mPdu = pdu;
+ mResolver = context.getContentResolver();
+ mPduHeader = pdu.getPduHeaders();
+ mStack = new BufferStack();
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ /**
+ * Make the message. No need to check whether mandatory fields are set,
+ * because the constructors of outgoing pdus are taking care of this.
+ *
+ * @return OutputStream of maked message. Return null if
+ * the PDU is invalid.
+ */
+ public byte[] make() {
+ // Get Message-type.
+ final int type = mPdu.getMessageType();
+
+ /* make the message */
+ switch (type) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ if (makeNotificationInd() != PDU_COMPOSE_SUCCESS) {
+ return null;
+ }
+ break;
+ default:
+ return null;
+ }
+
+ return mMessage.toByteArray();
+ }
+
+ /**
+ * Copy buf to mMessage.
+ */
+ protected void arraycopy(final byte[] buf, final int pos, final int length) {
+ mMessage.write(buf, pos, length);
+ mPosition = mPosition + length;
+ }
+
+ /**
+ * Append a byte to mMessage.
+ */
+ protected void append(final int value) {
+ mMessage.write(value);
+ mPosition++;
+ }
+
+ /**
+ * Append short integer value to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendShortInteger(final int value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Short-integer = OCTET
+ * ; Integers in range 0-127 shall be encoded as a one octet value
+ * ; with the most significant bit set to one (1xxx xxxx) and with
+ * ; the value in the remaining least significant bits.
+ * In our implementation, only low 7 bits are stored and otherwise
+ * bits are ignored.
+ */
+ append((value | 0x80) & 0xff);
+ }
+
+ /**
+ * Append an octet number between 128 and 255 into mMessage.
+ * NOTE:
+ * A value between 0 and 127 should be appended by using appendShortInteger.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendOctet(final int number) {
+ append(number);
+ }
+
+ /**
+ * Append a short length into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendShortLength(final int value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Short-length = <Any octet 0-30>
+ */
+ append(value);
+ }
+
+ /**
+ * Append long integer into mMessage. it's used for really long integers.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendLongInteger(final long longInt) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Long-integer = Short-length Multi-octet-integer
+ * ; The Short-length indicates the length of the Multi-octet-integer
+ * Multi-octet-integer = 1*30 OCTET
+ * ; The content octets shall be an unsigned integer value with the
+ * ; most significant octet encoded first (big-endian representation).
+ * ; The minimum number of octets must be used to encode the value.
+ */
+ int size;
+ long temp = longInt;
+
+ // Count the length of the long integer.
+ for (size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
+ temp = (temp >>> 8);
+ }
+
+ // Set Length.
+ appendShortLength(size);
+
+ // Count and set the long integer.
+ int i;
+ int shift = (size - 1) * 8;
+
+ for (i = 0; i < size; i++) {
+ append((int) ((longInt >>> shift) & 0xff));
+ shift = shift - 8;
+ }
+ }
+
+ /**
+ * Append text string into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendTextString(final byte[] text) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Text-string = [Quote] *TEXT End-of-string
+ * ; If the first character in the TEXT is in the range of 128-255,
+ * ; a Quote character must precede it. Otherwise the Quote character
+ * ;must be omitted. The Quote is not part of the contents.
+ */
+ if (((text[0]) & 0xff) > TEXT_MAX) { // No need to check for <= 255
+ append(TEXT_MAX);
+ }
+
+ arraycopy(text, 0, text.length);
+ append(0);
+ }
+
+ /**
+ * Append text string into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendTextString(final String str) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Text-string = [Quote] *TEXT End-of-string
+ * ; If the first character in the TEXT is in the range of 128-255,
+ * ; a Quote character must precede it. Otherwise the Quote character
+ * ;must be omitted. The Quote is not part of the contents.
+ */
+ appendTextString(str.getBytes());
+ }
+
+ /**
+ * Append encoded string value to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendEncodedString(final EncodedStringValue enStr) {
+ /*
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+ assert (enStr != null);
+
+ final int charset = enStr.getCharacterSet();
+ final byte[] textString = enStr.getTextString();
+ if (null == textString) {
+ return;
+ }
+
+ /*
+ * In the implementation of EncodedStringValue, the charset field will
+ * never be 0. It will always be composed as
+ * Encoded-string-value = Value-length Char-set Text-string
+ */
+ mStack.newbuf();
+ final PositionMarker start = mStack.mark();
+
+ appendShortInteger(charset);
+ appendTextString(textString);
+
+ final int len = start.getLength();
+ mStack.pop();
+ appendValueLength(len);
+ mStack.copy();
+ }
+
+ /**
+ * Append uintvar integer into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendUintvarInteger(final long value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * To encode a large unsigned integer, split it into 7-bit fragments
+ * and place them in the payloads of multiple octets. The most significant
+ * bits are placed in the first octets with the least significant bits
+ * ending up in the last octet. All octets MUST set the Continue bit to 1
+ * except the last octet, which MUST set the Continue bit to 0.
+ */
+ int i;
+ long max = SHORT_INTEGER_MAX;
+
+ for (i = 0; i < 5; i++) {
+ if (value < max) {
+ break;
+ }
+
+ max = (max << 7) | 0x7fL;
+ }
+
+ while (i > 0) {
+ long temp = value >>> (i * 7);
+ temp = temp & 0x7f;
+
+ append((int) ((temp | 0x80) & 0xff));
+
+ i--;
+ }
+
+ append((int) (value & 0x7f));
+ }
+
+ /**
+ * Append date value into mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendDateValue(final long date) {
+ /*
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+ * Date-value = Long-integer
+ */
+ appendLongInteger(date);
+ }
+
+ /**
+ * Append value length to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendValueLength(final long value) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Value-length = Short-length | (Length-quote Length)
+ * ; Value length is used to indicate the length of the value to follow
+ * Short-length = <Any octet 0-30>
+ * Length-quote = <Octet 31>
+ * Length = Uintvar-integer
+ */
+ if (value < LENGTH_QUOTE) {
+ appendShortLength((int) value);
+ return;
+ }
+
+ append(LENGTH_QUOTE);
+ appendUintvarInteger(value);
+ }
+
+ /**
+ * Append quoted string to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendQuotedString(final byte[] text) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+ * ;quotation-marks <"> removed.
+ */
+ append(QUOTED_STRING_FLAG);
+ arraycopy(text, 0, text.length);
+ append(END_STRING_FLAG);
+ }
+
+ /**
+ * Append quoted string to mMessage.
+ * This implementation doesn't check the validity of parameter, since it
+ * assumes that the values are validated in the GenericPdu setter methods.
+ */
+ protected void appendQuotedString(final String str) {
+ /*
+ * From WAP-230-WSP-20010705-a:
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+ * ;quotation-marks <"> removed.
+ */
+ appendQuotedString(str.getBytes());
+ }
+
+ private EncodedStringValue appendAddressType(final EncodedStringValue address) {
+ EncodedStringValue temp = null;
+
+ try {
+ final int addressType = checkAddressType(address.getString());
+ temp = EncodedStringValue.copy(address);
+ if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
+ // Phone number.
+ temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
+ } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
+ // Ipv4 address.
+ temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
+ } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
+ // Ipv6 address.
+ temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
+ }
+ } catch (final NullPointerException e) {
+ return null;
+ }
+
+ return temp;
+ }
+
+ /**
+ * Append header to mMessage.
+ */
+ private int appendHeader(final int field) {
+ switch (field) {
+ case PduHeaders.MMS_VERSION:
+ appendOctet(field);
+
+ final int version = mPduHeader.getOctet(field);
+ if (0 == version) {
+ appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
+ } else {
+ appendShortInteger(version);
+ }
+
+ break;
+
+ case PduHeaders.MESSAGE_ID:
+ case PduHeaders.TRANSACTION_ID:
+ case PduHeaders.CONTENT_LOCATION:
+ final byte[] textString = mPduHeader.getTextString(field);
+ if (null == textString) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendTextString(textString);
+ break;
+
+ case PduHeaders.TO:
+ case PduHeaders.BCC:
+ case PduHeaders.CC:
+ final EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
+
+ if (null == addr) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ EncodedStringValue temp;
+ for (int i = 0; i < addr.length; i++) {
+ temp = appendAddressType(addr[i]);
+ if (temp == null) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ appendOctet(field);
+ appendEncodedString(temp);
+ }
+ break;
+
+ case PduHeaders.FROM:
+ // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
+ appendOctet(field);
+
+ final EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
+ if ((from == null)
+ || TextUtils.isEmpty(from.getString())
+ || new String(from.getTextString()).equals(
+ PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+ // Length of from = 1
+ append(1);
+ // Insert-address-token = <Octet 129>
+ append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
+ } else {
+ mStack.newbuf();
+ final PositionMarker fstart = mStack.mark();
+
+ // Address-present-token = <Octet 128>
+ append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
+
+ temp = appendAddressType(from);
+ if (temp == null) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ appendEncodedString(temp);
+
+ final int flen = fstart.getLength();
+ mStack.pop();
+ appendValueLength(flen);
+ mStack.copy();
+ }
+ break;
+
+ case PduHeaders.READ_STATUS:
+ case PduHeaders.STATUS:
+ case PduHeaders.REPORT_ALLOWED:
+ case PduHeaders.PRIORITY:
+ case PduHeaders.DELIVERY_REPORT:
+ case PduHeaders.READ_REPORT:
+ final int octet = mPduHeader.getOctet(field);
+ if (0 == octet) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendOctet(octet);
+ break;
+
+ case PduHeaders.DATE:
+ final long date = mPduHeader.getLongInteger(field);
+ if (-1 == date) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendDateValue(date);
+ break;
+
+ case PduHeaders.SUBJECT:
+ final EncodedStringValue enString =
+ mPduHeader.getEncodedStringValue(field);
+ if (null == enString) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ appendEncodedString(enString);
+ break;
+
+ case PduHeaders.MESSAGE_CLASS:
+ final byte[] messageClass = mPduHeader.getTextString(field);
+ if (null == messageClass) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+ if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
+ } else if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
+ } else if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
+ } else if (Arrays.equals(messageClass,
+ PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
+ appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
+ } else {
+ appendTextString(messageClass);
+ }
+ break;
+
+ case PduHeaders.EXPIRY:
+ case PduHeaders.MESSAGE_SIZE:
+ final long value = mPduHeader.getLongInteger(field);
+ if (-1 == value) {
+ return PDU_COMPOSE_FIELD_NOT_SET;
+ }
+
+ appendOctet(field);
+
+ mStack.newbuf();
+ final PositionMarker valueStart = mStack.mark();
+
+ append(PduHeaders.VALUE_RELATIVE_TOKEN);
+ appendLongInteger(value);
+
+ final int valueLength = valueStart.getLength();
+ mStack.pop();
+ appendValueLength(valueLength);
+ mStack.copy();
+ break;
+
+ default:
+ return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
+ }
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make ReadRec.Ind.
+ */
+ private int makeReadRecInd() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Message-ID
+ if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // To
+ if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // From
+ if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Date Optional
+ appendHeader(PduHeaders.DATE);
+
+ // X-Mms-Read-Status
+ if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Applic-ID Optional(not support)
+ // X-Mms-Reply-Applic-ID Optional(not support)
+ // X-Mms-Aux-Applic-Info Optional(not support)
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make NotifyResp.Ind.
+ */
+ private int makeNotifyResp() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+
+ // X-Mms-Transaction-ID
+ if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Status
+ if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Report-Allowed Optional (not support)
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make Acknowledge.Ind.
+ */
+ private int makeAckInd() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+
+ // X-Mms-Transaction-ID
+ if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Report-Allowed Optional
+ appendHeader(PduHeaders.REPORT_ALLOWED);
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make Acknowledge.Ind.
+ */
+ private int makeNotificationInd() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+
+ // X-Mms-Transaction-ID
+ if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // From
+ if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Subject Optional
+ appendHeader(PduHeaders.SUBJECT);
+
+ // Expiry
+ if (appendHeader(PduHeaders.MESSAGE_CLASS) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Expiry
+ if (appendHeader(PduHeaders.MESSAGE_SIZE) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Expiry
+ if (appendHeader(PduHeaders.EXPIRY) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // X-Mms-Content-Location
+ if (appendHeader(PduHeaders.CONTENT_LOCATION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Make Send.req.
+ */
+ private int makeSendReqPdu() {
+ if (mMessage == null) {
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ // X-Mms-Message-Type
+ appendOctet(PduHeaders.MESSAGE_TYPE);
+ appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+
+ // X-Mms-Transaction-ID
+ appendOctet(PduHeaders.TRANSACTION_ID);
+
+ final byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
+ if (trid == null) {
+ // Transaction-ID should be set(by Transaction) before make().
+ throw new IllegalArgumentException("Transaction-ID is null.");
+ }
+ appendTextString(trid);
+
+ // X-Mms-MMS-Version
+ if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Date Date-value Optional.
+ appendHeader(PduHeaders.DATE);
+
+ // From
+ if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ boolean recipient = false;
+
+ // To
+ if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
+ recipient = true;
+ }
+
+ // Cc
+ if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
+ recipient = true;
+ }
+
+ // Bcc
+ if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
+ recipient = true;
+ }
+
+ // Need at least one of "cc", "bcc" and "to".
+ if (false == recipient) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // Subject Optional
+ appendHeader(PduHeaders.SUBJECT);
+
+ // X-Mms-Message-Class Optional
+ // Message-class-value = Class-identifier | Token-text
+ appendHeader(PduHeaders.MESSAGE_CLASS);
+
+ // X-Mms-Expiry Optional
+ appendHeader(PduHeaders.EXPIRY);
+
+ // X-Mms-Priority Optional
+ appendHeader(PduHeaders.PRIORITY);
+
+ // X-Mms-Delivery-Report Optional
+ appendHeader(PduHeaders.DELIVERY_REPORT);
+
+ // X-Mms-Read-Report Optional
+ appendHeader(PduHeaders.READ_REPORT);
+
+ // Content-Type
+ appendOctet(PduHeaders.CONTENT_TYPE);
+
+ // Message body
+ return makeMessageBody();
+ }
+
+ /**
+ * Make message body.
+ */
+ private int makeMessageBody() {
+ // 1. add body informations
+ mStack.newbuf(); // Switching buffer because we need to
+
+ final PositionMarker ctStart = mStack.mark();
+
+ // This contentTypeIdentifier should be used for type of attachment...
+ final String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
+ final Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
+ if (contentTypeIdentifier == null) {
+ // content type is mandatory
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ appendShortInteger(contentTypeIdentifier.intValue());
+
+ // content-type parameter: start
+ final PduBody body = ((SendReq) mPdu).getBody();
+ if (null == body || body.getPartsNum() == 0) {
+ // empty message
+ appendUintvarInteger(0);
+ mStack.pop();
+ mStack.copy();
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ PduPart part;
+ try {
+ part = body.getPart(0);
+
+ final byte[] start = part.getContentId();
+ if (start != null) {
+ appendOctet(PduPart.P_DEP_START);
+ if (('<' == start[0]) && ('>' == start[start.length - 1])) {
+ appendTextString(start);
+ } else {
+ appendTextString("<" + new String(start) + ">");
+ }
+ }
+
+ // content-type parameter: type
+ appendOctet(PduPart.P_CT_MR_TYPE);
+ appendTextString(part.getContentType());
+ } catch (final ArrayIndexOutOfBoundsException e) {
+ e.printStackTrace();
+ }
+
+ final int ctLength = ctStart.getLength();
+ mStack.pop();
+ appendValueLength(ctLength);
+ mStack.copy();
+
+ // 3. add content
+ final int partNum = body.getPartsNum();
+ appendUintvarInteger(partNum);
+ for (int i = 0; i < partNum; i++) {
+ part = body.getPart(i);
+ mStack.newbuf(); // Leaving space for header lengh and data length
+ final PositionMarker attachment = mStack.mark();
+
+ mStack.newbuf(); // Leaving space for Content-Type length
+ final PositionMarker contentTypeBegin = mStack.mark();
+
+ final byte[] partContentType = part.getContentType();
+
+ if (partContentType == null) {
+ // content type is mandatory
+ return PDU_COMPOSE_CONTENT_ERROR;
+ }
+
+ // content-type value
+ final Integer partContentTypeIdentifier =
+ mContentTypeMap.get(new String(partContentType));
+ if (partContentTypeIdentifier == null) {
+ appendTextString(partContentType);
+ } else {
+ appendShortInteger(partContentTypeIdentifier.intValue());
+ }
+
+ /* Content-type parameter : name.
+ * The value of name, filename, content-location is the same.
+ * Just one of them is enough for this PDU.
+ */
+ byte[] name = part.getName();
+
+ if (null == name) {
+ name = part.getFilename();
+
+ if (null == name) {
+ name = part.getContentLocation();
+
+ if (null == name) {
+ /* at lease one of name, filename, Content-location
+ * should be available.
+ */
+ // I found that an mms received from tmomail.net will include a SMIL part
+ // that has no name. That would cause the code here to return
+ // PDU_COMPOSE_CONTENT_ERROR when a user tried to forward the message. The
+ // message would never send and the user would be stuck in a retry
+ // situation. Simply jam in any old name here to fix the problem.
+ name = "smil.xml".getBytes();
+ }
+ }
+ }
+ appendOctet(PduPart.P_DEP_NAME);
+ appendTextString(name);
+
+ // content-type parameter : charset
+ final int charset = part.getCharset();
+ if (charset != 0) {
+ appendOctet(PduPart.P_CHARSET);
+ appendShortInteger(charset);
+ }
+
+ final int contentTypeLength = contentTypeBegin.getLength();
+ mStack.pop();
+ appendValueLength(contentTypeLength);
+ mStack.copy();
+
+ // content id
+ final byte[] contentId = part.getContentId();
+
+ if (null != contentId) {
+ appendOctet(PduPart.P_CONTENT_ID);
+ if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
+ appendQuotedString(contentId);
+ } else {
+ appendQuotedString("<" + new String(contentId) + ">");
+ }
+ }
+
+ // content-location
+ final byte[] contentLocation = part.getContentLocation();
+ if (null != contentLocation) {
+ appendOctet(PduPart.P_CONTENT_LOCATION);
+ appendTextString(contentLocation);
+ }
+
+ // content
+ final int headerLength = attachment.getLength();
+
+ int dataLength = 0; // Just for safety...
+ final byte[] partData = part.getData();
+
+ if (partData != null) {
+ arraycopy(partData, 0, partData.length);
+ dataLength = partData.length;
+ } else {
+ InputStream cr = null;
+ try {
+ final byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
+ cr = mResolver.openInputStream(part.getDataUri());
+ int len = 0;
+ while ((len = cr.read(buffer)) != -1) {
+ mMessage.write(buffer, 0, len);
+ mPosition += len;
+ dataLength += len;
+ }
+ } catch (final FileNotFoundException e) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ } catch (final IOException e) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ } catch (final RuntimeException e) {
+ return PDU_COMPOSE_CONTENT_ERROR;
+ } finally {
+ if (cr != null) {
+ try {
+ cr.close();
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+ }
+ }
+ }
+
+ if (dataLength != (attachment.getLength() - headerLength)) {
+ throw new RuntimeException("BUG: Length sanity check failed");
+ }
+
+ mStack.pop();
+ appendUintvarInteger(headerLength);
+ appendUintvarInteger(dataLength);
+ mStack.copy();
+ }
+
+ return PDU_COMPOSE_SUCCESS;
+ }
+
+ /**
+ * Record current message informations.
+ */
+ private static class LengthRecordNode {
+
+ ByteArrayOutputStream currentMessage = null;
+
+ public int currentPosition = 0;
+
+ public LengthRecordNode next = null;
+ }
+
+ /**
+ * Mark current message position and stact size.
+ */
+ private class PositionMarker {
+
+ private int c_pos; // Current position
+
+ private int currentStackSize; // Current stack size
+
+ int getLength() {
+ // If these assert fails, likely that you are finding the
+ // size of buffer that is deep in BufferStack you can only
+ // find the length of the buffer that is on top
+ if (currentStackSize != mStack.stackSize) {
+ throw new RuntimeException("BUG: Invalid call to getLength()");
+ }
+
+ return mPosition - c_pos;
+ }
+ }
+
+ /**
+ * This implementation can be OPTIMIZED to use only
+ * 2 buffers. This optimization involves changing BufferStack
+ * only... Its usage (interface) will not change.
+ */
+ private class BufferStack {
+
+ private LengthRecordNode stack = null;
+
+ private LengthRecordNode toCopy = null;
+
+ int stackSize = 0;
+
+ /**
+ * Create a new message buffer and push it into the stack.
+ */
+ void newbuf() {
+ // You can't create a new buff when toCopy != null
+ // That is after calling pop() and before calling copy()
+ // If you do, it is a bug
+ if (toCopy != null) {
+ throw new RuntimeException("BUG: Invalid newbuf() before copy()");
+ }
+
+ final LengthRecordNode temp = new LengthRecordNode();
+
+ temp.currentMessage = mMessage;
+ temp.currentPosition = mPosition;
+
+ temp.next = stack;
+ stack = temp;
+
+ stackSize = stackSize + 1;
+
+ mMessage = new ByteArrayOutputStream();
+ mPosition = 0;
+ }
+
+ /**
+ * Pop the message before and record current message in the stack.
+ */
+ void pop() {
+ final ByteArrayOutputStream currentMessage = mMessage;
+ final int currentPosition = mPosition;
+
+ mMessage = stack.currentMessage;
+ mPosition = stack.currentPosition;
+
+ toCopy = stack;
+ // Re using the top element of the stack to avoid memory allocation
+
+ stack = stack.next;
+ stackSize = stackSize - 1;
+
+ toCopy.currentMessage = currentMessage;
+ toCopy.currentPosition = currentPosition;
+ }
+
+ /**
+ * Append current message to the message before.
+ */
+ void copy() {
+ arraycopy(toCopy.currentMessage.toByteArray(), 0,
+ toCopy.currentPosition);
+
+ toCopy = null;
+ }
+
+ /**
+ * Mark current message position
+ */
+ PositionMarker mark() {
+ final PositionMarker m = new PositionMarker();
+
+ m.c_pos = mPosition;
+ m.currentStackSize = stackSize;
+
+ return m;
+ }
+ }
+
+ /**
+ * Check address type.
+ *
+ * @param address address string without the postfix stinng type,
+ * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
+ * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
+ * PDU_EMAIL_ADDRESS_TYPE if it is email address,
+ * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
+ * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
+ * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
+ */
+ protected static int checkAddressType(final String address) {
+ /**
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
+ * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
+ * e-mail = mailbox; to the definition of mailbox as described in
+ * section 3.4 of [RFC2822], but excluding the
+ * obsolete definitions as indicated by the "obs-" prefix.
+ * device-address = ( global-phone-number "/TYPE=PLMN" )
+ * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
+ * / ( escaped-value "/TYPE=" address-type )
+ *
+ * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+ * written-sep =("-"/".")
+ *
+ * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
+ *
+ * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
+ */
+
+ if (null == address) {
+ return PDU_UNKNOWN_ADDRESS_TYPE;
+ }
+
+ if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
+ // Ipv4 address.
+ return PDU_IPV4_ADDRESS_TYPE;
+ } else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
+ // Phone number.
+ return PDU_PHONE_NUMBER_ADDRESS_TYPE;
+ } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
+ // Email address.
+ return PDU_EMAIL_ADDRESS_TYPE;
+ } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
+ // Ipv6 address.
+ return PDU_IPV6_ADDRESS_TYPE;
+ } else {
+ // Unknown address.
+ return PDU_UNKNOWN_ADDRESS_TYPE;
+ }
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/PduContentTypes.java b/src/com/android/messaging/mmslib/pdu/PduContentTypes.java
new file mode 100644
index 0000000..f2cebf1
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduContentTypes.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+public class PduContentTypes {
+ /**
+ * All content types. From:
+ * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+ */
+ static final String[] contentTypes = {
+ "*/*", /* 0x00 */
+ "text/*", /* 0x01 */
+ "text/html", /* 0x02 */
+ "text/plain", /* 0x03 */
+ "text/x-hdml", /* 0x04 */
+ "text/x-ttml", /* 0x05 */
+ "text/x-vCalendar", /* 0x06 */
+ "text/x-vCard", /* 0x07 */
+ "text/vnd.wap.wml", /* 0x08 */
+ "text/vnd.wap.wmlscript", /* 0x09 */
+ "text/vnd.wap.wta-event", /* 0x0A */
+ "multipart/*", /* 0x0B */
+ "multipart/mixed", /* 0x0C */
+ "multipart/form-data", /* 0x0D */
+ "multipart/byterantes", /* 0x0E */
+ "multipart/alternative", /* 0x0F */
+ "application/*", /* 0x10 */
+ "application/java-vm", /* 0x11 */
+ "application/x-www-form-urlencoded", /* 0x12 */
+ "application/x-hdmlc", /* 0x13 */
+ "application/vnd.wap.wmlc", /* 0x14 */
+ "application/vnd.wap.wmlscriptc", /* 0x15 */
+ "application/vnd.wap.wta-eventc", /* 0x16 */
+ "application/vnd.wap.uaprof", /* 0x17 */
+ "application/vnd.wap.wtls-ca-certificate", /* 0x18 */
+ "application/vnd.wap.wtls-user-certificate", /* 0x19 */
+ "application/x-x509-ca-cert", /* 0x1A */
+ "application/x-x509-user-cert", /* 0x1B */
+ "image/*", /* 0x1C */
+ "image/gif", /* 0x1D */
+ "image/jpeg", /* 0x1E */
+ "image/tiff", /* 0x1F */
+ "image/png", /* 0x20 */
+ "image/vnd.wap.wbmp", /* 0x21 */
+ "application/vnd.wap.multipart.*", /* 0x22 */
+ "application/vnd.wap.multipart.mixed", /* 0x23 */
+ "application/vnd.wap.multipart.form-data", /* 0x24 */
+ "application/vnd.wap.multipart.byteranges", /* 0x25 */
+ "application/vnd.wap.multipart.alternative", /* 0x26 */
+ "application/xml", /* 0x27 */
+ "text/xml", /* 0x28 */
+ "application/vnd.wap.wbxml", /* 0x29 */
+ "application/x-x968-cross-cert", /* 0x2A */
+ "application/x-x968-ca-cert", /* 0x2B */
+ "application/x-x968-user-cert", /* 0x2C */
+ "text/vnd.wap.si", /* 0x2D */
+ "application/vnd.wap.sic", /* 0x2E */
+ "text/vnd.wap.sl", /* 0x2F */
+ "application/vnd.wap.slc", /* 0x30 */
+ "text/vnd.wap.co", /* 0x31 */
+ "application/vnd.wap.coc", /* 0x32 */
+ "application/vnd.wap.multipart.related", /* 0x33 */
+ "application/vnd.wap.sia", /* 0x34 */
+ "text/vnd.wap.connectivity-xml", /* 0x35 */
+ "application/vnd.wap.connectivity-wbxml", /* 0x36 */
+ "application/pkcs7-mime", /* 0x37 */
+ "application/vnd.wap.hashed-certificate", /* 0x38 */
+ "application/vnd.wap.signed-certificate", /* 0x39 */
+ "application/vnd.wap.cert-response", /* 0x3A */
+ "application/xhtml+xml", /* 0x3B */
+ "application/wml+xml", /* 0x3C */
+ "text/css", /* 0x3D */
+ "application/vnd.wap.mms-message", /* 0x3E */
+ "application/vnd.wap.rollover-certificate", /* 0x3F */
+ "application/vnd.wap.locc+wbxml", /* 0x40 */
+ "application/vnd.wap.loc+xml", /* 0x41 */
+ "application/vnd.syncml.dm+wbxml", /* 0x42 */
+ "application/vnd.syncml.dm+xml", /* 0x43 */
+ "application/vnd.syncml.notification", /* 0x44 */
+ "application/vnd.wap.xhtml+xml", /* 0x45 */
+ "application/vnd.wv.csp.cir", /* 0x46 */
+ "application/vnd.oma.dd+xml", /* 0x47 */
+ "application/vnd.oma.drm.message", /* 0x48 */
+ "application/vnd.oma.drm.content", /* 0x49 */
+ "application/vnd.oma.drm.rights+xml", /* 0x4A */
+ "application/vnd.oma.drm.rights+wbxml", /* 0x4B */
+ "application/vnd.wv.csp+xml", /* 0x4C */
+ "application/vnd.wv.csp+wbxml", /* 0x4D */
+ "application/vnd.syncml.ds.notification", /* 0x4E */
+ "audio/*", /* 0x4F */
+ "video/*", /* 0x50 */
+ "application/vnd.oma.dd2+xml", /* 0x51 */
+ "application/mikey" /* 0x52 */
+ };
+}
diff --git a/src/com/android/messaging/mmslib/pdu/PduHeaders.java b/src/com/android/messaging/mmslib/pdu/PduHeaders.java
new file mode 100644
index 0000000..96d18ee
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduHeaders.java
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import android.util.SparseArray;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+import java.util.ArrayList;
+
+public class PduHeaders {
+ /**
+ * All pdu header fields.
+ */
+ public static final int BCC = 0x81;
+ public static final int CC = 0x82;
+ public static final int CONTENT_LOCATION = 0x83;
+ public static final int CONTENT_TYPE = 0x84;
+ public static final int DATE = 0x85;
+ public static final int DELIVERY_REPORT = 0x86;
+ public static final int DELIVERY_TIME = 0x87;
+ public static final int EXPIRY = 0x88;
+ public static final int FROM = 0x89;
+ public static final int MESSAGE_CLASS = 0x8A;
+ public static final int MESSAGE_ID = 0x8B;
+ public static final int MESSAGE_TYPE = 0x8C;
+ public static final int MMS_VERSION = 0x8D;
+ public static final int MESSAGE_SIZE = 0x8E;
+ public static final int PRIORITY = 0x8F;
+
+ public static final int READ_REPLY = 0x90;
+ public static final int READ_REPORT = 0x90;
+ public static final int REPORT_ALLOWED = 0x91;
+ public static final int RESPONSE_STATUS = 0x92;
+ public static final int RESPONSE_TEXT = 0x93;
+ public static final int SENDER_VISIBILITY = 0x94;
+ public static final int STATUS = 0x95;
+ public static final int SUBJECT = 0x96;
+ public static final int TO = 0x97;
+ public static final int TRANSACTION_ID = 0x98;
+ public static final int RETRIEVE_STATUS = 0x99;
+ public static final int RETRIEVE_TEXT = 0x9A;
+ public static final int READ_STATUS = 0x9B;
+ public static final int REPLY_CHARGING = 0x9C;
+ public static final int REPLY_CHARGING_DEADLINE = 0x9D;
+ public static final int REPLY_CHARGING_ID = 0x9E;
+ public static final int REPLY_CHARGING_SIZE = 0x9F;
+
+ public static final int PREVIOUSLY_SENT_BY = 0xA0;
+ public static final int PREVIOUSLY_SENT_DATE = 0xA1;
+ public static final int STORE = 0xA2;
+ public static final int MM_STATE = 0xA3;
+ public static final int MM_FLAGS = 0xA4;
+ public static final int STORE_STATUS = 0xA5;
+ public static final int STORE_STATUS_TEXT = 0xA6;
+ public static final int STORED = 0xA7;
+ public static final int ATTRIBUTES = 0xA8;
+ public static final int TOTALS = 0xA9;
+ public static final int MBOX_TOTALS = 0xAA;
+ public static final int QUOTAS = 0xAB;
+ public static final int MBOX_QUOTAS = 0xAC;
+ public static final int MESSAGE_COUNT = 0xAD;
+ public static final int CONTENT = 0xAE;
+ public static final int START = 0xAF;
+
+ public static final int ADDITIONAL_HEADERS = 0xB0;
+ public static final int DISTRIBUTION_INDICATOR = 0xB1;
+ public static final int ELEMENT_DESCRIPTOR = 0xB2;
+ public static final int LIMIT = 0xB3;
+ public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4;
+ public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+ public static final int STATUS_TEXT = 0xB6;
+ public static final int APPLIC_ID = 0xB7;
+ public static final int REPLY_APPLIC_ID = 0xB8;
+ public static final int AUX_APPLIC_ID = 0xB9;
+ public static final int CONTENT_CLASS = 0xBA;
+ public static final int DRM_CONTENT = 0xBB;
+ public static final int ADAPTATION_ALLOWED = 0xBC;
+ public static final int REPLACE_ID = 0xBD;
+ public static final int CANCEL_ID = 0xBE;
+ public static final int CANCEL_STATUS = 0xBF;
+
+ /**
+ * X-Mms-Message-Type field types.
+ */
+ public static final int MESSAGE_TYPE_SEND_REQ = 0x80;
+ public static final int MESSAGE_TYPE_SEND_CONF = 0x81;
+ public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82;
+ public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83;
+ public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
+ public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85;
+ public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86;
+ public static final int MESSAGE_TYPE_READ_REC_IND = 0x87;
+ public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88;
+ public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89;
+ public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A;
+ public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B;
+ public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C;
+ public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D;
+ public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E;
+ public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F;
+ public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90;
+ public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91;
+ public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92;
+ public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93;
+ public static final int MESSAGE_TYPE_DELETE_REQ = 0x94;
+ public static final int MESSAGE_TYPE_DELETE_CONF = 0x95;
+ public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96;
+ public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97;
+
+ /**
+ * X-Mms-Delivery-Report |
+ * X-Mms-Read-Report |
+ * X-Mms-Report-Allowed |
+ * X-Mms-Sender-Visibility |
+ * X-Mms-Store |
+ * X-Mms-Stored |
+ * X-Mms-Totals |
+ * X-Mms-Quotas |
+ * X-Mms-Distribution-Indicator |
+ * X-Mms-DRM-Content |
+ * X-Mms-Adaptation-Allowed |
+ * field types.
+ */
+ public static final int VALUE_YES = 0x80;
+ public static final int VALUE_NO = 0x81;
+
+ /**
+ * Delivery-Time |
+ * Expiry and Reply-Charging-Deadline |
+ * field type components.
+ */
+ public static final int VALUE_ABSOLUTE_TOKEN = 0x80;
+ public static final int VALUE_RELATIVE_TOKEN = 0x81;
+
+ /**
+ * X-Mms-MMS-Version field types.
+ */
+ public static final int MMS_VERSION_1_3 = ((1 << 4) | 3);
+ public static final int MMS_VERSION_1_2 = ((1 << 4) | 2);
+ public static final int MMS_VERSION_1_1 = ((1 << 4) | 1);
+ public static final int MMS_VERSION_1_0 = ((1 << 4) | 0);
+
+ // Current version is 1.2.
+ public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2;
+
+ /**
+ * From field type components.
+ */
+ public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80;
+ public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81;
+
+ public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+
+ public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+ /**
+ * X-Mms-Status Field.
+ */
+ public static final int STATUS_EXPIRED = 0x80;
+ public static final int STATUS_RETRIEVED = 0x81;
+ public static final int STATUS_REJECTED = 0x82;
+ public static final int STATUS_DEFERRED = 0x83;
+ public static final int STATUS_UNRECOGNIZED = 0x84;
+ public static final int STATUS_INDETERMINATE = 0x85;
+ public static final int STATUS_FORWARDED = 0x86;
+ public static final int STATUS_UNREACHABLE = 0x87;
+
+ /**
+ * MM-Flags field type components.
+ */
+ public static final int MM_FLAGS_ADD_TOKEN = 0x80;
+ public static final int MM_FLAGS_REMOVE_TOKEN = 0x81;
+ public static final int MM_FLAGS_FILTER_TOKEN = 0x82;
+
+ /**
+ * X-Mms-Message-Class field types.
+ */
+ public static final int MESSAGE_CLASS_PERSONAL = 0x80;
+ public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81;
+ public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82;
+ public static final int MESSAGE_CLASS_AUTO = 0x83;
+
+ public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+
+ public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+
+ public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+
+ public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+ /**
+ * X-Mms-Priority field types.
+ */
+ public static final int PRIORITY_LOW = 0x80;
+ public static final int PRIORITY_NORMAL = 0x81;
+ public static final int PRIORITY_HIGH = 0x82;
+
+ /**
+ * X-Mms-Response-Status field types.
+ */
+ public static final int RESPONSE_STATUS_OK = 0x80;
+ public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81;
+ public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+ public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83;
+ public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+ public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85;
+ public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86;
+ public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+ public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3;
+ public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4;
+
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE
+ = 0xE0;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED
+ = 0xE1;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT
+ = 0xE2;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED
+ = 0xE3;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND
+ = 0xE4;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED
+ = 0xE5;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET
+ = 0xE6;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED
+ = 0xE7;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED
+ = 0xE8;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED
+ = 0xE9;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED
+ = 0xEA;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID
+ = 0xEB;
+ public static final int RESPONSE_STATUS_ERROR_PERMANENT_END
+ = 0xFF;
+
+ /**
+ * X-Mms-Retrieve-Status field types.
+ */
+ public static final int RETRIEVE_STATUS_OK = 0x80;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1;
+ public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2;
+ public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+ public static final int RETRIEVE_STATUS_ERROR_END = 0xFF;
+
+ /**
+ * X-Mms-Sender-Visibility field types.
+ */
+ public static final int SENDER_VISIBILITY_HIDE = 0x80;
+ public static final int SENDER_VISIBILITY_SHOW = 0x81;
+
+ /**
+ * X-Mms-Read-Status field types.
+ */
+ public static final int READ_STATUS_READ = 0x80;
+ public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+ /**
+ * X-Mms-Cancel-Status field types.
+ */
+ public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+ public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81;
+
+ /**
+ * X-Mms-Reply-Charging field types.
+ */
+ public static final int REPLY_CHARGING_REQUESTED = 0x80;
+ public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+ public static final int REPLY_CHARGING_ACCEPTED = 0x82;
+ public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83;
+
+ /**
+ * X-Mms-MM-State field types.
+ */
+ public static final int MM_STATE_DRAFT = 0x80;
+ public static final int MM_STATE_SENT = 0x81;
+ public static final int MM_STATE_NEW = 0x82;
+ public static final int MM_STATE_RETRIEVED = 0x83;
+ public static final int MM_STATE_FORWARDED = 0x84;
+
+ /**
+ * X-Mms-Recommended-Retrieval-Mode field types.
+ */
+ public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+ /**
+ * X-Mms-Content-Class field types.
+ */
+ public static final int CONTENT_CLASS_TEXT = 0x80;
+ public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81;
+ public static final int CONTENT_CLASS_IMAGE_RICH = 0x82;
+ public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83;
+ public static final int CONTENT_CLASS_VIDEO_RICH = 0x84;
+ public static final int CONTENT_CLASS_MEGAPIXEL = 0x85;
+ public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86;
+ public static final int CONTENT_CLASS_CONTENT_RICH = 0x87;
+
+ /**
+ * X-Mms-Store-Status field types.
+ */
+ public static final int STORE_STATUS_SUCCESS = 0x80;
+ public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0;
+ public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1;
+ public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0;
+ public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3;
+ public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4;
+ public static final int STORE_STATUS_ERROR_END = 0xFF;
+
+ /**
+ * The map contains the value of all headers.
+ */
+ private SparseArray<Object> mHeaderMap = null;
+
+ /**
+ * Constructor of PduHeaders.
+ */
+ public PduHeaders() {
+ mHeaderMap = new SparseArray<Object>();
+ }
+
+ /**
+ * Get octet value by header field.
+ *
+ * @param field the field
+ * @return the octet value of the pdu header
+ * with specified header field. Return 0 if
+ * the value is not set.
+ */
+ protected int getOctet(int field) {
+ Integer octet = (Integer) mHeaderMap.get(field);
+ if (null == octet) {
+ return 0;
+ }
+
+ return octet;
+ }
+
+ /**
+ * Set octet value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ protected void setOctet(int value, int field)
+ throws InvalidHeaderValueException {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ switch (field) {
+ case REPORT_ALLOWED:
+ case ADAPTATION_ALLOWED:
+ case DELIVERY_REPORT:
+ case DRM_CONTENT:
+ case DISTRIBUTION_INDICATOR:
+ case QUOTAS:
+ case READ_REPORT:
+ case STORE:
+ case STORED:
+ case TOTALS:
+ case SENDER_VISIBILITY:
+ if ((VALUE_YES != value) && (VALUE_NO != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case READ_STATUS:
+ if ((READ_STATUS_READ != value) &&
+ (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case CANCEL_STATUS:
+ if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+ (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case PRIORITY:
+ if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case STATUS:
+ if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case REPLY_CHARGING:
+ if ((value < REPLY_CHARGING_REQUESTED)
+ || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case MM_STATE:
+ if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case RECOMMENDED_RETRIEVAL_MODE:
+ if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case CONTENT_CLASS:
+ if ((value < CONTENT_CLASS_TEXT)
+ || (value > CONTENT_CLASS_CONTENT_RICH)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ case RETRIEVE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+ if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+ (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+ (value <= RETRIEVE_STATUS_ERROR_END)) {
+ value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+ } else if ((value < RETRIEVE_STATUS_OK) ||
+ ((value > RETRIEVE_STATUS_OK) &&
+ (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > RETRIEVE_STATUS_ERROR_END)) {
+ value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case STORE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+ if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+ (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+ (value <= STORE_STATUS_ERROR_END)) {
+ value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+ } else if ((value < STORE_STATUS_SUCCESS) ||
+ ((value > STORE_STATUS_SUCCESS) &&
+ (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > STORE_STATUS_ERROR_END)) {
+ value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case RESPONSE_STATUS:
+ // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+ if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+ (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+ value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+ } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+ (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+ (value < RESPONSE_STATUS_OK) ||
+ ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+ (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+ (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+ value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+ }
+ break;
+ case MMS_VERSION:
+ if ((value < MMS_VERSION_1_0) || (value > MMS_VERSION_1_3)) {
+ value = CURRENT_MMS_VERSION; // Current version is the default value.
+ }
+ break;
+ case MESSAGE_TYPE:
+ if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+ // Invalid value.
+ throw new InvalidHeaderValueException("Invalid Octet value!");
+ }
+ break;
+ default:
+ // This header value should not be Octect.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Get TextString value by header field.
+ *
+ * @param field the field
+ * @return the TextString value of the pdu header
+ * with specified header field
+ */
+ protected byte[] getTextString(int field) {
+ return (byte[]) mHeaderMap.get(field);
+ }
+
+ /**
+ * Set TextString value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the TextString value of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setTextString(byte[] value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case TRANSACTION_ID:
+ case REPLY_CHARGING_ID:
+ case AUX_APPLIC_ID:
+ case APPLIC_ID:
+ case REPLY_APPLIC_ID:
+ case MESSAGE_ID:
+ case REPLACE_ID:
+ case CANCEL_ID:
+ case CONTENT_LOCATION:
+ case MESSAGE_CLASS:
+ case CONTENT_TYPE:
+ break;
+ default:
+ // This header value should not be Text-String.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Get EncodedStringValue value by header field.
+ *
+ * @param field the field
+ * @return the EncodedStringValue value of the pdu header
+ * with specified header field
+ */
+ protected EncodedStringValue getEncodedStringValue(int field) {
+ return (EncodedStringValue) mHeaderMap.get(field);
+ }
+
+ /**
+ * Get TO, CC or BCC header value.
+ *
+ * @param field the field
+ * @return the EncodeStringValue array of the pdu header
+ * with specified header field
+ */
+ protected EncodedStringValue[] getEncodedStringValues(int field) {
+ ArrayList<EncodedStringValue> list =
+ (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+ if (null == list) {
+ return null;
+ }
+ EncodedStringValue[] values = new EncodedStringValue[list.size()];
+ return list.toArray(values);
+ }
+
+ /**
+ * Set EncodedStringValue value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the EncodedStringValue value of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setEncodedStringValue(EncodedStringValue value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case SUBJECT:
+ case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+ case RETRIEVE_TEXT:
+ case STATUS_TEXT:
+ case STORE_STATUS_TEXT:
+ case RESPONSE_TEXT:
+ case FROM:
+ case PREVIOUSLY_SENT_BY:
+ case MM_FLAGS:
+ break;
+ default:
+ // This header value should not be Encoded-String-Value.
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ mHeaderMap.put(field, value);
+ }
+
+ /**
+ * Set TO, CC or BCC header value.
+ *
+ * @param value the value
+ * @param field the field
+ * @return the EncodedStringValue value array of the pdu header
+ * with specified header field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case BCC:
+ case CC:
+ case TO:
+ break;
+ default:
+ // This header value should not be Encoded-String-Value.
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+ for (int i = 0; i < value.length; i++) {
+ list.add(value[i]);
+ }
+ mHeaderMap.put(field, list);
+ }
+
+ /**
+ * Append one EncodedStringValue to another.
+ *
+ * @param value the EncodedStringValue to append
+ * @param field the field
+ * @throws NullPointerException if the value is null.
+ */
+ protected void appendEncodedStringValue(EncodedStringValue value,
+ int field) {
+ if (null == value) {
+ throw new NullPointerException();
+ }
+
+ switch (field) {
+ case BCC:
+ case CC:
+ case TO:
+ break;
+ default:
+ throw new RuntimeException("Invalid header field!");
+ }
+
+ ArrayList<EncodedStringValue> list =
+ (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+ if (null == list) {
+ list = new ArrayList<EncodedStringValue>();
+ }
+ list.add(value);
+ mHeaderMap.put(field, list);
+ }
+
+ /**
+ * Get LongInteger value by header field.
+ *
+ * @param field the field
+ * @return the LongInteger value of the pdu header
+ * with specified header field. if return -1, the
+ * field is not existed in pdu header.
+ */
+ protected long getLongInteger(int field) {
+ Long longInteger = (Long) mHeaderMap.get(field);
+ if (null == longInteger) {
+ return -1;
+ }
+
+ return longInteger.longValue();
+ }
+
+ /**
+ * Set LongInteger value to pdu header by header field.
+ *
+ * @param value the value
+ * @param field the field
+ */
+ protected void setLongInteger(long value, int field) {
+ /**
+ * Check whether this field can be set for specific
+ * header and check validity of the field.
+ */
+ switch (field) {
+ case DATE:
+ case REPLY_CHARGING_SIZE:
+ case MESSAGE_SIZE:
+ case MESSAGE_COUNT:
+ case START:
+ case LIMIT:
+ case DELIVERY_TIME:
+ case EXPIRY:
+ case REPLY_CHARGING_DEADLINE:
+ case PREVIOUSLY_SENT_DATE:
+ break;
+ default:
+ // This header value should not be LongInteger.
+ throw new RuntimeException("Invalid header field!");
+ }
+ mHeaderMap.put(field, value);
+ }
+
+ public boolean hasHeader(int field) {
+ return mHeaderMap.get(field, null) != null;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/PduParser.java b/src/com/android/messaging/mmslib/pdu/PduParser.java
new file mode 100755
index 0000000..e392fb5
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduParser.java
@@ -0,0 +1,2044 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.mmslib.pdu;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+import com.android.messaging.util.ContentType;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+public class PduParser {
+ /**
+ * The log tag.
+ */
+ private static final String LOG_TAG = "PduParser";
+
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * The next are WAP values defined in WSP specification.
+ */
+ private static final int QUOTE = 127;
+
+ private static final int LENGTH_QUOTE = 31;
+
+ private static final int TEXT_MIN = 32;
+
+ private static final int TEXT_MAX = 127;
+
+ private static final int SHORT_INTEGER_MAX = 127;
+
+ private static final int SHORT_LENGTH_MAX = 30;
+
+ private static final int LONG_INTEGER_LENGTH_MAX = 8;
+
+ private static final int QUOTED_STRING_FLAG = 34;
+
+ private static final int END_STRING_FLAG = 0x00;
+
+ //The next two are used by the interface "parseWapString" to
+ //distinguish Text-String and Quoted-String.
+ private static final int TYPE_TEXT_STRING = 0;
+
+ private static final int TYPE_QUOTED_STRING = 1;
+
+ private static final int TYPE_TOKEN_STRING = 2;
+
+ /**
+ * Specify the part position.
+ */
+ private static final int THE_FIRST_PART = 0;
+
+ private static final int THE_LAST_PART = 1;
+
+ /**
+ * The pdu data.
+ */
+ private ByteArrayInputStream mPduDataStream = null;
+
+ /**
+ * Store pdu headers
+ */
+ private PduHeaders mHeaders = null;
+
+ /**
+ * Store pdu parts.
+ */
+ private PduBody mBody = null;
+
+ /**
+ * Store the "type" parameter in "Content-Type" header field.
+ */
+ private static byte[] mTypeParam = null;
+
+ /**
+ * Store the "start" parameter in "Content-Type" header field.
+ */
+ private static byte[] mStartParam = null;
+
+ /**
+ * Whether to parse content-disposition part header
+ */
+ private final boolean mParseContentDisposition;
+
+ /**
+ * Constructor.
+ *
+ * @param pduDataStream pdu data to be parsed
+ * @param parseContentDisposition whether to parse the Content-Disposition part header
+ */
+ public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
+ mPduDataStream = new ByteArrayInputStream(pduDataStream);
+ mParseContentDisposition = parseContentDisposition;
+ }
+
+ /**
+ * Parse the pdu.
+ *
+ * @return the pdu structure if parsing successfully.
+ * null if parsing error happened or mandatory fields are not set.
+ */
+ public GenericPdu parse() {
+ if (mPduDataStream == null) {
+ return null;
+ }
+
+ /* parse headers */
+ mHeaders = parseHeaders(mPduDataStream);
+ if (null == mHeaders) {
+ // Parse headers failed.
+ return null;
+ }
+
+ /* get the message type */
+ int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+ /* check mandatory header fields */
+ if (false == checkMandatoryHeader(mHeaders)) {
+ log("check mandatory headers failed!");
+ return null;
+ }
+ /*
+ * Get retrieve status. If the header is not there, assuming it is OK status.
+ * Some carriers may choose to not send this header.
+ */
+ int retrieveStatus = mHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ?
+ mHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK;
+
+ if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+ (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType &&
+ retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK)) {
+ /* need to parse the parts */
+ mBody = parseParts(mPduDataStream);
+ if (null == mBody) {
+ // Parse parts failed.
+ return null;
+ }
+ }
+
+ switch (messageType) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
+ }
+ SendReq sendReq = new SendReq(mHeaders, mBody);
+ return sendReq;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
+ }
+ SendConf sendConf = new SendConf(mHeaders);
+ return sendConf;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
+ }
+ NotificationInd notificationInd =
+ new NotificationInd(mHeaders);
+ return notificationInd;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
+ }
+ NotifyRespInd notifyRespInd =
+ new NotifyRespInd(mHeaders);
+ return notifyRespInd;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
+ }
+ RetrieveConf retrieveConf =
+ new RetrieveConf(mHeaders, mBody);
+ if (retrieveStatus != PduHeaders.RETRIEVE_STATUS_OK) {
+ // For failure only no need to check content type
+ return retrieveConf;
+ }
+ byte[] contentType = retrieveConf.getContentType();
+ if (null == contentType) {
+ return null;
+ }
+ String ctTypeStr = new String(contentType);
+ if (ctTypeStr.equals(ContentType.MMS_MULTIPART_MIXED)
+ || ctTypeStr.equals(ContentType.MMS_MULTIPART_RELATED)
+ || ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
+ // The MMS content type must be "application/vnd.wap.multipart.mixed"
+ // or "application/vnd.wap.multipart.related"
+ // or "application/vnd.wap.multipart.alternative"
+ return retrieveConf;
+ } else if (ctTypeStr.equals(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
+ // "application/vnd.wap.multipart.alternative"
+ // should take only the first part.
+ PduPart firstPart = mBody.getPart(0);
+ mBody.removeAll();
+ mBody.addPart(0, firstPart);
+ return retrieveConf;
+ }
+ return null;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
+ }
+ DeliveryInd deliveryInd =
+ new DeliveryInd(mHeaders);
+ return deliveryInd;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
+ }
+ AcknowledgeInd acknowledgeInd =
+ new AcknowledgeInd(mHeaders);
+ return acknowledgeInd;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
+ }
+ ReadOrigInd readOrigInd =
+ new ReadOrigInd(mHeaders);
+ return readOrigInd;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
+ }
+ ReadRecInd readRecInd =
+ new ReadRecInd(mHeaders);
+ return readRecInd;
+ default:
+ log("Parser doesn't support this message type in this version!");
+ return null;
+ }
+ }
+
+ /**
+ * Parse pdu headers.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return headers in PduHeaders structure, null when parse fail
+ */
+ protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream) {
+ if (pduDataStream == null) {
+ return null;
+ }
+ boolean keepParsing = true;
+ PduHeaders headers = new PduHeaders();
+
+ while (keepParsing && (pduDataStream.available() > 0)) {
+ pduDataStream.mark(1);
+ int headerField = extractByteValue(pduDataStream);
+ /* parse custom text header */
+ if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
+ pduDataStream.reset();
+ byte[] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
+ }
+ /* we should ignore it at the moment */
+ continue;
+ }
+ switch (headerField) {
+ case PduHeaders.MESSAGE_TYPE: {
+ int messageType = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType +
+ " (" + Integer.toHexString(headerField) + ")");
+ }
+ switch (messageType) {
+ // We don't support these kind of messages now.
+ case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+ case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+ return null;
+ }
+ try {
+ headers.setOctet(messageType, headerField);
+ } catch (InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + messageType +
+ " into the header filed: " + headerField);
+ return null;
+ } catch (RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+ /* Octect value */
+ case PduHeaders.REPORT_ALLOWED:
+ case PduHeaders.ADAPTATION_ALLOWED:
+ case PduHeaders.DELIVERY_REPORT:
+ case PduHeaders.DRM_CONTENT:
+ case PduHeaders.DISTRIBUTION_INDICATOR:
+ case PduHeaders.QUOTAS:
+ case PduHeaders.READ_REPORT:
+ case PduHeaders.STORE:
+ case PduHeaders.STORED:
+ case PduHeaders.TOTALS:
+ case PduHeaders.SENDER_VISIBILITY:
+ case PduHeaders.READ_STATUS:
+ case PduHeaders.CANCEL_STATUS:
+ case PduHeaders.PRIORITY:
+ case PduHeaders.STATUS:
+ case PduHeaders.REPLY_CHARGING:
+ case PduHeaders.MM_STATE:
+ case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+ case PduHeaders.CONTENT_CLASS:
+ case PduHeaders.RETRIEVE_STATUS:
+ case PduHeaders.STORE_STATUS:
+ /**
+ * The following field has a different value when
+ * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+ * For now we ignore this fact, since we do not support these PDUs
+ */
+ case PduHeaders.RESPONSE_STATUS: {
+ int value = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") Octect value: " +
+ value);
+ }
+
+ try {
+ headers.setOctet(value, headerField);
+ } catch (InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + value +
+ " into the header filed: " + headerField);
+ return null;
+ } catch (RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Long-Integer */
+ case PduHeaders.DATE:
+ case PduHeaders.REPLY_CHARGING_SIZE:
+ case PduHeaders.MESSAGE_SIZE: {
+ try {
+ long value = parseLongInteger(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") longint value: " +
+ value);
+ }
+ headers.setLongInteger(value, headerField);
+ } catch (RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Integer-Value */
+ case PduHeaders.MESSAGE_COUNT:
+ case PduHeaders.START:
+ case PduHeaders.LIMIT: {
+ try {
+ long value = parseIntegerValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") integer value: " +
+ value);
+ }
+ headers.setLongInteger(value, headerField);
+ } catch (RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ /* Text-String */
+ case PduHeaders.TRANSACTION_ID:
+ case PduHeaders.REPLY_CHARGING_ID:
+ case PduHeaders.AUX_APPLIC_ID:
+ case PduHeaders.APPLIC_ID:
+ case PduHeaders.REPLY_APPLIC_ID:
+ /**
+ * The next three header fields are email addresses
+ * as defined in RFC2822,
+ * not including the characters "<" and ">"
+ */
+ case PduHeaders.MESSAGE_ID:
+ case PduHeaders.REPLACE_ID:
+ case PduHeaders.CANCEL_ID:
+ /**
+ * The following field has a different value when
+ * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+ * For now we ignore this fact, since we do not support these PDUs
+ */
+ case PduHeaders.CONTENT_LOCATION: {
+ byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != value) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") string value: "
+ +
+ new String(value));
+ }
+ headers.setTextString(value, headerField);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Encoded-string-value */
+ case PduHeaders.SUBJECT:
+ case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+ case PduHeaders.RETRIEVE_TEXT:
+ case PduHeaders.STATUS_TEXT:
+ case PduHeaders.STORE_STATUS_TEXT:
+ /* the next one is not support
+ * M-Mbox-Delete.conf and M-Delete.conf now */
+ case PduHeaders.RESPONSE_TEXT: {
+ EncodedStringValue value =
+ parseEncodedStringValue(pduDataStream);
+ if (null != value) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField)
+ + ") encoded string: " +
+ value.getString());
+ }
+ headers.setEncodedStringValue(value, headerField);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Addressing model */
+ case PduHeaders.BCC:
+ case PduHeaders.CC:
+ case PduHeaders.TO: {
+ EncodedStringValue value =
+ parseEncodedStringValue(pduDataStream);
+ if (null != value) {
+ byte[] address = value.getTextString();
+ if (null != address) {
+ String str = new String(address);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
+ + " value: " + str);
+ }
+ int endIndex = str.indexOf("/");
+ if (endIndex > 0) {
+ str = str.substring(0, endIndex);
+ }
+ try {
+ value.setTextString(str.getBytes());
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ return null;
+ }
+ }
+
+ try {
+ headers.appendEncodedStringValue(value, headerField);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ /* Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+ case PduHeaders.DELIVERY_TIME:
+ case PduHeaders.EXPIRY:
+ case PduHeaders.REPLY_CHARGING_DEADLINE: {
+ /* parse Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Absolute-token or Relative-token */
+ int token = extractByteValue(pduDataStream);
+
+ /* Date-value or Delta-seconds-value */
+ long timeValue;
+ try {
+ timeValue = parseLongInteger(pduDataStream);
+ } catch (RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+ /* need to convert the Delta-seconds-value
+ * into Date-value */
+ timeValue = System.currentTimeMillis() / 1000 + timeValue;
+ }
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") time value: " +
+ timeValue);
+ }
+ headers.setLongInteger(timeValue, headerField);
+ } catch (RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.FROM: {
+ /* From-value =
+ * Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ */
+ EncodedStringValue from = null;
+ parseValueLength(pduDataStream); /* parse value-length */
+
+ /* Address-present-token or Insert-address-token */
+ int fromToken = extractByteValue(pduDataStream);
+
+ /* Address-present-token or Insert-address-token */
+ if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+ /* Encoded-string-value */
+ from = parseEncodedStringValue(pduDataStream);
+ if (null != from) {
+ byte[] address = from.getTextString();
+ if (null != address) {
+ String str = new String(address);
+ int endIndex = str.indexOf("/");
+ if (endIndex > 0) {
+ str = str.substring(0, endIndex);
+ }
+ try {
+ from.setTextString(str.getBytes());
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ return null;
+ }
+ }
+ }
+ } else {
+ try {
+ from = new EncodedStringValue(
+ PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+ } catch (NullPointerException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") from address: " +
+ from.getString());
+ }
+ headers.setEncodedStringValue(from, PduHeaders.FROM);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.MESSAGE_CLASS: {
+ /* Message-class-value = Class-identifier | Token-text */
+ pduDataStream.mark(1);
+ int messageClass = extractByteValue(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") MESSAGE_CLASS: " +
+ messageClass);
+ }
+
+ if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+ /* Class-identifier */
+ try {
+ if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+ headers.setTextString(
+ PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+ PduHeaders.MESSAGE_CLASS);
+ }
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ } else {
+ /* Token-text */
+ pduDataStream.reset();
+ byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != messageClassString) {
+ try {
+ headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+ }
+ break;
+ }
+
+ case PduHeaders.MMS_VERSION: {
+ int version = parseShortInteger(pduDataStream);
+
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") MMS_VERSION: " +
+ version);
+ }
+ headers.setOctet(version, PduHeaders.MMS_VERSION);
+ } catch (InvalidHeaderValueException e) {
+ log("Set invalid Octet value: " + version +
+ " into the header filed: " + headerField);
+ return null;
+ } catch (RuntimeException e) {
+ log(headerField + "is not Octet header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.PREVIOUSLY_SENT_BY: {
+ /* Previously-sent-by-value =
+ * Value-length Forwarded-count-value Encoded-string-value */
+ /* parse value-length */
+ parseValueLength(pduDataStream);
+
+ /* parse Forwarded-count-value */
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch (RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* parse Encoded-string-value */
+ EncodedStringValue previouslySentBy =
+ parseEncodedStringValue(pduDataStream);
+ if (null != previouslySentBy) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) +
+ ") PREVIOUSLY_SENT_BY: " + previouslySentBy.getString());
+ }
+ headers.setEncodedStringValue(previouslySentBy,
+ PduHeaders.PREVIOUSLY_SENT_BY);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Encoded-String-Value header field!");
+ return null;
+ }
+ }
+ break;
+ }
+
+ case PduHeaders.PREVIOUSLY_SENT_DATE: {
+ /* Previously-sent-date-value =
+ * Value-length Forwarded-count-value Date-value */
+ /* parse value-length */
+ parseValueLength(pduDataStream);
+
+ /* parse Forwarded-count-value */
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch (RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* Date-value */
+ try {
+ long previouslySentDate = parseLongInteger(pduDataStream);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) +
+ ") PREVIOUSLY_SENT_DATE: " + previouslySentDate);
+ }
+ headers.setLongInteger(previouslySentDate,
+ PduHeaders.PREVIOUSLY_SENT_DATE);
+ } catch (RuntimeException e) {
+ log(headerField + "is not Long-Integer header field!");
+ return null;
+ }
+ break;
+ }
+
+ case PduHeaders.MM_FLAGS: {
+ /* MM-flags-value =
+ * Value-length
+ * ( Add-token | Remove-token | Filter-token )
+ * Encoded-string-value
+ */
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") MM_FLAGS: " +
+ " NOT REALLY SUPPORTED");
+ }
+
+ /* parse Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Add-token | Remove-token | Filter-token */
+ extractByteValue(pduDataStream);
+
+ /* Encoded-string-value */
+ parseEncodedStringValue(pduDataStream);
+
+ /* not store this header filed in "headers",
+ * because now PduHeaders doesn't support it */
+ break;
+ }
+
+ /* Value-length
+ * (Message-total-token | Size-total-token) Integer-Value */
+ case PduHeaders.MBOX_TOTALS:
+ case PduHeaders.MBOX_QUOTAS: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") MBOX_");
+ }
+ /* Value-length */
+ parseValueLength(pduDataStream);
+
+ /* Message-total-token | Size-total-token */
+ extractByteValue(pduDataStream);
+
+ /*Integer-Value*/
+ try {
+ parseIntegerValue(pduDataStream);
+ } catch (RuntimeException e) {
+ log(headerField + " is not Integer-Value");
+ return null;
+ }
+
+ /* not store these headers filed in "headers",
+ because now PduHeaders doesn't support them */
+ break;
+ }
+
+ case PduHeaders.ELEMENT_DESCRIPTOR: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") ELEMENT_DESCRIPTOR");
+ }
+ parseContentType(pduDataStream, null);
+
+ /* not store this header filed in "headers",
+ because now PduHeaders doesn't support it */
+ break;
+ }
+
+ case PduHeaders.CONTENT_TYPE: {
+ SparseArray<Object> map = new SparseArray<Object>();
+ byte[] contentType =
+ parseContentType(pduDataStream, map);
+
+ if (null != contentType) {
+ try {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: headerField: " + headerField +
+ " (" + Integer.toHexString(headerField) + ") CONTENT_TYPE: "
+ + Arrays.toString(contentType));
+ }
+ headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+ } catch (NullPointerException e) {
+ log("null pointer error!");
+ } catch (RuntimeException e) {
+ log(headerField + "is not Text-String header field!");
+ return null;
+ }
+ }
+
+ /* get start parameter */
+ mStartParam = (byte[]) map.get(PduPart.P_START);
+
+ /* get charset parameter */
+ mTypeParam = (byte[]) map.get(PduPart.P_TYPE);
+
+ keepParsing = false;
+ break;
+ }
+
+ case PduHeaders.CONTENT:
+ case PduHeaders.ADDITIONAL_HEADERS:
+ case PduHeaders.ATTRIBUTES:
+ default: {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField +
+ " (" + Integer.toHexString(headerField) + ")");
+ }
+ log("Unknown header");
+ }
+ }
+ }
+
+ return headers;
+ }
+
+ /**
+ * Parse pdu parts.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return parts in PduBody structure
+ */
+ protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
+ if (pduDataStream == null) {
+ return null;
+ }
+
+ int count = parseUnsignedInt(pduDataStream); // get the number of parts
+ PduBody body = new PduBody();
+
+ for (int i = 0; i < count; i++) {
+ int headerLength = parseUnsignedInt(pduDataStream);
+ int dataLength = parseUnsignedInt(pduDataStream);
+ PduPart part = new PduPart();
+ int startPos = pduDataStream.available();
+ if (startPos <= 0) {
+ // Invalid part.
+ return null;
+ }
+
+ /* parse part's content-type */
+ SparseArray<Object> map = new SparseArray<Object>();
+ byte[] contentType = parseContentType(pduDataStream, map);
+ if (null != contentType) {
+ part.setContentType(contentType);
+ } else {
+ part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+ }
+
+ /* get name parameter */
+ byte[] name = (byte[]) map.get(PduPart.P_NAME);
+ if (null != name) {
+ part.setName(name);
+ }
+
+ /* get charset parameter */
+ Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+ if (null != charset) {
+ part.setCharset(charset);
+ }
+
+ /* parse part's headers */
+ int endPos = pduDataStream.available();
+ int partHeaderLen = headerLength - (startPos - endPos);
+ if (partHeaderLen > 0) {
+ if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+ // Parse part header faild.
+ return null;
+ }
+ } else if (partHeaderLen < 0) {
+ // Invalid length of content-type.
+ return null;
+ }
+
+ /* TODO: check content-id, name, filename and content location,
+ * if not set anyone of them, generate a default content-location
+ */
+ if ((null == part.getContentLocation())
+ && (null == part.getName())
+ && (null == part.getFilename())
+ && (null == part.getContentId())) {
+ part.setContentLocation(Long.toOctalString(
+ System.currentTimeMillis()).getBytes());
+ }
+
+ /* get part's data */
+ if (dataLength > 0) {
+ byte[] partData = new byte[dataLength];
+ String partContentType = new String(part.getContentType());
+ pduDataStream.read(partData, 0, dataLength);
+ if (partContentType.equalsIgnoreCase(ContentType.MMS_MULTIPART_ALTERNATIVE)) {
+ // parse "multipart/vnd.wap.multipart.alternative".
+ PduBody childBody = parseParts(new ByteArrayInputStream(partData));
+ // take the first part of children.
+ part = childBody.getPart(0);
+ } else {
+ // Check Content-Transfer-Encoding.
+ byte[] partDataEncoding = part.getContentTransferEncoding();
+ if (null != partDataEncoding) {
+ String encoding = new String(partDataEncoding);
+ if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+ // Decode "base64" into "binary".
+ partData = Base64.decodeBase64(partData);
+ } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+ // Decode "quoted-printable" into "binary".
+ partData = QuotedPrintable.decodeQuotedPrintable(partData);
+ } else {
+ // "binary" is the default encoding.
+ }
+ }
+ if (null == partData) {
+ log("Decode part data error!");
+ return null;
+ }
+ part.setData(partData);
+ }
+ }
+
+ /* add this part to body */
+ if (THE_FIRST_PART == checkPartPosition(part)) {
+ /* this is the first part */
+ body.addPart(0, part);
+ } else {
+ /* add the part to the end */
+ body.addPart(part);
+ }
+ }
+
+ return body;
+ }
+
+ /**
+ * Log status.
+ *
+ * @param text log information
+ */
+ private static void log(String text) {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, text);
+ }
+ }
+
+ /**
+ * Parse unsigned integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the integer, -1 when failed
+ */
+ protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * The maximum size of a uintvar is 32 bits.
+ * So it will be encoded in no more than 5 octets.
+ */
+ assert (null != pduDataStream);
+ int result = 0;
+ int temp = pduDataStream.read();
+ if (temp == -1) {
+ return temp;
+ }
+
+ while ((temp & 0x80) != 0) {
+ result = result << 7;
+ result |= temp & 0x7F;
+ temp = pduDataStream.read();
+ if (temp == -1) {
+ return temp;
+ }
+ }
+
+ result = result << 7;
+ result |= temp & 0x7F;
+
+ return result;
+ }
+
+ /**
+ * Parse value length.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the integer
+ */
+ protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Value-length = Short-length | (Length-quote Length)
+ * Short-length = <Any octet 0-30>
+ * Length-quote = <Octet 31>
+ * Length = Uintvar-integer
+ * Uintvar-integer = 1*5 OCTET
+ */
+ assert (null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ int first = temp & 0xFF;
+
+ if (first <= SHORT_LENGTH_MAX) {
+ return first;
+ } else if (first == LENGTH_QUOTE) {
+ return parseUnsignedInt(pduDataStream);
+ }
+
+ throw new RuntimeException("Value length > LENGTH_QUOTE!");
+ }
+
+ /**
+ * Parse encoded string value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the EncodedStringValue
+ */
+ protected static EncodedStringValue parseEncodedStringValue(
+ ByteArrayInputStream pduDataStream) {
+ /**
+ * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+ assert (null != pduDataStream);
+ pduDataStream.mark(1);
+ EncodedStringValue returnValue = null;
+ int charset = 0;
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ int first = temp & 0xFF;
+ if (first == 0) {
+ return null; // Blank subject, bail.
+ }
+
+ pduDataStream.reset();
+ if (first < TEXT_MIN) {
+ parseValueLength(pduDataStream);
+
+ charset = parseShortInteger(pduDataStream); //get the "Charset"
+ }
+
+ byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+ try {
+ if (0 != charset) {
+ returnValue = new EncodedStringValue(charset, textString);
+ } else {
+ returnValue = new EncodedStringValue(textString);
+ }
+ } catch (Exception e) {
+ return null;
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * Parse Text-String or Quoted-String.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+ * @return the string without End-of-string in byte array
+ */
+ protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+ int stringType) {
+ assert (null != pduDataStream);
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Text-string = [Quote] *TEXT End-of-string
+ * If the first character in the TEXT is in the range of 128-255,
+ * a Quote character must precede it.
+ * Otherwise the Quote character must be omitted.
+ * The Quote is not part of the contents.
+ * Quote = <Octet 127>
+ * End-of-string = <Octet 0>
+ *
+ * Quoted-string = <Octet 34> *TEXT End-of-string
+ *
+ * Token-text = Token End-of-string
+ */
+
+ // Mark supposed beginning of Text-string
+ // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+ pduDataStream.mark(1);
+
+ // Check first char
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ if ((TYPE_QUOTED_STRING == stringType) &&
+ (QUOTED_STRING_FLAG == temp)) {
+ // Mark again if QUOTED_STRING_FLAG and ignore it
+ pduDataStream.mark(1);
+ } else if ((TYPE_TEXT_STRING == stringType) &&
+ (QUOTE == temp)) {
+ // Mark again if QUOTE and ignore it
+ pduDataStream.mark(1);
+ } else {
+ // Otherwise go back to origin
+ pduDataStream.reset();
+ }
+
+ // We are now definitely at the beginning of string
+ /**
+ * Return *TOKEN or *TEXT (Text-String without QUOTE,
+ * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+ */
+ return getWapString(pduDataStream, stringType);
+ }
+
+ /**
+ * Check TOKEN data defined in RFC2616.
+ *
+ * @param ch checking data
+ * @return true when ch is TOKEN, false when ch is not TOKEN
+ */
+ protected static boolean isTokenCharacter(int ch) {
+ /**
+ * Token = 1*<any CHAR except CTLs or separators>
+ * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+ * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+ * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+ * | "{"(123) | "}"(125) | SP(32) | HT(9)
+ * CHAR = <any US-ASCII character (octets 0 - 127)>
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ */
+ if ((ch < 33) || (ch > 126)) {
+ return false;
+ }
+
+ switch (ch) {
+ case '"': /* '"' */
+ case '(': /* '(' */
+ case ')': /* ')' */
+ case ',': /* ',' */
+ case '/': /* '/' */
+ case ':': /* ':' */
+ case ';': /* ';' */
+ case '<': /* '<' */
+ case '=': /* '=' */
+ case '>': /* '>' */
+ case '?': /* '?' */
+ case '@': /* '@' */
+ case '[': /* '[' */
+ case '\\': /* '\' */
+ case ']': /* ']' */
+ case '{': /* '{' */
+ case '}': /* '}' */
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check TEXT data defined in RFC2616.
+ *
+ * @param ch checking data
+ * @return true when ch is TEXT, false when ch is not TEXT
+ */
+ protected static boolean isText(int ch) {
+ /**
+ * TEXT = <any OCTET except CTLs,
+ * but including LWS>
+ * CTL = <any US-ASCII control character
+ * (octets 0 - 31) and DEL (127)>
+ * LWS = [CRLF] 1*( SP | HT )
+ * CRLF = CR LF
+ * CR = <US-ASCII CR, carriage return (13)>
+ * LF = <US-ASCII LF, linefeed (10)>
+ */
+ if (((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+ return true;
+ }
+
+ switch (ch) {
+ case '\t': /* '\t' */
+ case '\n': /* '\n' */
+ case '\r': /* '\r' */
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+ int stringType) {
+ assert (null != pduDataStream);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ while ((-1 != temp) && ('\0' != temp)) {
+ // check each of the character
+ if (stringType == TYPE_TOKEN_STRING) {
+ if (isTokenCharacter(temp)) {
+ out.write(temp);
+ }
+ } else {
+ if (isText(temp)) {
+ out.write(temp);
+ }
+ }
+
+ temp = pduDataStream.read();
+ assert (-1 != temp);
+ }
+
+ if (out.size() > 0) {
+ return out.toByteArray();
+ }
+
+ return null;
+ }
+
+ /**
+ * Extract a byte value from the input stream.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the byte
+ */
+ protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+ assert (null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ return temp & 0xFF;
+ }
+
+ /**
+ * Parse Short-Integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return the byte
+ */
+ protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Short-integer = OCTET
+ * Integers in range 0-127 shall be encoded as a one
+ * octet value with the most significant bit set to one (1xxx xxxx)
+ * and with the value in the remaining least significant bits.
+ */
+ assert (null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ return temp & 0x7F;
+ }
+
+ /**
+ * Parse Long-Integer.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return long integer
+ */
+ protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Long-integer = Short-length Multi-octet-integer
+ * The Short-length indicates the length of the Multi-octet-integer
+ * Multi-octet-integer = 1*30 OCTET
+ * The content octets shall be an unsigned integer value
+ * with the most significant octet encoded first (big-endian representation).
+ * The minimum number of octets must be used to encode the value.
+ * Short-length = <Any octet 0-30>
+ */
+ assert (null != pduDataStream);
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ int count = temp & 0xFF;
+
+ if (count > LONG_INTEGER_LENGTH_MAX) {
+ throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+ }
+
+ long result = 0;
+
+ for (int i = 0; i < count; i++) {
+ temp = pduDataStream.read();
+ assert (-1 != temp);
+ result <<= 8;
+ result += (temp & 0xFF);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse Integer-Value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @return long integer
+ */
+ protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Integer-Value = Short-integer | Long-integer
+ */
+ assert (null != pduDataStream);
+ pduDataStream.mark(1);
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ pduDataStream.reset();
+ if (temp > SHORT_INTEGER_MAX) {
+ return parseShortInteger(pduDataStream);
+ } else {
+ return parseLongInteger(pduDataStream);
+ }
+ }
+
+ /**
+ * To skip length of the wap value.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param length area size
+ * @return the values in this area
+ */
+ protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+ assert (null != pduDataStream);
+ byte[] area = new byte[length];
+ int readLen = pduDataStream.read(area, 0, length);
+ if (readLen < length) { //The actually read length is lower than the length
+ return -1;
+ } else {
+ return readLen;
+ }
+ }
+
+ /**
+ * Parse content type parameters. For now we just support
+ * four parameters used in mms: "type", "start", "name", "charset".
+ *
+ * @param pduDataStream pdu data input stream
+ * @param map to store parameters of Content-Type field
+ * @param length length of all the parameters
+ */
+ protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+ SparseArray<Object> map, Integer length) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Parameter = Typed-parameter | Untyped-parameter
+ * Typed-parameter = Well-known-parameter-token Typed-value
+ * the actual expected type of the value is implied by the well-known parameter
+ * Well-known-parameter-token = Integer-value
+ * the code values used for parameters are specified in the Assigned Numbers appendix
+ * Typed-value = Compact-value | Text-value
+ * In addition to the expected type, there may be no value.
+ * If the value cannot be encoded using the expected type, it shall be encoded as text.
+ * Compact-value = Integer-value |
+ * Date-value | Delta-seconds-value | Q-value | Version-value |
+ * Uri-value
+ * Untyped-parameter = Token-text Untyped-value
+ * the type of the value is unknown, but it shall be encoded as an integer,
+ * if that is possible.
+ * Untyped-value = Integer-value | Text-value
+ */
+ assert (null != pduDataStream);
+ assert (length > 0);
+
+ int startPos = pduDataStream.available();
+ int tempPos = 0;
+ int lastLen = length;
+ while (0 < lastLen) {
+ int param = pduDataStream.read();
+ assert (-1 != param);
+ lastLen--;
+
+ switch (param) {
+ /**
+ * From rfc2387, chapter 3.1
+ * The type parameter must be specified and its value is the MIME media
+ * type of the "root" body part. It permits a MIME user agent to
+ * determine the content-type without reference to the enclosed body
+ * part. If the value of the type parameter and the root body part's
+ * content-type differ then the User Agent's behavior is undefined.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * type = Constrained-encoding
+ * Constrained-encoding = Extension-Media | Short-integer
+ * Extension-media = *TEXT End-of-string
+ */
+ case PduPart.P_TYPE:
+ case PduPart.P_CT_MR_TYPE:
+ pduDataStream.mark(1);
+ int first = extractByteValue(pduDataStream);
+ pduDataStream.reset();
+ if (first > TEXT_MAX) {
+ // Short-integer (well-known type)
+ int index = parseShortInteger(pduDataStream);
+
+ if (index < PduContentTypes.contentTypes.length) {
+ byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+ map.put(PduPart.P_TYPE, type);
+ } else {
+ //not support this type, ignore it.
+ }
+ } else {
+ // Text-String (extension-media)
+ byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != type) && (null != map)) {
+ map.put(PduPart.P_TYPE, type);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+ * Start Parameter Referring to Presentation
+ *
+ * From rfc2387, chapter 3.2
+ * The start parameter, if given, is the content-ID of the compound
+ * object's "root". If not present the "root" is the first body part in
+ * the Multipart/Related entity. The "root" is the element the
+ * applications processes first.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * start = Text-String
+ */
+ case PduPart.P_START:
+ case PduPart.P_DEP_START:
+ byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != start) && (null != map)) {
+ map.put(PduPart.P_START, start);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf
+ * In creation, the character set SHALL be either us-ascii
+ * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+ * In retrieval, both us-ascii and utf-8 SHALL be supported.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * charset = Well-known-charset|Text-String
+ * Well-known-charset = Any-charset | Integer-value
+ * Both are encoded using values from Character Set
+ * Assignments table in Assigned Numbers
+ * Any-charset = <Octet 128>
+ * Equivalent to the special RFC2616 charset value "*"
+ */
+ case PduPart.P_CHARSET:
+ pduDataStream.mark(1);
+ int firstValue = extractByteValue(pduDataStream);
+ pduDataStream.reset();
+ //Check first char
+ if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+ (END_STRING_FLAG == firstValue)) {
+ //Text-String (extension-charset)
+ byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ try {
+ int charsetInt = CharacterSets.getMibEnumValue(
+ new String(charsetStr));
+ map.put(PduPart.P_CHARSET, charsetInt);
+ } catch (UnsupportedEncodingException e) {
+ // Not a well-known charset, use "*".
+ Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+ map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+ }
+ } else {
+ //Well-known-charset
+ int charset = (int) parseIntegerValue(pduDataStream);
+ if (map != null) {
+ map.put(PduPart.P_CHARSET, charset);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf
+ * A name for multipart object SHALL be encoded using name-parameter
+ * for Content-Type header in WSP multipart headers.
+ *
+ * From wap-230-wsp-20010705-a.pdf
+ * name = Text-String
+ */
+ case PduPart.P_DEP_NAME:
+ case PduPart.P_NAME:
+ byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if ((null != name) && (null != map)) {
+ map.put(PduPart.P_NAME, name);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ default:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Content-Type parameter");
+ }
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Content-Type");
+ } else {
+ lastLen = 0;
+ }
+ break;
+ }
+ }
+
+ if (0 != lastLen) {
+ Log.e(LOG_TAG, "Corrupt Content-Type");
+ }
+ }
+
+ /**
+ * Parse content type.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param map to store parameters in Content-Type header field
+ * @return Content-Type value
+ */
+ protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+ SparseArray<Object> map) {
+ /**
+ * From wap-230-wsp-20010705-a.pdf
+ * Content-type-value = Constrained-media | Content-general-form
+ * Content-general-form = Value-length Media-type
+ * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+ */
+ assert (null != pduDataStream);
+
+ byte[] contentType = null;
+ pduDataStream.mark(1);
+ int temp = pduDataStream.read();
+ assert (-1 != temp);
+ pduDataStream.reset();
+
+ int cur = (temp & 0xFF);
+
+ if (cur < TEXT_MIN) {
+ int length = parseValueLength(pduDataStream);
+ int startPos = pduDataStream.available();
+ pduDataStream.mark(1);
+ temp = pduDataStream.read();
+ assert (-1 != temp);
+ pduDataStream.reset();
+ int first = (temp & 0xFF);
+
+ if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ } else if (first > TEXT_MAX) {
+ int index = parseShortInteger(pduDataStream);
+
+ if (index < PduContentTypes.contentTypes.length) { //well-known type
+ contentType = (PduContentTypes.contentTypes[index]).getBytes();
+ } else {
+ pduDataStream.reset();
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ }
+ } else {
+ Log.e(LOG_TAG, "Corrupt content-type");
+ return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+ }
+
+ int endPos = pduDataStream.available();
+ int parameterLen = length - (startPos - endPos);
+ if (parameterLen > 0) {//have parameters
+ parseContentTypeParams(pduDataStream, map, parameterLen);
+ }
+
+ if (parameterLen < 0) {
+ Log.e(LOG_TAG, "Corrupt MMS message");
+ return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+ }
+ } else if (cur <= TEXT_MAX) {
+ contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ } else {
+ contentType =
+ (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+ }
+
+ return contentType;
+ }
+
+ /**
+ * Parse part's headers.
+ *
+ * @param pduDataStream pdu data input stream
+ * @param part to store the header informations of the part
+ * @param length length of the headers
+ * @return true if parse successfully, false otherwise
+ */
+ protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+ PduPart part, int length) {
+ assert (null != pduDataStream);
+ assert (null != part);
+ assert (length > 0);
+
+ /**
+ * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+ * A name for multipart object SHALL be encoded using name-parameter
+ * for Content-Type header in WSP multipart headers.
+ * In decoding, name-parameter of Content-Type SHALL be used if available.
+ * If name-parameter of Content-Type is not available,
+ * filename parameter of Content-Disposition header SHALL be used if available.
+ * If neither name-parameter of Content-Type header nor filename parameter
+ * of Content-Disposition header is available,
+ * Content-Location header SHALL be used if available.
+ *
+ * Within SMIL part the reference to the media object parts SHALL use
+ * either Content-ID or Content-Location mechanism [RFC2557]
+ * and the corresponding WSP part headers in media object parts
+ * contain the corresponding definitions.
+ */
+ int startPos = pduDataStream.available();
+ int tempPos = 0;
+ int lastLen = length;
+ while (0 < lastLen) {
+ int header = pduDataStream.read();
+ assert (-1 != header);
+ lastLen--;
+
+ if (header > TEXT_MAX) {
+ // Number assigned headers.
+ switch (header) {
+ case PduPart.P_CONTENT_LOCATION:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-location-value = Uri-value
+ */
+ byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (null != contentLocation) {
+ part.setContentLocation(contentLocation);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ case PduPart.P_CONTENT_ID:
+ /**
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-ID-value = Quoted-string
+ */
+ byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+ if (null != contentId) {
+ part.setContentId(contentId);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ break;
+ case PduPart.P_DEP_CONTENT_DISPOSITION:
+ case PduPart.P_CONTENT_DISPOSITION:
+ /*
+ * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+ * Content-disposition-value = Value-length Disposition *(Parameter)
+ * Disposition = Form-data | Attachment | Inline | Token-text
+ * Form-data = <Octet 128>
+ * Attachment = <Octet 129>
+ * Inline = <Octet 130>
+ *
+ * some carrier mmsc servers do not support content_disposition
+ * field correctly
+ */
+ if (mParseContentDisposition) {
+ int len = parseValueLength(pduDataStream);
+ pduDataStream.mark(1);
+ int thisStartPos = pduDataStream.available();
+ int thisEndPos = 0;
+ int value = pduDataStream.read();
+
+ if (value == PduPart.P_DISPOSITION_FROM_DATA) {
+ part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+ } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+ part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+ } else if (value == PduPart.P_DISPOSITION_INLINE) {
+ part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+ } else {
+ pduDataStream.reset();
+ /* Token-text */
+ part.setContentDisposition(parseWapString(pduDataStream
+ , TYPE_TEXT_STRING));
+ }
+
+ /* get filename parameter and skip other parameters */
+ thisEndPos = pduDataStream.available();
+ if (thisStartPos - thisEndPos < len) {
+ value = pduDataStream.read();
+ if (value == PduPart.P_FILENAME) { //filename is text-string
+ part.setFilename(parseWapString(pduDataStream
+ , TYPE_TEXT_STRING));
+ }
+
+ /* skip other parameters */
+ thisEndPos = pduDataStream.available();
+ if (thisStartPos - thisEndPos < len) {
+ int last = len - (thisStartPos - thisEndPos);
+ byte[] temp = new byte[last];
+ pduDataStream.read(temp, 0, last);
+ }
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ }
+ break;
+ default:
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Part headers: " + header);
+ }
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+ lastLen = 0;
+ break;
+ }
+ } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+ // Not assigned header.
+ byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+ // Check the header whether it is "Content-Transfer-Encoding".
+ if (true ==
+ PduPart.CONTENT_TRANSFER_ENCODING
+ .equalsIgnoreCase(new String(tempHeader))) {
+ part.setContentTransferEncoding(tempValue);
+ }
+
+ tempPos = pduDataStream.available();
+ lastLen = length - (startPos - tempPos);
+ } else {
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "Not supported Part headers: " + header);
+ }
+ // Skip all headers of this part.
+ if (-1 == skipWapValue(pduDataStream, lastLen)) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+ lastLen = 0;
+ }
+ }
+
+ if (0 != lastLen) {
+ Log.e(LOG_TAG, "Corrupt Part headers");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check the position of a specified part.
+ *
+ * @param part the part to be checked
+ * @return part position, THE_FIRST_PART when it's the
+ * first one, THE_LAST_PART when it's the last one.
+ */
+ private static int checkPartPosition(PduPart part) {
+ assert (null != part);
+ if ((null == mTypeParam) &&
+ (null == mStartParam)) {
+ return THE_LAST_PART;
+ }
+
+ /* check part's content-id */
+ if (null != mStartParam) {
+ byte[] contentId = part.getContentId();
+ if (null != contentId) {
+ if (true == Arrays.equals(mStartParam, contentId)) {
+ return THE_FIRST_PART;
+ }
+ }
+ // This is not the first part, so append to end (keeping the original order)
+ // Check b/19607294 for details of this change
+ return THE_LAST_PART;
+ }
+
+ /* check part's content-type */
+ if (null != mTypeParam) {
+ byte[] contentType = part.getContentType();
+ if (null != contentType) {
+ if (true == Arrays.equals(mTypeParam, contentType)) {
+ return THE_FIRST_PART;
+ }
+ }
+ }
+
+ return THE_LAST_PART;
+ }
+
+ /**
+ * Check mandatory headers of a pdu.
+ *
+ * @param headers pdu headers
+ * @return true if the pdu has all of the mandatory headers, false otherwise.
+ */
+ protected static boolean checkMandatoryHeader(PduHeaders headers) {
+ if (null == headers) {
+ return false;
+ }
+
+ /* get message type */
+ int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+ /* check Mms-Version field */
+ int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+ if (0 == mmsVersion) {
+ // Every message should have Mms-Version field.
+ return false;
+ }
+
+ /* check mandatory header fields */
+ switch (messageType) {
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ // Content-Type field.
+ byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+ if (null == srContentType) {
+ return false;
+ }
+
+ // From field.
+ EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == srFrom) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == srTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ // Response-Status field.
+ int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+ if (0 == scResponseStatus) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == scTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ // Content-Location field.
+ byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+ if (null == niContentLocation) {
+ return false;
+ }
+
+ // Expiry field.
+ long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+ if (-1 == niExpiry) {
+ return false;
+ }
+
+ // Message-Class field.
+ byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+ if (null == niMessageClass) {
+ return false;
+ }
+
+ // Message-Size field.
+ long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ if (-1 == niMessageSize) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == niTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ // Status field.
+ int nriStatus = headers.getOctet(PduHeaders.STATUS);
+ if (0 == nriStatus) {
+ return false;
+ }
+
+ // Transaction-Id field.
+ byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == nriTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ // Content-Type field.
+ byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+ if (null == rcContentType) {
+ return false;
+ }
+
+ // Date field.
+ long rcDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == rcDate) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ // Date field.
+ long diDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == diDate) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == diMessageId) {
+ return false;
+ }
+
+ // Status field.
+ int diStatus = headers.getOctet(PduHeaders.STATUS);
+ if (0 == diStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == diTo) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ // Transaction-Id field.
+ byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+ if (null == aiTransactionId) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ // Date field.
+ long roDate = headers.getLongInteger(PduHeaders.DATE);
+ if (-1 == roDate) {
+ return false;
+ }
+
+ // From field.
+ EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == roFrom) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == roMessageId) {
+ return false;
+ }
+
+ // Read-Status field.
+ int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+ if (0 == roReadStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == roTo) {
+ return false;
+ }
+
+ break;
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ // From field.
+ EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+ if (null == rrFrom) {
+ return false;
+ }
+
+ // Message-Id field.
+ byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+ if (null == rrMessageId) {
+ return false;
+ }
+
+ // Read-Status field.
+ int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+ if (0 == rrReadStatus) {
+ return false;
+ }
+
+ // To field.
+ EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+ if (null == rrTo) {
+ return false;
+ }
+
+ break;
+ default:
+ // Parser doesn't support this message type in this version.
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/PduPart.java b/src/com/android/messaging/mmslib/pdu/PduPart.java
new file mode 100644
index 0000000..dcdb7a6
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduPart.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.mmslib.pdu;
+
+import android.net.Uri;
+import android.util.SparseArray;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+ /**
+ * Well-Known Parameters.
+ */
+ public static final int P_Q = 0x80;
+ public static final int P_CHARSET = 0x81;
+ public static final int P_LEVEL = 0x82;
+ public static final int P_TYPE = 0x83;
+ public static final int P_DEP_NAME = 0x85;
+ public static final int P_DEP_FILENAME = 0x86;
+ public static final int P_DIFFERENCES = 0x87;
+ public static final int P_PADDING = 0x88;
+ // This value of "TYPE" s used with Content-Type: multipart/related
+ public static final int P_CT_MR_TYPE = 0x89;
+ public static final int P_DEP_START = 0x8A;
+ public static final int P_DEP_START_INFO = 0x8B;
+ public static final int P_DEP_COMMENT = 0x8C;
+ public static final int P_DEP_DOMAIN = 0x8D;
+ public static final int P_MAX_AGE = 0x8E;
+ public static final int P_DEP_PATH = 0x8F;
+ public static final int P_SECURE = 0x90;
+ public static final int P_SEC = 0x91;
+ public static final int P_MAC = 0x92;
+ public static final int P_CREATION_DATE = 0x93;
+ public static final int P_MODIFICATION_DATE = 0x94;
+ public static final int P_READ_DATE = 0x95;
+ public static final int P_SIZE = 0x96;
+ public static final int P_NAME = 0x97;
+ public static final int P_FILENAME = 0x98;
+ public static final int P_START = 0x99;
+ public static final int P_START_INFO = 0x9A;
+ public static final int P_COMMENT = 0x9B;
+ public static final int P_DOMAIN = 0x9C;
+ public static final int P_PATH = 0x9D;
+
+ /**
+ * Header field names.
+ */
+ public static final int P_CONTENT_TYPE = 0x91;
+ public static final int P_CONTENT_LOCATION = 0x8E;
+ public static final int P_CONTENT_ID = 0xC0;
+ public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+ public static final int P_CONTENT_DISPOSITION = 0xC5;
+ // The next header is unassigned header, use reserved header(0x48) value.
+ public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+ /**
+ * Content=Transfer-Encoding string.
+ */
+ public static final String CONTENT_TRANSFER_ENCODING =
+ "Content-Transfer-Encoding";
+
+ /**
+ * Value of Content-Transfer-Encoding.
+ */
+ public static final String P_BINARY = "binary";
+ public static final String P_7BIT = "7bit";
+ public static final String P_8BIT = "8bit";
+ public static final String P_BASE64 = "base64";
+ public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+ /**
+ * Value of disposition can be set to PduPart when the value is octet in
+ * the PDU.
+ * "from-data" instead of Form-data<Octet 128>.
+ * "attachment" instead of Attachment<Octet 129>.
+ * "inline" instead of Inline<Octet 130>.
+ */
+ static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+ static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+ static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+ /**
+ * Content-Disposition value.
+ */
+ public static final int P_DISPOSITION_FROM_DATA = 0x80;
+ public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+ public static final int P_DISPOSITION_INLINE = 0x82;
+
+ /**
+ * Header of part.
+ */
+ private SparseArray<Object> mPartHeader = null;
+
+ /**
+ * Data uri.
+ */
+ private Uri mUri = null;
+
+ /**
+ * Part data.
+ */
+ private byte[] mPartData = null;
+
+ private static final String TAG = "PduPart";
+
+ /**
+ * Empty Constructor.
+ */
+ public PduPart() {
+ mPartHeader = new SparseArray<Object>();
+ }
+
+ /**
+ * Set part data. The data are stored as byte array.
+ *
+ * @param data the data
+ */
+ public void setData(final byte[] data) {
+ mPartData = data;
+ }
+
+ /**
+ * @return The part data or null if the data wasn't set or
+ * the data is stored as Uri.
+ * @see #getDataUri
+ */
+ public byte[] getData() {
+ return mPartData;
+ }
+
+ /**
+ * Set data uri. The data are stored as Uri.
+ *
+ * @param uri the uri
+ */
+ public void setDataUri(final Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * @return The Uri of the part data or null if the data wasn't set or
+ * the data is stored as byte array.
+ * @see #getData
+ */
+ public Uri getDataUri() {
+ return mUri;
+ }
+
+ /**
+ * Set Content-id value
+ *
+ * @param contentId the content-id value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentId(final byte[] contentId) {
+ if ((contentId == null) || (contentId.length == 0)) {
+ throw new IllegalArgumentException(
+ "Content-Id may not be null or empty.");
+ }
+
+ if ((contentId.length > 1)
+ && ((char) contentId[0] == '<')
+ && ((char) contentId[contentId.length - 1] == '>')) {
+ mPartHeader.put(P_CONTENT_ID, contentId);
+ return;
+ }
+
+ // Insert beginning '<' and trailing '>' for Content-Id.
+ final byte[] buffer = new byte[contentId.length + 2];
+ buffer[0] = (byte) (0xff & '<');
+ buffer[buffer.length - 1] = (byte) (0xff & '>');
+ System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+ mPartHeader.put(P_CONTENT_ID, buffer);
+ }
+
+ /**
+ * Get Content-id value.
+ *
+ * @return the value
+ */
+ public byte[] getContentId() {
+ return (byte[]) mPartHeader.get(P_CONTENT_ID);
+ }
+
+ /**
+ * Set Char-set value.
+ *
+ * @param charset the value
+ */
+ public void setCharset(final int charset) {
+ mPartHeader.put(P_CHARSET, charset);
+ }
+
+ /**
+ * Get Char-set value
+ *
+ * @return the charset value. Return 0 if charset was not set.
+ */
+ public int getCharset() {
+ final Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+ if (charset == null) {
+ return 0;
+ } else {
+ return charset.intValue();
+ }
+ }
+
+ /**
+ * Set Content-Location value.
+ *
+ * @param contentLocation the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentLocation(final byte[] contentLocation) {
+ if (contentLocation == null) {
+ throw new NullPointerException("null content-location");
+ }
+
+ mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+ }
+
+ /**
+ * Get Content-Location value.
+ *
+ * @return the value
+ * return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+ * return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+ * return PduPart.disposition[2] instead of <Octet 130> (Inline).
+ */
+ public byte[] getContentLocation() {
+ return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+ }
+
+ /**
+ * Set Content-Disposition value.
+ * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+ * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+ * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+ *
+ * @param contentDisposition the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentDisposition(final byte[] contentDisposition) {
+ if (contentDisposition == null) {
+ throw new NullPointerException("null content-disposition");
+ }
+
+ mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+ }
+
+ /**
+ * Get Content-Disposition value.
+ *
+ * @return the value
+ */
+ public byte[] getContentDisposition() {
+ return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+ }
+
+ /**
+ * Set Content-Type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentType(final byte[] contentType) {
+ if (contentType == null) {
+ throw new NullPointerException("null content-type");
+ }
+
+ mPartHeader.put(P_CONTENT_TYPE, contentType);
+ }
+
+ /**
+ * Get Content-Type value of part.
+ *
+ * @return the value
+ */
+ public byte[] getContentType() {
+ return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-Transfer-Encoding value
+ *
+ * @param contentId the content-id value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentTransferEncoding(final byte[] contentTransferEncoding) {
+ if (contentTransferEncoding == null) {
+ throw new NullPointerException("null content-transfer-encoding");
+ }
+
+ mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+ }
+
+ /**
+ * Get Content-Transfer-Encoding value.
+ *
+ * @return the value
+ */
+ public byte[] getContentTransferEncoding() {
+ return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+ }
+
+ /**
+ * Set Content-type parameter: name.
+ *
+ * @param name the name value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setName(final byte[] name) {
+ if (null == name) {
+ throw new NullPointerException("null content-id");
+ }
+
+ mPartHeader.put(P_NAME, name);
+ }
+
+ /**
+ * Get content-type parameter: name.
+ *
+ * @return the name
+ */
+ public byte[] getName() {
+ return (byte[]) mPartHeader.get(P_NAME);
+ }
+
+ /**
+ * Get Content-disposition parameter: filename
+ *
+ * @param fileName the filename value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFilename(final byte[] fileName) {
+ if (null == fileName) {
+ throw new NullPointerException("null content-id");
+ }
+
+ mPartHeader.put(P_FILENAME, fileName);
+ }
+
+ /**
+ * Set Content-disposition parameter: filename
+ *
+ * @return the filename
+ */
+ public byte[] getFilename() {
+ return (byte[]) mPartHeader.get(P_FILENAME);
+ }
+
+ public String generateLocation() {
+ // Assumption: At least one of the content-location / name / filename
+ // or content-id should be set. This is guaranteed by the PduParser
+ // for incoming messages and by MM composer for outgoing messages.
+ byte[] location = (byte[]) mPartHeader.get(P_NAME);
+ if (null == location) {
+ location = (byte[]) mPartHeader.get(P_FILENAME);
+
+ if (null == location) {
+ location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+ }
+ }
+
+ if (null == location) {
+ final byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+ return "cid:" + new String(contentId);
+ } else {
+ return new String(location);
+ }
+ }
+}
+
diff --git a/src/com/android/messaging/mmslib/pdu/PduPersister.java b/src/com/android/messaging/mmslib/pdu/PduPersister.java
new file mode 100644
index 0000000..ceb6a85
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/PduPersister.java
@@ -0,0 +1,1683 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.mmslib.pdu;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.SimpleArrayMap;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+import com.android.messaging.mmslib.MmsException;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.mmslib.util.DownloadDrmHelper;
+import com.android.messaging.mmslib.util.DrmConvertSession;
+import com.android.messaging.mmslib.util.PduCache;
+import com.android.messaging.mmslib.util.PduCacheEntry;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * This class is the high-level manager of PDU storage.
+ */
+public class PduPersister {
+ private static final String TAG = "PduPersister";
+ private static final boolean LOCAL_LOGV = false;
+
+ /**
+ * The uri of temporary drm objects.
+ */
+ public static final String TEMPORARY_DRM_OBJECT_URI =
+ "content://mms/" + Long.MAX_VALUE + "/part";
+
+ /**
+ * Indicate that we transiently failed to process a MM.
+ */
+ public static final int PROC_STATUS_TRANSIENT_FAILURE = 1;
+
+ /**
+ * Indicate that we permanently failed to process a MM.
+ */
+ public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
+
+ /**
+ * Indicate that we have successfully processed a MM.
+ */
+ public static final int PROC_STATUS_COMPLETED = 3;
+
+ public static final String BEGIN_VCARD = "BEGIN:VCARD";
+
+ private static PduPersister sPersister;
+
+ private static final PduCache PDU_CACHE_INSTANCE;
+
+ private static final int[] ADDRESS_FIELDS = new int[]{
+ PduHeaders.BCC,
+ PduHeaders.CC,
+ PduHeaders.FROM,
+ PduHeaders.TO
+ };
+
+ public static final String[] PDU_PROJECTION = new String[]{
+ Mms._ID,
+ Mms.MESSAGE_BOX,
+ Mms.THREAD_ID,
+ Mms.RETRIEVE_TEXT,
+ Mms.SUBJECT,
+ Mms.CONTENT_LOCATION,
+ Mms.CONTENT_TYPE,
+ Mms.MESSAGE_CLASS,
+ Mms.MESSAGE_ID,
+ Mms.RESPONSE_TEXT,
+ Mms.TRANSACTION_ID,
+ Mms.CONTENT_CLASS,
+ Mms.DELIVERY_REPORT,
+ Mms.MESSAGE_TYPE,
+ Mms.MMS_VERSION,
+ Mms.PRIORITY,
+ Mms.READ_REPORT,
+ Mms.READ_STATUS,
+ Mms.REPORT_ALLOWED,
+ Mms.RETRIEVE_STATUS,
+ Mms.STATUS,
+ Mms.DATE,
+ Mms.DELIVERY_TIME,
+ Mms.EXPIRY,
+ Mms.MESSAGE_SIZE,
+ Mms.SUBJECT_CHARSET,
+ Mms.RETRIEVE_TEXT_CHARSET,
+ Mms.READ,
+ Mms.SEEN,
+ };
+
+ public static final int PDU_COLUMN_ID = 0;
+ public static final int PDU_COLUMN_MESSAGE_BOX = 1;
+ public static final int PDU_COLUMN_THREAD_ID = 2;
+ public static final int PDU_COLUMN_RETRIEVE_TEXT = 3;
+ public static final int PDU_COLUMN_SUBJECT = 4;
+ public static final int PDU_COLUMN_CONTENT_LOCATION = 5;
+ public static final int PDU_COLUMN_CONTENT_TYPE = 6;
+ public static final int PDU_COLUMN_MESSAGE_CLASS = 7;
+ public static final int PDU_COLUMN_MESSAGE_ID = 8;
+ public static final int PDU_COLUMN_RESPONSE_TEXT = 9;
+ public static final int PDU_COLUMN_TRANSACTION_ID = 10;
+ public static final int PDU_COLUMN_CONTENT_CLASS = 11;
+ public static final int PDU_COLUMN_DELIVERY_REPORT = 12;
+ public static final int PDU_COLUMN_MESSAGE_TYPE = 13;
+ public static final int PDU_COLUMN_MMS_VERSION = 14;
+ public static final int PDU_COLUMN_PRIORITY = 15;
+ public static final int PDU_COLUMN_READ_REPORT = 16;
+ public static final int PDU_COLUMN_READ_STATUS = 17;
+ public static final int PDU_COLUMN_REPORT_ALLOWED = 18;
+ public static final int PDU_COLUMN_RETRIEVE_STATUS = 19;
+ public static final int PDU_COLUMN_STATUS = 20;
+ public static final int PDU_COLUMN_DATE = 21;
+ public static final int PDU_COLUMN_DELIVERY_TIME = 22;
+ public static final int PDU_COLUMN_EXPIRY = 23;
+ public static final int PDU_COLUMN_MESSAGE_SIZE = 24;
+ public static final int PDU_COLUMN_SUBJECT_CHARSET = 25;
+ public static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
+ public static final int PDU_COLUMN_READ = 27;
+ public static final int PDU_COLUMN_SEEN = 28;
+
+ private static final String[] PART_PROJECTION = new String[] {
+ Part._ID,
+ Part.CHARSET,
+ Part.CONTENT_DISPOSITION,
+ Part.CONTENT_ID,
+ Part.CONTENT_LOCATION,
+ Part.CONTENT_TYPE,
+ Part.FILENAME,
+ Part.NAME,
+ Part.TEXT
+ };
+
+ private static final int PART_COLUMN_ID = 0;
+ private static final int PART_COLUMN_CHARSET = 1;
+ private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
+ private static final int PART_COLUMN_CONTENT_ID = 3;
+ private static final int PART_COLUMN_CONTENT_LOCATION = 4;
+ private static final int PART_COLUMN_CONTENT_TYPE = 5;
+ private static final int PART_COLUMN_FILENAME = 6;
+ private static final int PART_COLUMN_NAME = 7;
+ private static final int PART_COLUMN_TEXT = 8;
+
+ private static final SimpleArrayMap<Uri, Integer> MESSAGE_BOX_MAP;
+
+ // These map are used for convenience in persist() and load().
+ private static final SparseIntArray CHARSET_COLUMN_INDEX_MAP;
+
+ private static final SparseIntArray ENCODED_STRING_COLUMN_INDEX_MAP;
+
+ private static final SparseIntArray TEXT_STRING_COLUMN_INDEX_MAP;
+
+ private static final SparseIntArray OCTET_COLUMN_INDEX_MAP;
+
+ private static final SparseIntArray LONG_COLUMN_INDEX_MAP;
+
+ private static final SparseArray<String> CHARSET_COLUMN_NAME_MAP;
+
+ private static final SparseArray<String> ENCODED_STRING_COLUMN_NAME_MAP;
+
+ private static final SparseArray<String> TEXT_STRING_COLUMN_NAME_MAP;
+
+ private static final SparseArray<String> OCTET_COLUMN_NAME_MAP;
+
+ private static final SparseArray<String> LONG_COLUMN_NAME_MAP;
+
+ static {
+ MESSAGE_BOX_MAP = new SimpleArrayMap<Uri, Integer>();
+ MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX);
+ MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT);
+ MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS);
+ MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
+
+ CHARSET_COLUMN_INDEX_MAP = new SparseIntArray();
+ CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
+ CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
+
+ CHARSET_COLUMN_NAME_MAP = new SparseArray<String>();
+ CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
+ CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
+
+ // Encoded string field code -> column index/name map.
+ ENCODED_STRING_COLUMN_INDEX_MAP = new SparseIntArray();
+ ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
+ ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
+
+ ENCODED_STRING_COLUMN_NAME_MAP = new SparseArray<String>();
+ ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
+ ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
+
+ // Text string field code -> column index/name map.
+ TEXT_STRING_COLUMN_INDEX_MAP = new SparseIntArray();
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
+ TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
+
+ TEXT_STRING_COLUMN_NAME_MAP = new SparseArray<String>();
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
+ TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
+
+ // Octet field code -> column index/name map.
+ OCTET_COLUMN_INDEX_MAP = new SparseIntArray();
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
+ OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
+
+ OCTET_COLUMN_NAME_MAP = new SparseArray<String>();
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
+ OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
+
+ // Long field code -> column index/name map.
+ LONG_COLUMN_INDEX_MAP = new SparseIntArray();
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
+ LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
+
+ LONG_COLUMN_NAME_MAP = new SparseArray<String>();
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
+ LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
+
+ PDU_CACHE_INSTANCE = PduCache.getInstance();
+ }
+
+ private final Context mContext;
+
+ private final ContentResolver mContentResolver;
+
+ private PduPersister(final Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ }
+
+ /** Get(or create if not exist) an instance of PduPersister */
+ public static PduPersister getPduPersister(final Context context) {
+ if ((sPersister == null) || !context.equals(sPersister.mContext)) {
+ sPersister = new PduPersister(context);
+ }
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "PduPersister getPduPersister");
+ }
+
+ return sPersister;
+ }
+
+ private void setEncodedStringValueToHeaders(
+ final Cursor c, final int columnIndex,
+ final PduHeaders headers, final int mapColumn) {
+ final String s = c.getString(columnIndex);
+ if ((s != null) && (s.length() > 0)) {
+ final int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
+ final int charset = c.getInt(charsetColumnIndex);
+ final EncodedStringValue value = new EncodedStringValue(
+ charset, getBytes(s));
+ headers.setEncodedStringValue(value, mapColumn);
+ }
+ }
+
+ private void setTextStringToHeaders(
+ final Cursor c, final int columnIndex,
+ final PduHeaders headers, final int mapColumn) {
+ final String s = c.getString(columnIndex);
+ if (s != null) {
+ headers.setTextString(getBytes(s), mapColumn);
+ }
+ }
+
+ private void setOctetToHeaders(
+ final Cursor c, final int columnIndex,
+ final PduHeaders headers, final int mapColumn) throws InvalidHeaderValueException {
+ if (!c.isNull(columnIndex)) {
+ final int b = c.getInt(columnIndex);
+ headers.setOctet(b, mapColumn);
+ }
+ }
+
+ private void setLongToHeaders(
+ final Cursor c, final int columnIndex,
+ final PduHeaders headers, final int mapColumn) {
+ if (!c.isNull(columnIndex)) {
+ final long l = c.getLong(columnIndex);
+ headers.setLongInteger(l, mapColumn);
+ }
+ }
+
+ private Integer getIntegerFromPartColumn(final Cursor c, final int columnIndex) {
+ if (!c.isNull(columnIndex)) {
+ return c.getInt(columnIndex);
+ }
+ return null;
+ }
+
+ private byte[] getByteArrayFromPartColumn(final Cursor c, final int columnIndex) {
+ if (!c.isNull(columnIndex)) {
+ return getBytes(c.getString(columnIndex));
+ }
+ return null;
+ }
+
+ private PduPart[] loadParts(final long msgId) throws MmsException {
+ final Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+ Uri.parse("content://mms/" + msgId + "/part"),
+ PART_PROJECTION, null, null, null);
+
+ PduPart[] parts = null;
+
+ try {
+ if ((c == null) || (c.getCount() == 0)) {
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "loadParts(" + msgId + "): no part to load.");
+ }
+ return null;
+ }
+
+ final int partCount = c.getCount();
+ int partIdx = 0;
+ parts = new PduPart[partCount];
+ while (c.moveToNext()) {
+ final PduPart part = new PduPart();
+ final Integer charset = getIntegerFromPartColumn(
+ c, PART_COLUMN_CHARSET);
+ if (charset != null) {
+ part.setCharset(charset);
+ }
+
+ final byte[] contentDisposition = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_DISPOSITION);
+ if (contentDisposition != null) {
+ part.setContentDisposition(contentDisposition);
+ }
+
+ final byte[] contentId = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_ID);
+ if (contentId != null) {
+ part.setContentId(contentId);
+ }
+
+ final byte[] contentLocation = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_LOCATION);
+ if (contentLocation != null) {
+ part.setContentLocation(contentLocation);
+ }
+
+ final byte[] contentType = getByteArrayFromPartColumn(
+ c, PART_COLUMN_CONTENT_TYPE);
+ if (contentType != null) {
+ part.setContentType(contentType);
+ } else {
+ throw new MmsException("Content-Type must be set.");
+ }
+
+ final byte[] fileName = getByteArrayFromPartColumn(
+ c, PART_COLUMN_FILENAME);
+ if (fileName != null) {
+ part.setFilename(fileName);
+ }
+
+ final byte[] name = getByteArrayFromPartColumn(
+ c, PART_COLUMN_NAME);
+ if (name != null) {
+ part.setName(name);
+ }
+
+ // Construct a Uri for this part.
+ final long partId = c.getLong(PART_COLUMN_ID);
+ final Uri partURI = Uri.parse("content://mms/part/" + partId);
+ part.setDataUri(partURI);
+
+ // For images/audio/video, we won't keep their data in Part
+ // because their renderer accept Uri as source.
+ final String type = toIsoString(contentType);
+ if (!ContentType.isImageType(type)
+ && !ContentType.isAudioType(type)
+ && !ContentType.isVideoType(type)) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ InputStream is = null;
+
+ // Store simple string values directly in the database instead of an
+ // external file. This makes the text searchable and retrieval slightly
+ // faster.
+ if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
+ || ContentType.TEXT_HTML.equals(type)) {
+ final String text = c.getString(PART_COLUMN_TEXT);
+ final byte[] blob = new EncodedStringValue(
+ charset != null ? charset : CharacterSets.DEFAULT_CHARSET,
+ text != null ? text : "")
+ .getTextString();
+ baos.write(blob, 0, blob.length);
+ } else {
+
+ try {
+ is = mContentResolver.openInputStream(partURI);
+
+ final byte[] buffer = new byte[256];
+ int len = is.read(buffer);
+ while (len >= 0) {
+ baos.write(buffer, 0, len);
+ len = is.read(buffer);
+ }
+ } catch (final IOException e) {
+ Log.e(TAG, "Failed to load part data", e);
+ c.close();
+ throw new MmsException(e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ Log.e(TAG, "Failed to close stream", e);
+ } // Ignore
+ }
+ }
+ }
+ part.setData(baos.toByteArray());
+ }
+ parts[partIdx++] = part;
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ return parts;
+ }
+
+ private void loadAddress(final long msgId, final PduHeaders headers) {
+ final Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+ Uri.parse("content://mms/" + msgId + "/addr"),
+ new String[]{Addr.ADDRESS, Addr.CHARSET, Addr.TYPE},
+ null, null, null);
+
+ if (c != null) {
+ try {
+ while (c.moveToNext()) {
+ final String addr = c.getString(0);
+ if (!TextUtils.isEmpty(addr)) {
+ final int addrType = c.getInt(2);
+ switch (addrType) {
+ case PduHeaders.FROM:
+ headers.setEncodedStringValue(
+ new EncodedStringValue(c.getInt(1), getBytes(addr)),
+ addrType);
+ break;
+ case PduHeaders.TO:
+ case PduHeaders.CC:
+ case PduHeaders.BCC:
+ headers.appendEncodedStringValue(
+ new EncodedStringValue(c.getInt(1), getBytes(addr)),
+ addrType);
+ break;
+ default:
+ Log.e(TAG, "Unknown address type: " + addrType);
+ break;
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+ }
+
+ /**
+ * Load a PDU from a given cursor
+ *
+ * @param c The cursor
+ * @return A parsed PDU from the database row
+ */
+ public GenericPdu load(final Cursor c) throws MmsException {
+ final PduHeaders headers = new PduHeaders();
+ final long msgId = c.getLong(PDU_COLUMN_ID);
+ // Fill in the headers from the PDU columns
+ loadHeadersFromCursor(c, headers);
+ // Load address information of the MM.
+ loadAddress(msgId, headers);
+ // Load parts for the PDU body
+ final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+ final PduBody body = loadBody(msgId, msgType);
+ return createPdu(msgType, headers, body);
+ }
+
+ /**
+ * Load a PDU from storage by given Uri.
+ *
+ * @param uri The Uri of the PDU to be loaded.
+ * @return A generic PDU object, it may be cast to dedicated PDU.
+ * @throws MmsException Failed to load some fields of a PDU.
+ */
+ public GenericPdu load(final Uri uri) throws MmsException {
+ GenericPdu pdu = null;
+ PduCacheEntry cacheEntry = null;
+ int msgBox = 0;
+ final long threadId = -1;
+ try {
+ synchronized (PDU_CACHE_INSTANCE) {
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "load: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "load: ", e);
+ }
+ }
+
+ // Check if the pdu is already loaded
+ cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+ if (cacheEntry != null) {
+ return cacheEntry.getPdu();
+ }
+
+ // Tell the cache to indicate to other callers that this item
+ // is currently being updated.
+ PDU_CACHE_INSTANCE.setUpdating(uri, true);
+ }
+
+ final Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
+ PDU_PROJECTION, null, null, null);
+ final PduHeaders headers = new PduHeaders();
+ final long msgId = ContentUris.parseId(uri);
+
+ try {
+ if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
+ return null; // MMS not found
+ }
+
+ msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
+ //threadId = c.getLong(PDU_COLUMN_THREAD_ID);
+ loadHeadersFromCursor(c, headers);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+
+ // Check whether 'msgId' has been assigned a valid value.
+ if (msgId == -1L) {
+ throw new MmsException("Error! ID of the message: -1.");
+ }
+
+ // Load address information of the MM.
+ loadAddress(msgId, headers);
+
+ final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+ final PduBody body = loadBody(msgId, msgType);
+ pdu = createPdu(msgType, headers, body);
+ } finally {
+ synchronized (PDU_CACHE_INSTANCE) {
+ if (pdu != null) {
+ Assert.isNull(PDU_CACHE_INSTANCE.get(uri), "Pdu exists for " + uri);
+ // Update the cache entry with the real info
+ cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
+ PDU_CACHE_INSTANCE.put(uri, cacheEntry);
+ }
+ PDU_CACHE_INSTANCE.setUpdating(uri, false);
+ PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
+ }
+ }
+ return pdu;
+ }
+
+ private void loadHeadersFromCursor(final Cursor c, final PduHeaders headers)
+ throws InvalidHeaderValueException {
+ for (int i = ENCODED_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
+ setEncodedStringValueToHeaders(
+ c, ENCODED_STRING_COLUMN_INDEX_MAP.valueAt(i), headers,
+ ENCODED_STRING_COLUMN_INDEX_MAP.keyAt(i));
+ }
+ for (int i = TEXT_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
+ setTextStringToHeaders(
+ c, TEXT_STRING_COLUMN_INDEX_MAP.valueAt(i), headers,
+ TEXT_STRING_COLUMN_INDEX_MAP.keyAt(i));
+ }
+ for (int i = OCTET_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
+ setOctetToHeaders(
+ c, OCTET_COLUMN_INDEX_MAP.valueAt(i), headers,
+ OCTET_COLUMN_INDEX_MAP.keyAt(i));
+ }
+ for (int i = LONG_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
+ setLongToHeaders(
+ c, LONG_COLUMN_INDEX_MAP.valueAt(i), headers,
+ LONG_COLUMN_INDEX_MAP.keyAt(i));
+ }
+ }
+
+ private GenericPdu createPdu(final int msgType, final PduHeaders headers, final PduBody body)
+ throws MmsException {
+ switch (msgType) {
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ return new NotificationInd(headers);
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ return new DeliveryInd(headers);
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+ return new ReadOrigInd(headers);
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ return new RetrieveConf(headers, body);
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ return new SendReq(headers, body);
+ case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+ return new AcknowledgeInd(headers);
+ case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+ return new NotifyRespInd(headers);
+ case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+ return new ReadRecInd(headers);
+ case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+ case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+ case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+ case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+ case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+ throw new MmsException(
+ "Unsupported PDU type: " + Integer.toHexString(msgType));
+
+ default:
+ throw new MmsException(
+ "Unrecognized PDU type: " + Integer.toHexString(msgType));
+ }
+ }
+
+ private PduBody loadBody(final long msgId, final int msgType) throws MmsException {
+ final PduBody body = new PduBody();
+
+ // For PDU which type is M_retrieve.conf or Send.req, we should
+ // load multiparts and put them into the body of the PDU.
+ if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+ || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+ final PduPart[] parts = loadParts(msgId);
+ if (parts != null) {
+ final int partsNum = parts.length;
+ for (int i = 0; i < partsNum; i++) {
+ body.addPart(parts[i]);
+ }
+ }
+ }
+
+ return body;
+ }
+
+ private void persistAddress(
+ final long msgId, final int type, final EncodedStringValue[] array) {
+ final ContentValues values = new ContentValues(3);
+
+ for (final EncodedStringValue addr : array) {
+ values.clear(); // Clear all values first.
+ values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
+ values.put(Addr.CHARSET, addr.getCharacterSet());
+ values.put(Addr.TYPE, type);
+
+ final Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
+ SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+ }
+ }
+
+ private static String getPartContentType(final PduPart part) {
+ return part.getContentType() == null ? null : toIsoString(part.getContentType());
+ }
+
+ private static void getValues(final PduPart part, final ContentValues values) {
+ byte[] bytes = part.getFilename();
+ if (bytes != null) {
+ values.put(Part.FILENAME, new String(bytes));
+ }
+
+ bytes = part.getName();
+ if (bytes != null) {
+ values.put(Part.NAME, new String(bytes));
+ }
+
+ bytes = part.getContentDisposition();
+ if (bytes != null) {
+ values.put(Part.CONTENT_DISPOSITION, toIsoString(bytes));
+ }
+
+ bytes = part.getContentId();
+ if (bytes != null) {
+ values.put(Part.CONTENT_ID, toIsoString(bytes));
+ }
+
+ bytes = part.getContentLocation();
+ if (bytes != null) {
+ values.put(Part.CONTENT_LOCATION, toIsoString(bytes));
+ }
+ }
+
+ public Uri persistPart(final PduPart part, final long msgId,
+ final Map<Uri, InputStream> preOpenedFiles) throws MmsException {
+ final Uri uri = Uri.parse("content://mms/" + msgId + "/part");
+ final ContentValues values = new ContentValues(8);
+
+ final int charset = part.getCharset();
+ if (charset != 0) {
+ values.put(Part.CHARSET, charset);
+ }
+
+ String contentType = getPartContentType(part);
+ final byte[] data = part.getData();
+
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "PduPersister.persistPart part: " + uri + " contentType: " +
+ contentType);
+ }
+
+ if (contentType != null) {
+ // There is no "image/jpg" in Android (and it's an invalid mimetype).
+ // Change it to "image/jpeg"
+ if (ContentType.IMAGE_JPG.equals(contentType)) {
+ contentType = ContentType.IMAGE_JPEG;
+ }
+
+ // On somes phones, a vcard comes in as text/plain instead of text/v-card.
+ // Fix it if necessary.
+ if (ContentType.TEXT_PLAIN.equals(contentType) && data != null) {
+ // There might be a more efficient way to just check the beginning of the string
+ // without encoding the whole thing, but we're concerned that with various
+ // characters sets, just comparing the byte data to BEGIN_VCARD would not be
+ // reliable.
+ final String encodedDataString = new EncodedStringValue(charset, data).getString();
+ if (encodedDataString != null && encodedDataString.startsWith(BEGIN_VCARD)) {
+ contentType = ContentType.TEXT_VCARD;
+ part.setContentType(contentType.getBytes());
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "PduPersister.persistPart part: " + uri + " contentType: " +
+ contentType + " changing to vcard");
+ }
+ }
+ }
+
+ values.put(Part.CONTENT_TYPE, contentType);
+ // To ensure the SMIL part is always the first part.
+ if (ContentType.APP_SMIL.equals(contentType)) {
+ values.put(Part.SEQ, -1);
+ }
+ } else {
+ throw new MmsException("MIME type of the part must be set.");
+ }
+
+ getValues(part, values);
+
+ Uri res = null;
+
+ try {
+ res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+ } catch (IllegalStateException e) {
+ // Currently the MMS provider throws an IllegalStateException when it's out of space
+ LogUtil.e(TAG, "SqliteWrapper.insert threw: ", e);
+ }
+
+ if (res == null) {
+ throw new MmsException("Failed to persist part, return null.");
+ }
+
+ persistData(part, res, contentType, preOpenedFiles);
+ // After successfully store the data, we should update
+ // the dataUri of the part.
+ part.setDataUri(res);
+
+ return res;
+ }
+
+ /**
+ * Save data of the part into storage. The source data may be given
+ * by a byte[] or a Uri. If it's a byte[], directly save it
+ * into storage, otherwise load source data from the dataUri and then
+ * save it. If the data is an image, we may scale down it according
+ * to user preference.
+ *
+ * @param part The PDU part which contains data to be saved.
+ * @param uri The URI of the part.
+ * @param contentType The MIME type of the part.
+ * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+ * @throws MmsException Cannot find source data or error occurred
+ * while saving the data.
+ */
+ private void persistData(final PduPart part, final Uri uri,
+ final String contentType, final Map<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ OutputStream os = null;
+ InputStream is = null;
+ DrmConvertSession drmConvertSession = null;
+ Uri dataUri = null;
+ String path = null;
+
+ try {
+ final byte[] data = part.getData();
+ final int charset = part.getCharset();
+ if (ContentType.TEXT_PLAIN.equals(contentType)
+ || ContentType.APP_SMIL.equals(contentType)
+ || ContentType.TEXT_HTML.equals(contentType)) {
+ // Some phone could send MMS with a text part having empty data
+ // Let's just skip those parts.
+ // EncodedStringValue() throws NPE if data is empty
+ if (data != null) {
+ final ContentValues cv = new ContentValues();
+ cv.put(Mms.Part.TEXT, new EncodedStringValue(charset, data).getString());
+ if (mContentResolver.update(uri, cv, null, null) != 1) {
+ throw new MmsException("unable to update " + uri.toString());
+ }
+ }
+ } else {
+ final boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
+ if (isDrm) {
+ if (uri != null) {
+ try {
+ path = convertUriToPath(mContext, uri);
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "drm uri: " + uri + " path: " + path);
+ }
+ final File f = new File(path);
+ final long len = f.length();
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "drm path: " + path + " len: " + len);
+ }
+ if (len > 0) {
+ // we're not going to re-persist and re-encrypt an already
+ // converted drm file
+ return;
+ }
+ } catch (final Exception e) {
+ Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
+ }
+ }
+ // We haven't converted the file yet, start the conversion
+ drmConvertSession = DrmConvertSession.open(mContext, contentType);
+ if (drmConvertSession == null) {
+ throw new MmsException("Mimetype " + contentType +
+ " can not be converted.");
+ }
+ }
+ // uri can look like:
+ // content://mms/part/98
+ os = mContentResolver.openOutputStream(uri);
+ if (os == null) {
+ throw new MmsException("Failed to create output stream on " + uri);
+ }
+ if (data == null) {
+ dataUri = part.getDataUri();
+ if ((dataUri == null) || (dataUri == uri)) {
+ Log.w(TAG, "Can't find data for this part.");
+ return;
+ }
+ // dataUri can look like:
+ // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715
+ if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
+ is = preOpenedFiles.get(dataUri);
+ }
+ if (is == null) {
+ is = mContentResolver.openInputStream(dataUri);
+ }
+ if (is == null) {
+ throw new MmsException("Failed to create input stream on " + dataUri);
+ }
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "Saving data to: " + uri);
+ }
+
+ final byte[] buffer = new byte[8192];
+ for (int len = 0; (len = is.read(buffer)) != -1; ) {
+ if (!isDrm) {
+ os.write(buffer, 0, len);
+ } else {
+ final byte[] convertedData = drmConvertSession.convert(buffer, len);
+ if (convertedData != null) {
+ os.write(convertedData, 0, convertedData.length);
+ } else {
+ throw new MmsException("Error converting drm data.");
+ }
+ }
+ }
+ } else {
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "Saving data to: " + uri);
+ }
+ if (!isDrm) {
+ os.write(data);
+ } else {
+ dataUri = uri;
+ final byte[] convertedData = drmConvertSession.convert(data, data.length);
+ if (convertedData != null) {
+ os.write(convertedData, 0, convertedData.length);
+ } else {
+ throw new MmsException("Error converting drm data.");
+ }
+ }
+ }
+ }
+ } catch (final SQLiteException e) {
+ Log.e(TAG, "Failed with SQLiteException.", e);
+ throw new MmsException(e);
+ } catch (final FileNotFoundException e) {
+ Log.e(TAG, "Failed to open Input/Output stream.", e);
+ throw new MmsException(e);
+ } catch (final IOException e) {
+ Log.e(TAG, "Failed to read/write data.", e);
+ throw new MmsException(e);
+ } finally {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (final IOException e) {
+ Log.e(TAG, "IOException while closing: " + os, e);
+ } // Ignore
+ }
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ Log.e(TAG, "IOException while closing: " + is, e);
+ } // Ignore
+ }
+ if (drmConvertSession != null) {
+ drmConvertSession.close(path);
+
+ // Reset the permissions on the encrypted part file so everyone has only read
+ // permission.
+ final File f = new File(path);
+ final ContentValues values = new ContentValues(0);
+ SqliteWrapper.update(mContext, mContentResolver,
+ Uri.parse("content://mms/resetFilePerm/" + f.getName()),
+ values, null, null);
+ }
+ }
+ }
+
+ /**
+ * This method expects uri in the following format
+ * content://media/<table_name>/<row_index> (or)
+ * file://sdcard/test.mp4
+ * http://test.com/test.mp4
+ *
+ * Here <table_name> shall be "video" or "audio" or "images"
+ * <row_index> the index of the content in given table
+ */
+ public static String convertUriToPath(final Context context, final Uri uri) {
+ String path = null;
+ if (null != uri) {
+ final String scheme = uri.getScheme();
+ if (null == scheme || scheme.equals("") ||
+ scheme.equals(ContentResolver.SCHEME_FILE)) {
+ path = uri.getPath();
+
+ } else if (scheme.equals("http")) {
+ path = uri.toString();
+
+ } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+ final String[] projection = new String[] {MediaStore.MediaColumns.DATA};
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(uri, projection, null,
+ null, null);
+ if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) {
+ throw new IllegalArgumentException("Given Uri could not be found" +
+ " in media store");
+ }
+ final int pathIndex =
+ cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
+ path = cursor.getString(pathIndex);
+ } catch (final SQLiteException e) {
+ throw new IllegalArgumentException("Given Uri is not formatted in a way " +
+ "so that it can be found in media store.");
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Given Uri scheme is not supported");
+ }
+ }
+ return path;
+ }
+
+ private void updateAddress(
+ final long msgId, final int type, final EncodedStringValue[] array) {
+ // Delete old address information and then insert new ones.
+ SqliteWrapper.delete(mContext, mContentResolver,
+ Uri.parse("content://mms/" + msgId + "/addr"),
+ Addr.TYPE + "=" + type, null);
+
+ persistAddress(msgId, type, array);
+ }
+
+ /**
+ * Update headers of a SendReq.
+ *
+ * @param uri The PDU which need to be updated.
+ * @param pdu New headers.
+ * @throws MmsException Bad URI or updating failed.
+ */
+ public void updateHeaders(final Uri uri, final SendReq sendReq) {
+ synchronized (PDU_CACHE_INSTANCE) {
+ // If the cache item is getting updated, wait until it's done updating before
+ // purging it.
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "updateHeaders: ", e);
+ }
+ }
+ }
+ PDU_CACHE_INSTANCE.purge(uri);
+
+ final ContentValues values = new ContentValues(10);
+ final byte[] contentType = sendReq.getContentType();
+ if (contentType != null) {
+ values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
+ }
+
+ final long date = sendReq.getDate();
+ if (date != -1) {
+ values.put(Mms.DATE, date);
+ }
+
+ final int deliveryReport = sendReq.getDeliveryReport();
+ if (deliveryReport != 0) {
+ values.put(Mms.DELIVERY_REPORT, deliveryReport);
+ }
+
+ final long expiry = sendReq.getExpiry();
+ if (expiry != -1) {
+ values.put(Mms.EXPIRY, expiry);
+ }
+
+ final byte[] msgClass = sendReq.getMessageClass();
+ if (msgClass != null) {
+ values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
+ }
+
+ final int priority = sendReq.getPriority();
+ if (priority != 0) {
+ values.put(Mms.PRIORITY, priority);
+ }
+
+ final int readReport = sendReq.getReadReport();
+ if (readReport != 0) {
+ values.put(Mms.READ_REPORT, readReport);
+ }
+
+ final byte[] transId = sendReq.getTransactionId();
+ if (transId != null) {
+ values.put(Mms.TRANSACTION_ID, toIsoString(transId));
+ }
+
+ final EncodedStringValue subject = sendReq.getSubject();
+ if (subject != null) {
+ values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
+ values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
+ } else {
+ values.put(Mms.SUBJECT, "");
+ }
+
+ final long messageSize = sendReq.getMessageSize();
+ if (messageSize > 0) {
+ values.put(Mms.MESSAGE_SIZE, messageSize);
+ }
+
+ final PduHeaders headers = sendReq.getPduHeaders();
+ final HashSet<String> recipients = new HashSet<String>();
+ for (final int addrType : ADDRESS_FIELDS) {
+ EncodedStringValue[] array = null;
+ if (addrType == PduHeaders.FROM) {
+ final EncodedStringValue v = headers.getEncodedStringValue(addrType);
+ if (v != null) {
+ array = new EncodedStringValue[1];
+ array[0] = v;
+ }
+ } else {
+ array = headers.getEncodedStringValues(addrType);
+ }
+
+ if (array != null) {
+ final long msgId = ContentUris.parseId(uri);
+ updateAddress(msgId, addrType, array);
+ if (addrType == PduHeaders.TO) {
+ for (final EncodedStringValue v : array) {
+ if (v != null) {
+ recipients.add(v.getString());
+ }
+ }
+ }
+ }
+ }
+ if (!recipients.isEmpty()) {
+ final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
+ values.put(Mms.THREAD_ID, threadId);
+ }
+
+ SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+ }
+
+
+ private void updatePart(final Uri uri, final PduPart part,
+ final Map<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ final ContentValues values = new ContentValues(7);
+
+ final int charset = part.getCharset();
+ if (charset != 0) {
+ values.put(Part.CHARSET, charset);
+ }
+
+ String contentType = null;
+ if (part.getContentType() != null) {
+ contentType = toIsoString(part.getContentType());
+ values.put(Part.CONTENT_TYPE, contentType);
+ } else {
+ throw new MmsException("MIME type of the part must be set.");
+ }
+
+ getValues(part, values);
+
+ SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+
+ // Only update the data when:
+ // 1. New binary data supplied or
+ // 2. The Uri of the part is different from the current one.
+ if ((part.getData() != null)
+ || (uri != part.getDataUri())) {
+ persistData(part, uri, contentType, preOpenedFiles);
+ }
+ }
+
+ /**
+ * Update all parts of a PDU.
+ *
+ * @param uri The PDU which need to be updated.
+ * @param body New message body of the PDU.
+ * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+ * @throws MmsException Bad URI or updating failed.
+ */
+ public void updateParts(final Uri uri, final PduBody body,
+ final Map<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ try {
+ PduCacheEntry cacheEntry;
+ synchronized (PDU_CACHE_INSTANCE) {
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "updateParts: ", e);
+ }
+ cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+ if (cacheEntry != null) {
+ ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
+ }
+ }
+ // Tell the cache to indicate to other callers that this item
+ // is currently being updated.
+ PDU_CACHE_INSTANCE.setUpdating(uri, true);
+ }
+
+ final ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
+ final ArrayMap<Uri, PduPart> toBeUpdated = new ArrayMap<Uri, PduPart>();
+
+ final int partsNum = body.getPartsNum();
+ final StringBuilder filter = new StringBuilder().append('(');
+ for (int i = 0; i < partsNum; i++) {
+ final PduPart part = body.getPart(i);
+ final Uri partUri = part.getDataUri();
+ if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) {
+ toBeCreated.add(part);
+ } else {
+ toBeUpdated.put(partUri, part);
+
+ // Don't use 'i > 0' to determine whether we should append
+ // 'AND' since 'i = 0' may be skipped in another branch.
+ if (filter.length() > 1) {
+ filter.append(" AND ");
+ }
+
+ filter.append(Part._ID);
+ filter.append("!=");
+ DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
+ }
+ }
+ filter.append(')');
+
+ final long msgId = ContentUris.parseId(uri);
+
+ // Remove the parts which doesn't exist anymore.
+ SqliteWrapper.delete(mContext, mContentResolver,
+ Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
+ filter.length() > 2 ? filter.toString() : null, null);
+
+ // Create new parts which didn't exist before.
+ for (final PduPart part : toBeCreated) {
+ persistPart(part, msgId, preOpenedFiles);
+ }
+
+ // Update the modified parts.
+ for (final Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
+ updatePart(e.getKey(), e.getValue(), preOpenedFiles);
+ }
+ } finally {
+ synchronized (PDU_CACHE_INSTANCE) {
+ PDU_CACHE_INSTANCE.setUpdating(uri, false);
+ PDU_CACHE_INSTANCE.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Persist a PDU object to specific location in the storage.
+ *
+ * @param pdu The PDU object to be stored.
+ * @param uri Where to store the given PDU object.
+ * @param subId Subscription id associated with this message.
+ * @param subPhoneNumber TODO
+ * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+ * @return A Uri which can be used to access the stored PDU.
+ */
+ public Uri persist(final GenericPdu pdu, final Uri uri, final int subId,
+ final String subPhoneNumber, final Map<Uri, InputStream> preOpenedFiles)
+ throws MmsException {
+ if (uri == null) {
+ throw new MmsException("Uri may not be null.");
+ }
+ long msgId = -1;
+ try {
+ msgId = ContentUris.parseId(uri);
+ } catch (final NumberFormatException e) {
+ // the uri ends with "inbox" or something else like that
+ }
+ final boolean existingUri = msgId != -1;
+
+ if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
+ throw new MmsException(
+ "Bad destination, must be one of "
+ + "content://mms/inbox, content://mms/sent, "
+ + "content://mms/drafts, content://mms/outbox, "
+ + "content://mms/temp."
+ );
+ }
+ synchronized (PDU_CACHE_INSTANCE) {
+ // If the cache item is getting updated, wait until it's done updating before
+ // purging it.
+ if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "persist: " + uri + " blocked by isUpdating()");
+ }
+ try {
+ PDU_CACHE_INSTANCE.wait();
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "persist1: ", e);
+ }
+ }
+ }
+ PDU_CACHE_INSTANCE.purge(uri);
+
+ final PduHeaders header = pdu.getPduHeaders();
+ PduBody body = null;
+ ContentValues values = new ContentValues();
+
+ // Mark new messages as seen in the telephony database so that we don't have to
+ // do a global "set all messages as seen" since that occasionally seems to be
+ // problematic (i.e. very slow). See bug 18189471.
+ values.put(Mms.SEEN, 1);
+
+ //Set<Entry<Integer, String>> set;
+
+ for (int i = ENCODED_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) {
+ final int field = ENCODED_STRING_COLUMN_NAME_MAP.keyAt(i);
+ final EncodedStringValue encodedString = header.getEncodedStringValue(field);
+ if (encodedString != null) {
+ final String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
+ values.put(ENCODED_STRING_COLUMN_NAME_MAP.valueAt(i),
+ toIsoString(encodedString.getTextString()));
+ values.put(charsetColumn, encodedString.getCharacterSet());
+ }
+ }
+
+ for (int i = TEXT_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) {
+ final byte[] text = header.getTextString(TEXT_STRING_COLUMN_NAME_MAP.keyAt(i));
+ if (text != null) {
+ values.put(TEXT_STRING_COLUMN_NAME_MAP.valueAt(i), toIsoString(text));
+ }
+ }
+
+ for (int i = OCTET_COLUMN_NAME_MAP.size(); --i >= 0; ) {
+ final int b = header.getOctet(OCTET_COLUMN_NAME_MAP.keyAt(i));
+ if (b != 0) {
+ values.put(OCTET_COLUMN_NAME_MAP.valueAt(i), b);
+ }
+ }
+
+ for (int i = LONG_COLUMN_NAME_MAP.size(); --i >= 0; ) {
+ final long l = header.getLongInteger(LONG_COLUMN_NAME_MAP.keyAt(i));
+ if (l != -1L) {
+ values.put(LONG_COLUMN_NAME_MAP.valueAt(i), l);
+ }
+ }
+
+ final SparseArray<EncodedStringValue[]> addressMap =
+ new SparseArray<EncodedStringValue[]>(ADDRESS_FIELDS.length);
+ // Save address information.
+ for (final int addrType : ADDRESS_FIELDS) {
+ EncodedStringValue[] array = null;
+ if (addrType == PduHeaders.FROM) {
+ final EncodedStringValue v = header.getEncodedStringValue(addrType);
+ if (v != null) {
+ array = new EncodedStringValue[1];
+ array[0] = v;
+ }
+ } else {
+ array = header.getEncodedStringValues(addrType);
+ }
+ addressMap.put(addrType, array);
+ }
+
+ final HashSet<String> recipients = new HashSet<String>();
+ final int msgType = pdu.getMessageType();
+ // Here we only allocate thread ID for M-Notification.ind,
+ // M-Retrieve.conf and M-Send.req.
+ // Some of other PDU types may be allocated a thread ID outside
+ // this scope.
+ if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
+ || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+ || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+ switch (msgType) {
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+ case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+ loadRecipients(PduHeaders.FROM, recipients, addressMap);
+
+ // For received messages (whether group MMS is enabled or not) we want to
+ // associate this message with the thread composed of all the recipients
+ // EXCLUDING our own number. This includes the person who sent the
+ // message (the FROM field above) in addition to the other people the message
+ // was addressed TO (or CC fields to address group messaging compatibility
+ // issues with devices that place numbers in this field). Typically our own
+ // number is in the TO/CC field so we have to remove it in loadRecipients.
+ checkAndLoadToCcRecipients(recipients, addressMap, subPhoneNumber);
+ break;
+ case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+ loadRecipients(PduHeaders.TO, recipients, addressMap);
+ break;
+ }
+ long threadId = -1L;
+ if (!recipients.isEmpty()) {
+ // Given all the recipients associated with this message, find (or create) the
+ // correct thread.
+ threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
+ } else {
+ LogUtil.w(TAG, "PduPersister.persist No recipients; persisting PDU to thread: "
+ + threadId);
+ }
+ values.put(Mms.THREAD_ID, threadId);
+ }
+
+ // Save parts first to avoid inconsistent message is loaded
+ // while saving the parts.
+ final long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
+
+ // Figure out if this PDU is a text-only message
+ boolean textOnly = true;
+
+ // Get body if the PDU is a RetrieveConf or SendReq.
+ if (pdu instanceof MultimediaMessagePdu) {
+ body = ((MultimediaMessagePdu) pdu).getBody();
+ // Start saving parts if necessary.
+ if (body != null) {
+ final int partsNum = body.getPartsNum();
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "PduPersister.persist partsNum: " + partsNum);
+ }
+ if (partsNum > 2) {
+ // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
+ // Down a few lines below we're checking to make sure we've only got SMIL or
+ // text. We also have to check then we don't have more than two parts.
+ // Otherwise, a slideshow with two text slides would be marked as textOnly.
+ textOnly = false;
+ }
+ for (int i = 0; i < partsNum; i++) {
+ final PduPart part = body.getPart(i);
+ persistPart(part, dummyId, preOpenedFiles);
+
+ // If we've got anything besides text/plain or SMIL part, then we've got
+ // an mms message with some other type of attachment.
+ final String contentType = getPartContentType(part);
+ if (LOCAL_LOGV) {
+ LogUtil.v(TAG, "PduPersister.persist part: " + i + " contentType: " +
+ contentType);
+ }
+ if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
+ && !ContentType.TEXT_PLAIN.equals(contentType)) {
+ textOnly = false;
+ }
+ }
+ }
+ }
+ // Record whether this mms message is a simple plain text or not. This is a hint for the
+ // UI.
+ if (OsUtil.isAtLeastJB_MR1()) {
+ values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
+ }
+
+ if (OsUtil.isAtLeastL_MR1()) {
+ values.put(Mms.SUBSCRIPTION_ID, subId);
+ } else {
+ Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId);
+ }
+
+ Uri res = null;
+ if (existingUri) {
+ res = uri;
+ SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
+ } else {
+ res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+ if (res == null) {
+ throw new MmsException("persist() failed: return null.");
+ }
+ // Get the real ID of the PDU and update all parts which were
+ // saved with the dummy ID.
+ msgId = ContentUris.parseId(res);
+ }
+
+ values = new ContentValues(1);
+ values.put(Part.MSG_ID, msgId);
+ SqliteWrapper.update(mContext, mContentResolver,
+ Uri.parse("content://mms/" + dummyId + "/part"),
+ values, null, null);
+ // We should return the longest URI of the persisted PDU, for
+ // example, if input URI is "content://mms/inbox" and the _ID of
+ // persisted PDU is '8', we should return "content://mms/inbox/8"
+ // instead of "content://mms/8".
+ // TODO: Should the MmsProvider be responsible for this???
+ if (!existingUri) {
+ res = Uri.parse(uri + "/" + msgId);
+ }
+
+ // Save address information.
+ for (final int addrType : ADDRESS_FIELDS) {
+ final EncodedStringValue[] array = addressMap.get(addrType);
+ if (array != null) {
+ persistAddress(msgId, addrType, array);
+ }
+ }
+
+ return res;
+ }
+
+ /**
+ * For a given address type, extract the recipients from the headers.
+ *
+ * @param addressType can be PduHeaders.FROM or PduHeaders.TO
+ * @param recipients a HashSet that is loaded with the recipients from the FROM or TO
+ * headers
+ * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
+ */
+ private void loadRecipients(final int addressType, final HashSet<String> recipients,
+ final SparseArray<EncodedStringValue[]> addressMap) {
+ final EncodedStringValue[] array = addressMap.get(addressType);
+ if (array == null) {
+ return;
+ }
+ for (final EncodedStringValue v : array) {
+ if (v != null) {
+ final String number = v.getString();
+ if (!recipients.contains(number)) {
+ // Only add numbers which aren't already included.
+ recipients.add(number);
+ }
+ }
+ }
+ }
+
+ /**
+ * For a given address type, extract the recipients from the headers.
+ *
+ * @param recipients a HashSet that is loaded with the recipients from the FROM or TO
+ * headers
+ * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
+ * @param selfNumber self phone number
+ */
+ private void checkAndLoadToCcRecipients(final HashSet<String> recipients,
+ final SparseArray<EncodedStringValue[]> addressMap, final String selfNumber) {
+ final EncodedStringValue[] arrayTo = addressMap.get(PduHeaders.TO);
+ final EncodedStringValue[] arrayCc = addressMap.get(PduHeaders.CC);
+ final ArrayList<String> numbers = new ArrayList<String>();
+ if (arrayTo != null) {
+ for (final EncodedStringValue v : arrayTo) {
+ if (v != null) {
+ numbers.add(v.getString());
+ }
+ }
+ }
+ if (arrayCc != null) {
+ for (final EncodedStringValue v : arrayCc) {
+ if (v != null) {
+ numbers.add(v.getString());
+ }
+ }
+ }
+ for (final String number : numbers) {
+ // Only add numbers which aren't my own number.
+ if (TextUtils.isEmpty(selfNumber) || !PhoneNumberUtils.compare(number, selfNumber)) {
+ if (!recipients.contains(number)) {
+ // Only add numbers which aren't already included.
+ recipients.add(number);
+ }
+ }
+ }
+ }
+
+ /**
+ * Move a PDU object from one location to another.
+ *
+ * @param from Specify the PDU object to be moved.
+ * @param to The destination location, should be one of the following:
+ * "content://mms/inbox", "content://mms/sent",
+ * "content://mms/drafts", "content://mms/outbox",
+ * "content://mms/trash".
+ * @return New Uri of the moved PDU.
+ * @throws MmsException Error occurred while moving the message.
+ */
+ public Uri move(final Uri from, final Uri to) throws MmsException {
+ // Check whether the 'msgId' has been assigned a valid value.
+ final long msgId = ContentUris.parseId(from);
+ if (msgId == -1L) {
+ throw new MmsException("Error! ID of the message: -1.");
+ }
+
+ // Get corresponding int value of destination box.
+ final Integer msgBox = MESSAGE_BOX_MAP.get(to);
+ if (msgBox == null) {
+ throw new MmsException(
+ "Bad destination, must be one of "
+ + "content://mms/inbox, content://mms/sent, "
+ + "content://mms/drafts, content://mms/outbox, "
+ + "content://mms/temp."
+ );
+ }
+
+ final ContentValues values = new ContentValues(1);
+ values.put(Mms.MESSAGE_BOX, msgBox);
+ SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
+ return ContentUris.withAppendedId(to, msgId);
+ }
+
+ /**
+ * Wrap a byte[] into a String.
+ */
+ public static String toIsoString(final byte[] bytes) {
+ try {
+ return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (final UnsupportedEncodingException e) {
+ // Impossible to reach here!
+ Log.e(TAG, "ISO_8859_1 must be supported!", e);
+ return "";
+ }
+ }
+
+ /**
+ * Unpack a given String into a byte[].
+ */
+ public static byte[] getBytes(final String data) {
+ try {
+ return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (final UnsupportedEncodingException e) {
+ // Impossible to reach here!
+ Log.e(TAG, "ISO_8859_1 must be supported!", e);
+ return new byte[0];
+ }
+ }
+
+ /**
+ * Remove all objects in the temporary path.
+ */
+ public void release() {
+ final Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
+ SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
+ }
+
+ /**
+ * Find all messages to be sent or downloaded before certain time.
+ */
+ public Cursor getPendingMessages(final long dueTime) {
+ final Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
+ uriBuilder.appendQueryParameter("protocol", "mms");
+
+ final String selection = PendingMessages.ERROR_TYPE + " < ?"
+ + " AND " + PendingMessages.DUE_TIME + " <= ?";
+
+ final String[] selectionArgs = new String[] {
+ String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
+ String.valueOf(dueTime)
+ };
+
+ return SqliteWrapper.query(mContext, mContentResolver,
+ uriBuilder.build(), null, selection, selectionArgs,
+ PendingMessages.DUE_TIME);
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java b/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..1ce9dc1
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/QuotedPrintable.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+ private static byte ESCAPE_CHAR = '=';
+
+ /**
+ * Decodes an array quoted-printable characters into an array of original bytes.
+ * Escaped characters are converted back to their original representation.
+ *
+ * <p>
+ * This function implements a subset of
+ * quoted-printable encoding specification (rule #1 and rule #2)
+ * as defined in RFC 1521.
+ * </p>
+ *
+ * @param bytes array of quoted-printable characters
+ * @return array of original bytes,
+ * null if quoted-printable decoding is unsuccessful.
+ */
+ public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ for (int i = 0; i < bytes.length; i++) {
+ int b = bytes[i];
+ if (b == ESCAPE_CHAR) {
+ try {
+ if ('\r' == (char) bytes[i + 1] &&
+ '\n' == (char) bytes[i + 2]) {
+ i += 2;
+ continue;
+ }
+ int u = Character.digit((char) bytes[++i], 16);
+ int l = Character.digit((char) bytes[++i], 16);
+ if (u == -1 || l == -1) {
+ return null;
+ }
+ buffer.write((char) ((u << 4) + l));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ } else {
+ buffer.write(b);
+ }
+ }
+ return buffer.toByteArray();
+ }
+}
diff --git a/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java b/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..198e4c3
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/ReadOrigInd.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+public class ReadOrigInd extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public ReadOrigInd() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ ReadOrigInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-MMS-Read-status value.
+ *
+ * @return the value
+ */
+ public int getReadStatus() {
+ return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Set X-MMS-Read-status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/pdu/ReadRecInd.java b/src/com/android/messaging/mmslib/pdu/ReadRecInd.java
new file mode 100644
index 0000000..c1a9ae4
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/ReadRecInd.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+public class ReadRecInd extends GenericPdu {
+ /**
+ * Constructor, used when composing a M-ReadRec.ind pdu.
+ *
+ * @param from the from value
+ * @param messageId the message ID value
+ * @param mmsVersion current viersion of mms
+ * @param readStatus the read status value
+ * @param to the to value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * @throws NullPointerException if messageId or to is null.
+ */
+ public ReadRecInd(EncodedStringValue from,
+ byte[] messageId,
+ int mmsVersion,
+ int readStatus,
+ EncodedStringValue[] to) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+ setFrom(from);
+ setMessageId(messageId);
+ setMmsVersion(mmsVersion);
+ setTo(to);
+ setReadStatus(readStatus);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ ReadRecInd(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Date value.
+ *
+ * @return the value
+ */
+ public long getDate() {
+ return mPduHeaders.getLongInteger(PduHeaders.DATE);
+ }
+
+ /**
+ * Set Date value.
+ *
+ * @param value the value
+ */
+ public void setDate(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get To value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getTo() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+ }
+
+ /**
+ * Set To value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-MMS-Read-status value.
+ *
+ * @return the value
+ */
+ public int getReadStatus() {
+ return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+ }
+
+ /**
+ * Set X-MMS-Read-status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/pdu/RetrieveConf.java b/src/com/android/messaging/mmslib/pdu/RetrieveConf.java
new file mode 100644
index 0000000..9e0faed
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/RetrieveConf.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+/**
+ * M-Retrive.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public RetrieveConf() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ RetrieveConf(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Constructor with given headers and body
+ *
+ * @param headers Headers for this PDU.
+ * @param body Body of this PDu.
+ */
+ RetrieveConf(PduHeaders headers, PduBody body) {
+ super(headers, body);
+ }
+
+ /**
+ * Get CC value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getCc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+ }
+
+ /**
+ * Add a "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addCc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+ }
+
+ /**
+ * Get Content-type value.
+ *
+ * @return the value
+ */
+ public byte[] getContentType() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentType(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report value.
+ *
+ * @return the value
+ */
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Get From value.
+ * From-value = Value-length
+ * (Address-present-token Encoded-string-value | Insert-address-token)
+ *
+ * @return the value
+ */
+ public EncodedStringValue getFrom() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+ }
+
+ /**
+ * Set From value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setFrom(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-Mms-Read-Report value.
+ *
+ * @return the value
+ */
+ public int getReadReport() {
+ return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Read-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Get X-Mms-Retrieve-Status value.
+ *
+ * @return the value
+ */
+ public int getRetrieveStatus() {
+ /*
+ * If the header is not there, assuming it is OK status.
+ * Some carriers may choose to not send this header.
+ */
+ return mPduHeaders.hasHeader(PduHeaders.RETRIEVE_STATUS) ?
+ mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS) : PduHeaders.RETRIEVE_STATUS_OK;
+ }
+
+ /**
+ * Set X-Mms-Retrieve-Status value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+ }
+
+ /**
+ * Get X-Mms-Retrieve-Text value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue getRetrieveText() {
+ return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+ }
+
+ /**
+ * Set X-Mms-Retrieve-Text value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setRetrieveText(EncodedStringValue value) {
+ mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id.
+ *
+ * @return the value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getContentClass() {return 0x00;}
+ * public void setApplicId(byte value) {}
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public byte getDistributionIndicator() {return 0x00;}
+ * public void setDistributionIndicator(byte value) {}
+ *
+ * public PreviouslySentByValue getPreviouslySentBy() {return null;}
+ * public void setPreviouslySentBy(PreviouslySentByValue value) {}
+ *
+ * public PreviouslySentDateValue getPreviouslySentDate() {}
+ * public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+ *
+ * public MmFlagsValue getMmFlags() {return null;}
+ * public void setMmFlags(MmFlagsValue value) {}
+ *
+ * public MmStateValue getMmState() {return null;}
+ * public void getMmState(MmStateValue value) {}
+ *
+ * public byte[] getReplaceId() {return 0x00;}
+ * public void setReplaceId(byte[] value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/pdu/SendConf.java b/src/com/android/messaging/mmslib/pdu/SendConf.java
new file mode 100644
index 0000000..cf1399e
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/SendConf.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 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.mmslib.pdu;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+public class SendConf extends GenericPdu {
+ /**
+ * Empty constructor.
+ * Since the Pdu corresponding to this class is constructed
+ * by the Proxy-Relay server, this class is only instantiated
+ * by the Pdu Parser.
+ *
+ * @throws InvalidHeaderValueException if error occurs.
+ */
+ public SendConf() throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ SendConf(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Get Message-ID value.
+ *
+ * @return the value
+ */
+ public byte[] getMessageId() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Set Message-ID value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+ }
+
+ /**
+ * Get X-Mms-Response-Status.
+ *
+ * @return the value
+ */
+ public int getResponseStatus() {
+ return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+ }
+
+ /**
+ * Set X-Mms-Response-Status.
+ *
+ * @param value the values
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setResponseStatus(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte[] getContentLocation() {return null;}
+ * public void setContentLocation(byte[] value) {}
+ *
+ * public EncodedStringValue getResponseText() {return null;}
+ * public void setResponseText(EncodedStringValue value) {}
+ *
+ * public byte getStoreStatus() {return 0x00;}
+ * public void setStoreStatus(byte value) {}
+ *
+ * public byte[] getStoreStatusText() {return null;}
+ * public void setStoreStatusText(byte[] value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/pdu/SendReq.java b/src/com/android/messaging/mmslib/pdu/SendReq.java
new file mode 100644
index 0000000..d173c0b
--- /dev/null
+++ b/src/com/android/messaging/mmslib/pdu/SendReq.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.mmslib.pdu;
+
+import android.util.Log;
+
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+
+public class SendReq extends MultimediaMessagePdu {
+ private static final String TAG = "SendReq";
+
+ public SendReq() {
+ super();
+
+ try {
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+ // TODO: Content-type must be decided according to whether
+ // SMIL part present.
+ setContentType("application/vnd.wap.multipart.related".getBytes());
+ setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+ setTransactionId(generateTransactionId());
+ } catch (InvalidHeaderValueException e) {
+ // Impossible to reach here since all headers we set above are valid.
+ Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private byte[] generateTransactionId() {
+ String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+ return transactionId.getBytes();
+ }
+
+ /**
+ * Constructor, used when composing a M-Send.req pdu.
+ *
+ * @param contentType the content type value
+ * @param from the from value
+ * @param mmsVersion current viersion of mms
+ * @param transactionId the transaction-id value
+ * @throws InvalidHeaderValueException if parameters are invalid.
+ * NullPointerException if contentType, form or
+ * transactionId is null.
+ */
+ public SendReq(byte[] contentType,
+ EncodedStringValue from,
+ int mmsVersion,
+ byte[] transactionId) throws InvalidHeaderValueException {
+ super();
+ setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+ setContentType(contentType);
+ setFrom(from);
+ setMmsVersion(mmsVersion);
+ setTransactionId(transactionId);
+ }
+
+ /**
+ * Constructor with given headers.
+ *
+ * @param headers Headers for this PDU.
+ */
+ SendReq(PduHeaders headers) {
+ super(headers);
+ }
+
+ /**
+ * Constructor with given headers and body
+ *
+ * @param headers Headers for this PDU.
+ * @param body Body of this PDu.
+ */
+ SendReq(PduHeaders headers, PduBody body) {
+ super(headers, body);
+ }
+
+ /**
+ * Get Bcc value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getBcc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+ }
+
+ /**
+ * Add a "BCC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addBcc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+ }
+
+ /**
+ * Set "BCC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setBcc(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+ }
+
+ /**
+ * Get CC value.
+ *
+ * @return the value
+ */
+ public EncodedStringValue[] getCc() {
+ return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+ }
+
+ /**
+ * Add a "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void addCc(EncodedStringValue value) {
+ mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+ }
+
+ /**
+ * Set "CC" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setCc(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+ }
+
+ /**
+ * Get Content-type value.
+ *
+ * @return the value
+ */
+ public byte[] getContentType() {
+ return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Set Content-type value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setContentType(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+ }
+
+ /**
+ * Get X-Mms-Delivery-Report value.
+ *
+ * @return the value
+ */
+ public int getDeliveryReport() {
+ return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Delivery-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+ }
+
+ /**
+ * Get X-Mms-Expiry value.
+ *
+ * Expiry-value = Value-length
+ * (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ *
+ * @return the value
+ */
+ public long getExpiry() {
+ return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Set X-Mms-Expiry value.
+ *
+ * @param value the value
+ */
+ public void setExpiry(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+ }
+
+ /**
+ * Get X-Mms-MessageSize value.
+ *
+ * Expiry-value = size of message
+ *
+ * @return the value
+ */
+ public long getMessageSize() {
+ return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Set X-Mms-MessageSize value.
+ *
+ * @param value the value
+ */
+ public void setMessageSize(long value) {
+ mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+ }
+
+ /**
+ * Get X-Mms-Message-Class value.
+ * Message-class-value = Class-identifier | Token-text
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ *
+ * @return the value
+ */
+ public byte[] getMessageClass() {
+ return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Set X-Mms-Message-Class value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setMessageClass(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+ }
+
+ /**
+ * Get X-Mms-Read-Report value.
+ *
+ * @return the value
+ */
+ public int getReadReport() {
+ return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set X-Mms-Read-Report value.
+ *
+ * @param value the value
+ * @throws InvalidHeaderValueException if the value is invalid.
+ */
+ public void setReadReport(int value) throws InvalidHeaderValueException {
+ mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+ }
+
+ /**
+ * Set "To" value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTo(EncodedStringValue[] value) {
+ mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+ }
+
+ /**
+ * Get X-Mms-Transaction-Id field value.
+ *
+ * @return the X-Mms-Report-Allowed value
+ */
+ public byte[] getTransactionId() {
+ return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+ }
+
+ /**
+ * Set X-Mms-Transaction-Id field value.
+ *
+ * @param value the value
+ * @throws NullPointerException if the value is null.
+ */
+ public void setTransactionId(byte[] value) {
+ mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+ }
+
+ /*
+ * Optional, not supported header fields:
+ *
+ * public byte getAdaptationAllowed() {return 0};
+ * public void setAdaptationAllowed(btye value) {};
+ *
+ * public byte[] getApplicId() {return null;}
+ * public void setApplicId(byte[] value) {}
+ *
+ * public byte[] getAuxApplicId() {return null;}
+ * public void getAuxApplicId(byte[] value) {}
+ *
+ * public byte getContentClass() {return 0x00;}
+ * public void setApplicId(byte value) {}
+ *
+ * public long getDeliveryTime() {return 0};
+ * public void setDeliveryTime(long value) {};
+ *
+ * public byte getDrmContent() {return 0x00;}
+ * public void setDrmContent(byte value) {}
+ *
+ * public MmFlagsValue getMmFlags() {return null;}
+ * public void setMmFlags(MmFlagsValue value) {}
+ *
+ * public MmStateValue getMmState() {return null;}
+ * public void getMmState(MmStateValue value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getReplyCharging() {return 0x00;}
+ * public void setReplyCharging(byte value) {}
+ *
+ * public byte getReplyChargingDeadline() {return 0x00;}
+ * public void setReplyChargingDeadline(byte value) {}
+ *
+ * public byte[] getReplyChargingId() {return 0x00;}
+ * public void setReplyChargingId(byte[] value) {}
+ *
+ * public long getReplyChargingSize() {return 0;}
+ * public void setReplyChargingSize(long value) {}
+ *
+ * public byte[] getReplyApplicId() {return 0x00;}
+ * public void setReplyApplicId(byte[] value) {}
+ *
+ * public byte getStore() {return 0x00;}
+ * public void setStore(byte value) {}
+ */
+}
diff --git a/src/com/android/messaging/mmslib/util/AbstractCache.java b/src/com/android/messaging/mmslib/util/AbstractCache.java
new file mode 100644
index 0000000..db98bb9
--- /dev/null
+++ b/src/com/android/messaging/mmslib/util/AbstractCache.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 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.mmslib.util;
+
+import android.support.v4.util.SimpleArrayMap;
+import android.util.Log;
+
+public abstract class AbstractCache<K, V> {
+ private static final String TAG = "AbstractCache";
+ private static final boolean LOCAL_LOGV = false;
+
+ private static final int MAX_CACHED_ITEMS = 500;
+
+ private final SimpleArrayMap<K, CacheEntry<V>> mCacheMap;
+
+ protected AbstractCache() {
+ mCacheMap = new SimpleArrayMap<K, CacheEntry<V>>();
+ }
+
+ public boolean put(K key, V value) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Trying to put " + key + " into cache.");
+ }
+
+ if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
+ // TODO: Should remove the oldest or least hit cached entry
+ // and then cache the new one.
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Failed! size limitation reached.");
+ }
+ return false;
+ }
+
+ if (key != null) {
+ CacheEntry<V> cacheEntry = new CacheEntry<V>();
+ cacheEntry.value = value;
+ mCacheMap.put(key, cacheEntry);
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public V get(K key) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Trying to get " + key + " from cache.");
+ }
+
+ if (key != null) {
+ CacheEntry<V> cacheEntry = mCacheMap.get(key);
+ if (cacheEntry != null) {
+ cacheEntry.hit++;
+ if (LOCAL_LOGV) {
+ Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
+ }
+ return cacheEntry.value;
+ }
+ }
+ return null;
+ }
+
+ public V purge(K key) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Trying to purge " + key);
+ }
+
+ CacheEntry<V> v = mCacheMap.remove(key);
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, mCacheMap.size() + " items cached.");
+ }
+
+ return v != null ? v.value : null;
+ }
+
+ public void purgeAll() {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Purging cache, " + mCacheMap.size()
+ + " items dropped.");
+ }
+ mCacheMap.clear();
+ }
+
+ public int size() {
+ return mCacheMap.size();
+ }
+
+ private static class CacheEntry<V> {
+
+ int hit;
+
+ V value;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java b/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java
new file mode 100644
index 0000000..c38b179
--- /dev/null
+++ b/src/com/android/messaging/mmslib/util/DownloadDrmHelper.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2012 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.mmslib.util;
+
+import android.content.Context;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+
+public class DownloadDrmHelper {
+ private static final String TAG = "DownloadDrmHelper";
+
+ /** The MIME type of special DRM files */
+ public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+ /** The extensions of special DRM files */
+ public static final String EXTENSION_DRM_MESSAGE = ".dm";
+
+ public static final String EXTENSION_INTERNAL_FWDL = ".fl";
+
+ /**
+ * Checks if the Media Type is a DRM Media Type
+ *
+ * @param drmManagerClient A DrmManagerClient
+ * @param mimetype Media Type to check
+ * @return True if the Media Type is DRM else false
+ */
+ public static boolean isDrmMimeType(Context context, String mimetype) {
+ boolean result = false;
+ if (context != null) {
+ try {
+ DrmManagerClient drmClient = new DrmManagerClient(context);
+ if (drmClient != null && mimetype != null && mimetype.length() > 0) {
+ result = drmClient.canHandle("", mimetype);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,
+ "DrmManagerClient instance could not be created, context is Illegal.");
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the Media Type needs to be DRM converted
+ *
+ * @param mimetype Media type of the content
+ * @return True if convert is needed else false
+ */
+ public static boolean isDrmConvertNeeded(String mimetype) {
+ return MIMETYPE_DRM_MESSAGE.equals(mimetype);
+ }
+
+ /**
+ * Modifies the file extension for a DRM Forward Lock file NOTE: This
+ * function shouldn't be called if the file shouldn't be DRM converted
+ */
+ public static String modifyDrmFwLockFileExtension(String filename) {
+ if (filename != null) {
+ int extensionIndex;
+ extensionIndex = filename.lastIndexOf(".");
+ if (extensionIndex != -1) {
+ filename = filename.substring(0, extensionIndex);
+ }
+ filename = filename.concat(EXTENSION_INTERNAL_FWDL);
+ }
+ return filename;
+ }
+
+ /**
+ * Gets the original mime type of DRM protected content.
+ *
+ * @param context The context
+ * @param path Path to the file
+ * @param containingMime The current mime type of of the file i.e. the
+ * containing mime type
+ * @return The original mime type of the file if DRM protected else the
+ * currentMime
+ */
+ public static String getOriginalMimeType(Context context, String path, String containingMime) {
+ String result = containingMime;
+ DrmManagerClient drmClient = new DrmManagerClient(context);
+ try {
+ if (drmClient.canHandle(path, null)) {
+ result = drmClient.getOriginalMimeType(path);
+ }
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG,
+ "Can't get original mime type since path is null or empty string.");
+ } catch (IllegalStateException ex) {
+ Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+ }
+ return result;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/util/DrmConvertSession.java b/src/com/android/messaging/mmslib/util/DrmConvertSession.java
new file mode 100644
index 0000000..604e391
--- /dev/null
+++ b/src/com/android/messaging/mmslib/util/DrmConvertSession.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 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.mmslib.util;
+
+import android.content.Context;
+import android.drm.DrmConvertedStatus;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+
+import com.android.messaging.mmslib.Downloads;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class DrmConvertSession {
+ private DrmManagerClient mDrmClient;
+ private int mConvertSessionId;
+ private static final String TAG = "DrmConvertSession";
+
+ private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
+ mDrmClient = drmClient;
+ mConvertSessionId = convertSessionId;
+ }
+
+ /**
+ * Start of converting a file.
+ *
+ * @param context The context of the application running the convert session.
+ * @param mimeType Mimetype of content that shall be converted.
+ * @return A convert session or null in case an error occurs.
+ */
+ public static DrmConvertSession open(Context context, String mimeType) {
+ DrmManagerClient drmClient = null;
+ int convertSessionId = -1;
+ if (context != null && mimeType != null && !mimeType.equals("")) {
+ try {
+ drmClient = new DrmManagerClient(context);
+ try {
+ convertSessionId = drmClient.openConvertSession(mimeType);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Conversion of Mimetype: " + mimeType
+ + " is not supported.", e);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not access Open DrmFramework.", e);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG,
+ "DrmManagerClient instance could not be created, context is Illegal.");
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+ }
+ }
+
+ if (drmClient == null || convertSessionId < 0) {
+ return null;
+ } else {
+ return new DrmConvertSession(drmClient, convertSessionId);
+ }
+ }
+
+ /**
+ * Convert a buffer of data to protected format.
+ *
+ * @param buffer Buffer filled with data to convert.
+ * @param size The number of bytes that shall be converted.
+ * @return A Buffer filled with converted data, if execution is ok, in all
+ * other case null.
+ */
+ public byte[] convert(byte[] inBuffer, int size) {
+ byte[] result = null;
+ if (inBuffer != null) {
+ DrmConvertedStatus convertedStatus = null;
+ try {
+ if (size != inBuffer.length) {
+ byte[] buf = new byte[size];
+ System.arraycopy(inBuffer, 0, buf, 0, size);
+ convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
+ } else {
+ convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
+ }
+
+ if (convertedStatus != null &&
+ convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
+ convertedStatus.convertedData != null) {
+ result = convertedStatus.convertedData;
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
+ + mConvertSessionId, e);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not convert data. Convertsession: " +
+ mConvertSessionId, e);
+ }
+ } else {
+ throw new IllegalArgumentException("Parameter inBuffer is null");
+ }
+ return result;
+ }
+
+ /**
+ * Ends a conversion session of a file.
+ *
+ * @param fileName The filename of the converted file.
+ * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
+ * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
+ * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
+ * occurs when accessing drm framework.
+ * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
+ */
+ public int close(String filename) {
+ DrmConvertedStatus convertedStatus = null;
+ int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
+ if (mDrmClient != null && mConvertSessionId >= 0) {
+ try {
+ convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
+ if (convertedStatus == null ||
+ convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
+ convertedStatus.convertedData == null) {
+ result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
+ } else {
+ RandomAccessFile rndAccessFile = null;
+ try {
+ rndAccessFile = new RandomAccessFile(filename, "rw");
+ rndAccessFile.seek(convertedStatus.offset);
+ rndAccessFile.write(convertedStatus.convertedData);
+ result = Downloads.Impl.STATUS_SUCCESS;
+ } catch (FileNotFoundException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "File: " + filename + " could not be found.", e);
+ } catch (IOException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "Could not access File: " + filename + " .", e);
+ } catch (IllegalArgumentException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "Could not open file in mode: rw", e);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Access to File: " + filename +
+ " was denied denied by SecurityManager.", e);
+ } finally {
+ if (rndAccessFile != null) {
+ try {
+ rndAccessFile.close();
+ } catch (IOException e) {
+ result = Downloads.Impl.STATUS_FILE_ERROR;
+ Log.w(TAG, "Failed to close File:" + filename
+ + ".", e);
+ }
+ }
+ }
+ }
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Could not close convertsession. Convertsession: " +
+ mConvertSessionId, e);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/com/android/messaging/mmslib/util/PduCache.java b/src/com/android/messaging/mmslib/util/PduCache.java
new file mode 100644
index 0000000..9a400c0
--- /dev/null
+++ b/src/com/android/messaging/mmslib/util/PduCache.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 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.mmslib.util;
+
+import android.content.ContentUris;
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.provider.Telephony.Mms;
+import android.support.v4.util.SimpleArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.HashSet;
+
+public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
+ private static final String TAG = "PduCache";
+ private static final boolean LOCAL_LOGV = false;
+
+ private static final int MMS_ALL = 0;
+ private static final int MMS_ALL_ID = 1;
+ private static final int MMS_INBOX = 2;
+ private static final int MMS_INBOX_ID = 3;
+ private static final int MMS_SENT = 4;
+ private static final int MMS_SENT_ID = 5;
+ private static final int MMS_DRAFTS = 6;
+ private static final int MMS_DRAFTS_ID = 7;
+ private static final int MMS_OUTBOX = 8;
+ private static final int MMS_OUTBOX_ID = 9;
+ private static final int MMS_CONVERSATION = 10;
+ private static final int MMS_CONVERSATION_ID = 11;
+
+ private static final UriMatcher URI_MATCHER;
+ private static final SparseArray<Integer> MATCH_TO_MSGBOX_ID_MAP;
+
+ private static PduCache sInstance;
+
+ static {
+ URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ URI_MATCHER.addURI("mms", null, MMS_ALL);
+ URI_MATCHER.addURI("mms", "#", MMS_ALL_ID);
+ URI_MATCHER.addURI("mms", "inbox", MMS_INBOX);
+ URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID);
+ URI_MATCHER.addURI("mms", "sent", MMS_SENT);
+ URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID);
+ URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS);
+ URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
+ URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX);
+ URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
+ URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION);
+ URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
+
+ MATCH_TO_MSGBOX_ID_MAP = new SparseArray<Integer>();
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX);
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT);
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
+ MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
+ }
+
+ private final SparseArray<HashSet<Uri>> mMessageBoxes;
+ private final SimpleArrayMap<Long, HashSet<Uri>> mThreads;
+ private final HashSet<Uri> mUpdating;
+
+ private PduCache() {
+ mMessageBoxes = new SparseArray<HashSet<Uri>>();
+ mThreads = new SimpleArrayMap<Long, HashSet<Uri>>();
+ mUpdating = new HashSet<Uri>();
+ }
+
+ public static final synchronized PduCache getInstance() {
+ if (sInstance == null) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Constructing new PduCache instance.");
+ }
+ sInstance = new PduCache();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public synchronized boolean put(Uri uri, PduCacheEntry entry) {
+ int msgBoxId = entry.getMessageBox();
+ HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+ if (msgBox == null) {
+ msgBox = new HashSet<Uri>();
+ mMessageBoxes.put(msgBoxId, msgBox);
+ }
+
+ long threadId = entry.getThreadId();
+ HashSet<Uri> thread = mThreads.get(threadId);
+ if (thread == null) {
+ thread = new HashSet<Uri>();
+ mThreads.put(threadId, thread);
+ }
+
+ Uri finalKey = normalizeKey(uri);
+ boolean result = super.put(finalKey, entry);
+ if (result) {
+ msgBox.add(finalKey);
+ thread.add(finalKey);
+ }
+ setUpdating(uri, false);
+ return result;
+ }
+
+ public synchronized void setUpdating(Uri uri, boolean updating) {
+ if (updating) {
+ mUpdating.add(uri);
+ } else {
+ mUpdating.remove(uri);
+ }
+ }
+
+ public synchronized boolean isUpdating(Uri uri) {
+ return mUpdating.contains(uri);
+ }
+
+ @Override
+ public synchronized PduCacheEntry purge(Uri uri) {
+ int match = URI_MATCHER.match(uri);
+ switch (match) {
+ case MMS_ALL_ID:
+ return purgeSingleEntry(uri);
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ String msgId = uri.getLastPathSegment();
+ return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
+ // Implicit batch of purge, return null.
+ case MMS_ALL:
+ case MMS_CONVERSATION:
+ purgeAll();
+ return null;
+ case MMS_INBOX:
+ case MMS_SENT:
+ case MMS_DRAFTS:
+ case MMS_OUTBOX:
+ purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
+ return null;
+ case MMS_CONVERSATION_ID:
+ purgeByThreadId(ContentUris.parseId(uri));
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ private PduCacheEntry purgeSingleEntry(Uri key) {
+ mUpdating.remove(key);
+ PduCacheEntry entry = super.purge(key);
+ if (entry != null) {
+ removeFromThreads(key, entry);
+ removeFromMessageBoxes(key, entry);
+ return entry;
+ }
+ return null;
+ }
+
+ @Override
+ public synchronized void purgeAll() {
+ super.purgeAll();
+
+ mMessageBoxes.clear();
+ mThreads.clear();
+ mUpdating.clear();
+ }
+
+ /**
+ * @param uri The Uri to be normalized.
+ * @return Uri The normalized key of cached entry.
+ */
+ private Uri normalizeKey(Uri uri) {
+ int match = URI_MATCHER.match(uri);
+ Uri normalizedKey = null;
+
+ switch (match) {
+ case MMS_ALL_ID:
+ normalizedKey = uri;
+ break;
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ String msgId = uri.getLastPathSegment();
+ normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
+ break;
+ default:
+ return null;
+ }
+
+ if (LOCAL_LOGV) {
+ Log.v(TAG, uri + " -> " + normalizedKey);
+ }
+ return normalizedKey;
+ }
+
+ private void purgeByMessageBox(Integer msgBoxId) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Purge cache in message box: " + msgBoxId);
+ }
+
+ if (msgBoxId != null) {
+ HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+ mMessageBoxes.remove(msgBoxId);
+ if (msgBox != null) {
+ for (Uri key : msgBox) {
+ mUpdating.remove(key);
+ PduCacheEntry entry = super.purge(key);
+ if (entry != null) {
+ removeFromThreads(key, entry);
+ }
+ }
+ }
+ }
+ }
+
+ private void removeFromThreads(Uri key, PduCacheEntry entry) {
+ HashSet<Uri> thread = mThreads.get(entry.getThreadId());
+ if (thread != null) {
+ thread.remove(key);
+ }
+ }
+
+ private void purgeByThreadId(long threadId) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Purge cache in thread: " + threadId);
+ }
+
+ HashSet<Uri> thread = mThreads.remove(threadId);
+ if (thread != null) {
+ for (Uri key : thread) {
+ mUpdating.remove(key);
+ PduCacheEntry entry = super.purge(key);
+ if (entry != null) {
+ removeFromMessageBoxes(key, entry);
+ }
+ }
+ }
+ }
+
+ private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
+ HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
+ if (msgBox != null) {
+ msgBox.remove(key);
+ }
+ }
+}
diff --git a/src/com/android/messaging/mmslib/util/PduCacheEntry.java b/src/com/android/messaging/mmslib/util/PduCacheEntry.java
new file mode 100644
index 0000000..b287f00
--- /dev/null
+++ b/src/com/android/messaging/mmslib/util/PduCacheEntry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 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.mmslib.util;
+
+import com.android.messaging.mmslib.pdu.GenericPdu;
+
+public final class PduCacheEntry {
+ private final GenericPdu mPdu;
+ private final int mMessageBox;
+ private final long mThreadId;
+
+ public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
+ mPdu = pdu;
+ mMessageBox = msgBox;
+ mThreadId = threadId;
+ }
+
+ public GenericPdu getPdu() {
+ return mPdu;
+ }
+
+ public int getMessageBox() {
+ return mMessageBox;
+ }
+
+ public long getThreadId() {
+ return mThreadId;
+ }
+}
diff --git a/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java b/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java
new file mode 100644
index 0000000..9e09a2a
--- /dev/null
+++ b/src/com/android/messaging/receiver/AbortMmsWapPushReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Telephony;
+
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * This receiver is used to abort MMS WAP broadcasts pre-KLP when SMS is enabled.
+ */
+public class AbortMmsWapPushReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction())
+ && ContentType.MMS_MESSAGE.equals(intent.getType())) {
+ // If we are enabled, it's our job to stop the broadcast from continuing. This
+ // receiver is not used on KLP but we do an extra check here just to make sure.
+ if (!OsUtil.isAtLeastKLP() && PhoneUtils.getDefault().isSmsEnabled()) {
+ abortBroadcast();
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/receiver/AbortSmsReceiver.java b/src/com/android/messaging/receiver/AbortSmsReceiver.java
new file mode 100644
index 0000000..f4491d8
--- /dev/null
+++ b/src/com/android/messaging/receiver/AbortSmsReceiver.java
@@ -0,0 +1,41 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * This receiver is used to abort SMS broadcasts pre-KLP when SMS is enabled.
+ */
+public final class AbortSmsReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // If we are enabled, it's our job to stop the broadcast from continuing. This
+ // receiver is not used on KLP but we do an extra check here just to make sure.
+ if (!OsUtil.isAtLeastKLP() && PhoneUtils.getDefault().isSmsEnabled()) {
+ if (!SmsReceiver.shouldIgnoreMessage(intent)) {
+ abortBroadcast();
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java b/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java
new file mode 100644
index 0000000..be0d296
--- /dev/null
+++ b/src/com/android/messaging/receiver/BootAndPackageReplacedReceiver.java
@@ -0,0 +1,49 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.messaging.BugleApplication;
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
+import com.android.messaging.util.BuglePrefsKeys;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Receives notification of boot completion and package replacement
+ */
+public class BootAndPackageReplacedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())
+ || Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
+ // Repost unseen notifications
+ Factory.get().getApplicationPrefs().putLong(
+ BuglePrefsKeys.LATEST_NOTIFICATION_MESSAGE_TIMESTAMP, Long.MIN_VALUE);
+ UpdateMessageNotificationAction.updateMessageNotification();
+
+ BugleApplication.updateAppConfig(context);
+ } else {
+ LogUtil.i(LogUtil.BUGLE_TAG, "BootAndPackageReplacedReceiver got unexpected action: "
+ + intent.getAction());
+ }
+ }
+}
+
diff --git a/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java b/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java
new file mode 100644
index 0000000..d5153a0
--- /dev/null
+++ b/src/com/android/messaging/receiver/DefaultSmsSubscriptionChangeReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.messaging.datamodel.ParticipantRefresh;
+
+/**
+ * Responds to default SMS subscription selection changes from system Settings.
+ */
+public class DefaultSmsSubscriptionChangeReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ParticipantRefresh.refreshSelfParticipants();
+ }
+}
diff --git a/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java b/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java
new file mode 100644
index 0000000..a5c247c
--- /dev/null
+++ b/src/com/android/messaging/receiver/MmsWapPushDeliverReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Telephony;
+
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Class that handles MMS WAP push intent from telephony on KLP+ Devices.
+ */
+public class MmsWapPushDeliverReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION.equals(intent.getAction())
+ && ContentType.MMS_MESSAGE.equals(intent.getType())) {
+ // Always convert negative subIds into -1
+ int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
+ intent, MmsWapPushReceiver.EXTRA_SUBSCRIPTION);
+ byte[] data = intent.getByteArrayExtra(MmsWapPushReceiver.EXTRA_DATA);
+ MmsWapPushReceiver.mmsReceived(subId, data);
+ }
+ }
+}
diff --git a/src/com/android/messaging/receiver/MmsWapPushReceiver.java b/src/com/android/messaging/receiver/MmsWapPushReceiver.java
new file mode 100644
index 0000000..29cf0db
--- /dev/null
+++ b/src/com/android/messaging/receiver/MmsWapPushReceiver.java
@@ -0,0 +1,58 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Telephony;
+
+import com.android.messaging.datamodel.action.ReceiveMmsMessageAction;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Class that handles MMS WAP push intent from telephony on pre-KLP Devices.
+ */
+public class MmsWapPushReceiver extends BroadcastReceiver {
+ static final String EXTRA_SUBSCRIPTION = "subscription";
+ static final String EXTRA_DATA = "data";
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION.equals(intent.getAction())
+ && ContentType.MMS_MESSAGE.equals(intent.getType())) {
+ if (PhoneUtils.getDefault().isSmsEnabled()) {
+ // Always convert negative subIds into -1
+ final int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
+ intent, MmsWapPushReceiver.EXTRA_SUBSCRIPTION);
+ final byte[] data = intent.getByteArrayExtra(MmsWapPushReceiver.EXTRA_DATA);
+ mmsReceived(subId, data);
+ }
+ }
+ }
+
+ static void mmsReceived(final int subId, final byte[] data) {
+ if (!PhoneUtils.getDefault().isSmsEnabled()) {
+ return;
+ }
+
+ final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction(subId, data);
+ action.start();
+ }
+}
+
diff --git a/src/com/android/messaging/receiver/NotificationReceiver.java b/src/com/android/messaging/receiver/NotificationReceiver.java
new file mode 100644
index 0000000..bbb847d
--- /dev/null
+++ b/src/com/android/messaging/receiver/NotificationReceiver.java
@@ -0,0 +1,57 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.action.MarkAsSeenAction;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.ConversationIdSet;
+import com.android.messaging.util.LogUtil;
+
+// NotificationReceiver is used to handle delete intents from notifications. When a user
+// clears all notifications or swipes a bugle notification away, the intent we pass in as
+// the delete intent will get handled here.
+public class NotificationReceiver extends BroadcastReceiver {
+ // Logging
+ public static final String TAG = LogUtil.BUGLE_TAG;
+ public static final boolean VERBOSE = false;
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (VERBOSE) {
+ LogUtil.v(TAG, "NotificationReceiver.onReceive: intent " + intent);
+ }
+ if (intent.getAction().equals(UIIntents.ACTION_RESET_NOTIFICATIONS)) {
+ final String conversationIdSetString =
+ intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID_SET);
+ final int notificationTargets = intent.getIntExtra(
+ UIIntents.UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, BugleNotifications.UPDATE_ALL);
+ if (conversationIdSetString == null) {
+ BugleNotifications.markAllMessagesAsSeen();
+ } else {
+ for (final String conversationId :
+ ConversationIdSet.createSet(conversationIdSetString)) {
+ MarkAsSeenAction.markAsSeen(conversationId);
+ BugleNotifications.resetLastMessageDing(conversationId);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/receiver/SendStatusReceiver.java b/src/com/android/messaging/receiver/SendStatusReceiver.java
new file mode 100644
index 0000000..fc0e8c9
--- /dev/null
+++ b/src/com/android/messaging/receiver/SendStatusReceiver.java
@@ -0,0 +1,96 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.telephony.SmsMessage;
+
+import com.android.messaging.datamodel.action.ProcessDeliveryReportAction;
+import com.android.messaging.datamodel.action.ProcessDownloadedMmsAction;
+import com.android.messaging.datamodel.action.ProcessSentMessageAction;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.sms.SmsSender;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * The SMS sent and delivery intent receiver.
+ *
+ * This class just simply forwards the intents to proper recipients for actual handling.
+ */
+public class SendStatusReceiver extends BroadcastReceiver {
+ public static final String MESSAGE_SENT_ACTION =
+ "com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT";
+ public static final String MESSAGE_DELIVERED_ACTION =
+ "com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED";
+ public static final String MMS_SENT_ACTION =
+ "com.android.messaging.receiver.SendStatusReceiver.MMS_SENT";
+ public static final String MMS_DOWNLOADED_ACTION =
+ "com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED";
+
+ // Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
+ public static final String EXTRA_ERROR_CODE = "errorCode";
+
+ public static final String EXTRA_PART_ID = "partId";
+ public static final String EXTRA_SUB_ID = "subId";
+
+ public static final int NO_ERROR_CODE = 0;
+ public static final int NO_PART_ID = -1;
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // This will be called on the main thread (so it should exit quickly)
+ final String action = intent.getAction();
+ final int resultCode = getResultCode();
+ if (MESSAGE_SENT_ACTION.equals(action)) {
+ final Uri requestId = intent.getData();
+ SmsSender.setResult(
+ requestId,
+ resultCode,
+ intent.getIntExtra(EXTRA_ERROR_CODE, NO_ERROR_CODE),
+ intent.getIntExtra(EXTRA_PART_ID, NO_PART_ID),
+ intent.getIntExtra(EXTRA_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID));
+ } else if (MMS_SENT_ACTION.equals(action)) {
+ final Uri messageUri = intent.getData();
+ ProcessSentMessageAction.processMmsSent(resultCode, messageUri,
+ intent.getExtras());
+ } else if (MMS_DOWNLOADED_ACTION.equals(action)) {
+ ProcessDownloadedMmsAction.processMessageDownloaded(resultCode,
+ intent.getExtras());
+ } else if (MESSAGE_DELIVERED_ACTION.equals(action)) {
+ final SmsMessage smsMessage = MmsUtils.getSmsMessageFromDeliveryReport(intent);
+ final Uri smsMessageUri = intent.getData();
+ if (smsMessage == null) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "SendStatusReceiver: empty report message");
+ return;
+ }
+ int status = 0;
+ try {
+ status = smsMessage.getStatus();
+ } catch (final NullPointerException e) {
+ // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
+ // the methods on it although the SmsMessage itself is not null.
+ LogUtil.e(LogUtil.BUGLE_TAG, "SendStatusReceiver: NPE inside SmsMessage");
+ return;
+ }
+ ProcessDeliveryReportAction.deliveryReportReceived(smsMessageUri, status);
+ }
+ }
+}
diff --git a/src/com/android/messaging/receiver/SmsDeliverReceiver.java b/src/com/android/messaging/receiver/SmsDeliverReceiver.java
new file mode 100644
index 0000000..6a9b66c
--- /dev/null
+++ b/src/com/android/messaging/receiver/SmsDeliverReceiver.java
@@ -0,0 +1,31 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Class that receives incoming SMS messages on KLP+ Devices.
+ */
+public final class SmsDeliverReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ SmsReceiver.deliverSmsIntent(context, intent);
+ }
+}
diff --git a/src/com/android/messaging/receiver/SmsReceiver.java b/src/com/android/messaging/receiver/SmsReceiver.java
new file mode 100644
index 0000000..db9b4bb
--- /dev/null
+++ b/src/com/android/messaging/receiver/SmsReceiver.java
@@ -0,0 +1,375 @@
+/*
+ * 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.receiver;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationCompat.Builder;
+import android.support.v4.app.NotificationCompat.Style;
+import android.support.v4.app.NotificationManagerCompat;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.MessageNotificationState;
+import com.android.messaging.datamodel.NoConfirmationSmsSendService;
+import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PendingIntentConstants;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Class that receives incoming SMS messages through android.provider.Telephony.SMS_RECEIVED
+ *
+ * This class serves two purposes:
+ * - Process phone verification SMS messages
+ * - Handle SMS messages when the user has enabled us to be the default SMS app (Pre-KLP)
+ */
+public final class SmsReceiver extends BroadcastReceiver {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static ArrayList<Pattern> sIgnoreSmsPatterns;
+
+ /**
+ * Enable or disable the SmsReceiver as appropriate. Pre-KLP we use this receiver for
+ * receiving incoming SMS messages. For KLP+ this receiver is not used when running as the
+ * primary user and the SmsDeliverReceiver is used for receiving incoming SMS messages.
+ * When running as a secondary user, this receiver is still used to trigger the incoming
+ * notification.
+ */
+ public static void updateSmsReceiveHandler(final Context context) {
+ boolean smsReceiverEnabled;
+ boolean mmsWapPushReceiverEnabled;
+ boolean respondViaMessageEnabled;
+ boolean broadcastAbortEnabled;
+
+ if (OsUtil.isAtLeastKLP()) {
+ // When we're running as the secondary user, we don't get the new SMS_DELIVER intent,
+ // only the primary user receives that. As secondary, we need to go old-school and
+ // listen for the SMS_RECEIVED intent. For the secondary user, use this SmsReceiver
+ // for both sms and mms notification. For the primary user on KLP (and above), we don't
+ // use the SmsReceiver.
+ smsReceiverEnabled = OsUtil.isSecondaryUser();
+ // On KLP use the new deliver event for mms
+ mmsWapPushReceiverEnabled = false;
+ // On KLP we need to always enable this handler to show in the list of sms apps
+ respondViaMessageEnabled = true;
+ // On KLP we don't need to abort the broadcast
+ broadcastAbortEnabled = false;
+ } else {
+ // On JB we use the sms receiver for both sms/mms delivery
+ final boolean carrierSmsEnabled = PhoneUtils.getDefault().isSmsEnabled();
+ smsReceiverEnabled = carrierSmsEnabled;
+
+ // On JB we use the mms receiver when sms/mms is enabled
+ mmsWapPushReceiverEnabled = carrierSmsEnabled;
+ // On JB this is dynamic to make sure we don't show in dialer if sms is disabled
+ respondViaMessageEnabled = carrierSmsEnabled;
+ // On JB we need to abort broadcasts if SMS is enabled
+ broadcastAbortEnabled = carrierSmsEnabled;
+ }
+
+ final PackageManager packageManager = context.getPackageManager();
+ final boolean logv = LogUtil.isLoggable(TAG, LogUtil.VERBOSE);
+ if (smsReceiverEnabled) {
+ if (logv) {
+ LogUtil.v(TAG, "Enabling SMS message receiving");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, SmsReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+
+ } else {
+ if (logv) {
+ LogUtil.v(TAG, "Disabling SMS message receiving");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, SmsReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+ if (mmsWapPushReceiverEnabled) {
+ if (logv) {
+ LogUtil.v(TAG, "Enabling MMS message receiving");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, MmsWapPushReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ } else {
+ if (logv) {
+ LogUtil.v(TAG, "Disabling MMS message receiving");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, MmsWapPushReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+ if (broadcastAbortEnabled) {
+ if (logv) {
+ LogUtil.v(TAG, "Enabling SMS/MMS broadcast abort");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, AbortSmsReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, AbortMmsWapPushReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ } else {
+ if (logv) {
+ LogUtil.v(TAG, "Disabling SMS/MMS broadcast abort");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, AbortSmsReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, AbortMmsWapPushReceiver.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+ if (respondViaMessageEnabled) {
+ if (logv) {
+ LogUtil.v(TAG, "Enabling respond via message intent");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, NoConfirmationSmsSendService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
+ } else {
+ if (logv) {
+ LogUtil.v(TAG, "Disabling respond via message intent");
+ }
+ packageManager.setComponentEnabledSetting(
+ new ComponentName(context, NoConfirmationSmsSendService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ }
+ }
+
+ private static final String EXTRA_ERROR_CODE = "errorCode";
+ private static final String EXTRA_SUB_ID = "subscription";
+
+ public static void deliverSmsIntent(final Context context, final Intent intent) {
+ final android.telephony.SmsMessage[] messages = getMessagesFromIntent(intent);
+
+ // Check messages for validity
+ if (messages == null || messages.length < 1) {
+ LogUtil.e(TAG, "processReceivedSms: null or zero or ignored message");
+ return;
+ }
+
+ final int errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, 0);
+ // Always convert negative subIds into -1
+ int subId = PhoneUtils.getDefault().getEffectiveIncomingSubIdFromSystem(
+ intent, EXTRA_SUB_ID);
+ deliverSmsMessages(context, subId, errorCode, messages);
+ if (MmsUtils.isDumpSmsEnabled()) {
+ final String format = null;
+ DebugUtils.dumpSms(messages[0].getTimestampMillis(), messages, format);
+ }
+ }
+
+ public static void deliverSmsMessages(final Context context, final int subId,
+ final int errorCode, final android.telephony.SmsMessage[] messages) {
+ final ContentValues messageValues =
+ MmsUtils.parseReceivedSmsMessage(context, messages, errorCode);
+
+ LogUtil.v(TAG, "SmsReceiver.deliverSmsMessages");
+
+ final long nowInMillis = System.currentTimeMillis();
+ final long receivedTimestampMs = MmsUtils.getMessageDate(messages[0], nowInMillis);
+
+ messageValues.put(Sms.Inbox.DATE, receivedTimestampMs);
+ // Default to unread and unseen for us but ReceiveSmsMessageAction will override
+ // seen for the telephony db.
+ messageValues.put(Sms.Inbox.READ, 0);
+ messageValues.put(Sms.Inbox.SEEN, 0);
+ if (OsUtil.isAtLeastL_MR1()) {
+ messageValues.put(Sms.SUBSCRIPTION_ID, subId);
+ }
+
+ if (messages[0].getMessageClass() == android.telephony.SmsMessage.MessageClass.CLASS_0 ||
+ DebugUtils.debugClassZeroSmsEnabled()) {
+ Factory.get().getUIIntents().launchClassZeroActivity(context, messageValues);
+ } else {
+ final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(messageValues);
+ action.start();
+ }
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ LogUtil.v(TAG, "SmsReceiver.onReceive " + intent);
+ // On KLP+ we only take delivery of SMS messages in SmsDeliverReceiver.
+ if (PhoneUtils.getDefault().isSmsEnabled()) {
+ final String action = intent.getAction();
+ if (OsUtil.isSecondaryUser() &&
+ (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action) ||
+ // TODO: update this with the actual constant from Telephony
+ "android.provider.Telephony.MMS_DOWNLOADED".equals(action))) {
+ postNewMessageSecondaryUserNotification();
+ } else if (!OsUtil.isAtLeastKLP()) {
+ deliverSmsIntent(context, intent);
+ }
+ }
+ }
+
+ private static class SecondaryUserNotificationState extends MessageNotificationState {
+ SecondaryUserNotificationState() {
+ super(null);
+ }
+
+ @Override
+ protected Style build(Builder builder) {
+ return null;
+ }
+
+ @Override
+ public boolean getNotificationVibrate() {
+ return true;
+ }
+ }
+
+ public static void postNewMessageSecondaryUserNotification() {
+ final Context context = Factory.get().getApplicationContext();
+ final Resources resources = context.getResources();
+ final PendingIntent pendingIntent = UIIntents.get()
+ .getPendingIntentForSecondaryUserNewMessageNotification(context);
+
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
+ builder.setContentTitle(resources.getString(R.string.secondary_user_new_message_title))
+ .setTicker(resources.getString(R.string.secondary_user_new_message_ticker))
+ .setSmallIcon(R.drawable.ic_sms_light)
+ // Returning PRIORITY_HIGH causes L to put up a HUD notification. Without it, the ticker
+ // isn't displayed.
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setContentIntent(pendingIntent);
+
+ final NotificationCompat.BigTextStyle bigTextStyle =
+ new NotificationCompat.BigTextStyle(builder);
+ bigTextStyle.bigText(resources.getString(R.string.secondary_user_new_message_title));
+ final Notification notification = bigTextStyle.build();
+
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(Factory.get().getApplicationContext());
+
+ int defaults = Notification.DEFAULT_LIGHTS;
+ if (BugleNotifications.shouldVibrate(new SecondaryUserNotificationState())) {
+ defaults |= Notification.DEFAULT_VIBRATE;
+ }
+ notification.defaults = defaults;
+
+ notificationManager.notify(getNotificationTag(),
+ PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancel the notification
+ */
+ public static void cancelSecondaryUserNotification() {
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(Factory.get().getApplicationContext());
+ notificationManager.cancel(getNotificationTag(),
+ PendingIntentConstants.SMS_SECONDARY_USER_NOTIFICATION_ID);
+ }
+
+ private static String getNotificationTag() {
+ return Factory.get().getApplicationContext().getPackageName() + ":secondaryuser";
+ }
+
+ /**
+ * Compile all of the patterns we check for to ignore system SMS messages.
+ */
+ private static void compileIgnoreSmsPatterns() {
+ // Get the pattern set from GServices
+ final String smsIgnoreRegex = BugleGservices.get().getString(
+ BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX,
+ BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT);
+ if (smsIgnoreRegex != null) {
+ final String[] ignoreSmsExpressions = smsIgnoreRegex.split("\n");
+ if (ignoreSmsExpressions.length != 0) {
+ sIgnoreSmsPatterns = new ArrayList<Pattern>();
+ for (int i = 0; i < ignoreSmsExpressions.length; i++) {
+ try {
+ sIgnoreSmsPatterns.add(Pattern.compile(ignoreSmsExpressions[i]));
+ } catch (PatternSyntaxException e) {
+ LogUtil.e(TAG, "compileIgnoreSmsPatterns: Skipping bad expression: " +
+ ignoreSmsExpressions[i]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the SMS messages from the specified SMS intent.
+ * @return the messages. If there is an error or the message should be ignored, return null.
+ */
+ public static android.telephony.SmsMessage[] getMessagesFromIntent(Intent intent) {
+ final android.telephony.SmsMessage[] messages = Sms.Intents.getMessagesFromIntent(intent);
+
+ // Check messages for validity
+ if (messages == null || messages.length < 1) {
+ return null;
+ }
+ // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
+ // the methods on it although the SmsMessage itself is not null. So do this check
+ // before we do anything on the parsed SmsMessages.
+ try {
+ final String messageBody = messages[0].getDisplayMessageBody();
+ if (messageBody != null) {
+ // Compile patterns if necessary
+ if (sIgnoreSmsPatterns == null) {
+ compileIgnoreSmsPatterns();
+ }
+ // Check against filters
+ for (final Pattern pattern : sIgnoreSmsPatterns) {
+ if (pattern.matcher(messageBody).matches()) {
+ return null;
+ }
+ }
+ }
+ } catch (final NullPointerException e) {
+ LogUtil.e(TAG, "shouldIgnoreMessage: NPE inside SmsMessage");
+ return null;
+ }
+ return messages;
+ }
+
+
+ /**
+ * Check the specified SMS intent to see if the message should be ignored
+ * @return true if the message should be ignored
+ */
+ public static boolean shouldIgnoreMessage(Intent intent) {
+ return getMessagesFromIntent(intent) == null;
+ }
+}
diff --git a/src/com/android/messaging/receiver/StorageStatusReceiver.java b/src/com/android/messaging/receiver/StorageStatusReceiver.java
new file mode 100644
index 0000000..bee899c
--- /dev/null
+++ b/src/com/android/messaging/receiver/StorageStatusReceiver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.messaging.sms.SmsStorageStatusManager;
+
+/**
+ * Receiver that listens on storage status changes
+ */
+public class StorageStatusReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
+ SmsStorageStatusManager.handleStorageLow();
+ } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
+ SmsStorageStatusManager.handleStorageOk();
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/ApnDatabase.java b/src/com/android/messaging/sms/ApnDatabase.java
new file mode 100644
index 0000000..a8d0d0c
--- /dev/null
+++ b/src/com/android/messaging/sms/ApnDatabase.java
@@ -0,0 +1,374 @@
+/*
+ * 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.sms;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.Telephony;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * Database helper class for looking up APNs. This database has a single table
+ * which stores the APNs that are initially created from an xml file.
+ */
+public class ApnDatabase extends SQLiteOpenHelper {
+ private static final int DB_VERSION = 3; // added sub_id columns
+
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final boolean DEBUG = false;
+
+ private static Context sContext;
+ private static ApnDatabase sApnDatabase;
+
+ private static final String APN_DATABASE_NAME = "apn.db";
+
+ /** table for carrier APN's */
+ public static final String APN_TABLE = "apn";
+
+ // APN table
+ private static final String APN_TABLE_SQL =
+ "CREATE TABLE " + APN_TABLE +
+ "(_id INTEGER PRIMARY KEY," +
+ Telephony.Carriers.NAME + " TEXT," +
+ Telephony.Carriers.NUMERIC + " TEXT," +
+ Telephony.Carriers.MCC + " TEXT," +
+ Telephony.Carriers.MNC + " TEXT," +
+ Telephony.Carriers.APN + " TEXT," +
+ Telephony.Carriers.USER + " TEXT," +
+ Telephony.Carriers.SERVER + " TEXT," +
+ Telephony.Carriers.PASSWORD + " TEXT," +
+ Telephony.Carriers.PROXY + " TEXT," +
+ Telephony.Carriers.PORT + " TEXT," +
+ Telephony.Carriers.MMSPROXY + " TEXT," +
+ Telephony.Carriers.MMSPORT + " TEXT," +
+ Telephony.Carriers.MMSC + " TEXT," +
+ Telephony.Carriers.AUTH_TYPE + " INTEGER," +
+ Telephony.Carriers.TYPE + " TEXT," +
+ Telephony.Carriers.CURRENT + " INTEGER," +
+ Telephony.Carriers.PROTOCOL + " TEXT," +
+ Telephony.Carriers.ROAMING_PROTOCOL + " TEXT," +
+ Telephony.Carriers.CARRIER_ENABLED + " BOOLEAN," +
+ Telephony.Carriers.BEARER + " INTEGER," +
+ Telephony.Carriers.MVNO_TYPE + " TEXT," +
+ Telephony.Carriers.MVNO_MATCH_DATA + " TEXT," +
+ Telephony.Carriers.SUBSCRIPTION_ID + " INTEGER DEFAULT " +
+ ParticipantData.DEFAULT_SELF_SUB_ID + ");";
+
+ public static final String[] APN_PROJECTION = {
+ Telephony.Carriers.TYPE, // 0
+ Telephony.Carriers.MMSC, // 1
+ Telephony.Carriers.MMSPROXY, // 2
+ Telephony.Carriers.MMSPORT, // 3
+ Telephony.Carriers._ID, // 4
+ Telephony.Carriers.CURRENT, // 5
+ Telephony.Carriers.NUMERIC, // 6
+ Telephony.Carriers.NAME, // 7
+ Telephony.Carriers.MCC, // 8
+ Telephony.Carriers.MNC, // 9
+ Telephony.Carriers.APN, // 10
+ Telephony.Carriers.SUBSCRIPTION_ID // 11
+ };
+
+ public static final int COLUMN_TYPE = 0;
+ public static final int COLUMN_MMSC = 1;
+ public static final int COLUMN_MMSPROXY = 2;
+ public static final int COLUMN_MMSPORT = 3;
+ public static final int COLUMN_ID = 4;
+ public static final int COLUMN_CURRENT = 5;
+ public static final int COLUMN_NUMERIC = 6;
+ public static final int COLUMN_NAME = 7;
+ public static final int COLUMN_MCC = 8;
+ public static final int COLUMN_MNC = 9;
+ public static final int COLUMN_APN = 10;
+ public static final int COLUMN_SUB_ID = 11;
+
+ public static final String[] APN_FULL_PROJECTION = {
+ Telephony.Carriers.NAME,
+ Telephony.Carriers.MCC,
+ Telephony.Carriers.MNC,
+ Telephony.Carriers.APN,
+ Telephony.Carriers.USER,
+ Telephony.Carriers.SERVER,
+ Telephony.Carriers.PASSWORD,
+ Telephony.Carriers.PROXY,
+ Telephony.Carriers.PORT,
+ Telephony.Carriers.MMSC,
+ Telephony.Carriers.MMSPROXY,
+ Telephony.Carriers.MMSPORT,
+ Telephony.Carriers.AUTH_TYPE,
+ Telephony.Carriers.TYPE,
+ Telephony.Carriers.PROTOCOL,
+ Telephony.Carriers.ROAMING_PROTOCOL,
+ Telephony.Carriers.CARRIER_ENABLED,
+ Telephony.Carriers.BEARER,
+ Telephony.Carriers.MVNO_TYPE,
+ Telephony.Carriers.MVNO_MATCH_DATA,
+ Telephony.Carriers.CURRENT,
+ Telephony.Carriers.SUBSCRIPTION_ID,
+ };
+
+ private static final String CURRENT_SELECTION = Telephony.Carriers.CURRENT + " NOT NULL";
+
+ /**
+ * ApnDatabase is initialized asynchronously from the application.onCreate
+ * To ensure that it works in a testing environment it needs to never access the factory context
+ */
+ public static void initializeAppContext(final Context context) {
+ sContext = context;
+ }
+
+ private ApnDatabase() {
+ super(sContext, APN_DATABASE_NAME, null, DB_VERSION);
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase constructor");
+ }
+ }
+
+ public static ApnDatabase getApnDatabase() {
+ if (sApnDatabase == null) {
+ sApnDatabase = new ApnDatabase();
+ }
+ return sApnDatabase;
+ }
+
+ public static boolean doesDatabaseExist() {
+ final File dbFile = sContext.getDatabasePath(APN_DATABASE_NAME);
+ return dbFile.exists();
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase onCreate");
+ }
+ // Build the table using defaults (apn info bundled with the app)
+ rebuildTables(db);
+ }
+
+ /**
+ * Get a copy of user changes in the old table
+ *
+ * @return The list of user changed apns
+ */
+ public static List<ContentValues> loadUserDataFromOldTable(final SQLiteDatabase db) {
+ Cursor cursor = null;
+ try {
+ cursor = db.query(APN_TABLE,
+ APN_FULL_PROJECTION, CURRENT_SELECTION,
+ null/*selectionArgs*/,
+ null/*groupBy*/, null/*having*/, null/*orderBy*/);
+ if (cursor != null) {
+ final List<ContentValues> result = Lists.newArrayList();
+ while (cursor.moveToNext()) {
+ final ContentValues row = cursorToValues(cursor);
+ if (row != null) {
+ result.add(row);
+ }
+ }
+ return result;
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.w(TAG, "ApnDatabase.loadUserDataFromOldTable: no old user data: " + e, e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ private static final String[] ID_PROJECTION = new String[]{Telephony.Carriers._ID};
+
+ private static final String ID_SELECTION = Telephony.Carriers._ID + "=?";
+
+ /**
+ * Store use changes of old table into the new apn table
+ *
+ * @param data The user changes
+ */
+ public static void saveUserDataFromOldTable(
+ final SQLiteDatabase db, final List<ContentValues> data) {
+ if (data == null || data.size() < 1) {
+ return;
+ }
+ for (final ContentValues row : data) {
+ // Build query from the row data. It is an exact match, column by column,
+ // except the CURRENT column
+ final StringBuilder selectionBuilder = new StringBuilder();
+ final ArrayList<String> selectionArgs = Lists.newArrayList();
+ for (final String key : row.keySet()) {
+ if (!Telephony.Carriers.CURRENT.equals(key)) {
+ if (selectionBuilder.length() > 0) {
+ selectionBuilder.append(" AND ");
+ }
+ final String value = row.getAsString(key);
+ if (TextUtils.isEmpty(value)) {
+ selectionBuilder.append(key).append(" IS NULL");
+ } else {
+ selectionBuilder.append(key).append("=?");
+ selectionArgs.add(value);
+ }
+ }
+ }
+ Cursor cursor = null;
+ try {
+ cursor = db.query(APN_TABLE,
+ ID_PROJECTION,
+ selectionBuilder.toString(),
+ selectionArgs.toArray(new String[0]),
+ null/*groupBy*/, null/*having*/, null/*orderBy*/);
+ if (cursor != null && cursor.moveToFirst()) {
+ db.update(APN_TABLE, row, ID_SELECTION, new String[]{cursor.getString(0)});
+ } else {
+ // User APN does not exist, insert into the new table
+ row.put(Telephony.Carriers.NUMERIC,
+ PhoneUtils.canonicalizeMccMnc(
+ row.getAsString(Telephony.Carriers.MCC),
+ row.getAsString(Telephony.Carriers.MNC))
+ );
+ db.insert(APN_TABLE, null/*nullColumnHack*/, row);
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "ApnDatabase.saveUserDataFromOldTable: query error " + e, e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ // Convert Cursor to ContentValues
+ private static ContentValues cursorToValues(final Cursor cursor) {
+ final int columnCount = cursor.getColumnCount();
+ if (columnCount > 0) {
+ final ContentValues result = new ContentValues();
+ for (int i = 0; i < columnCount; i++) {
+ final String name = cursor.getColumnName(i);
+ final String value = cursor.getString(i);
+ result.put(name, value);
+ }
+ return result;
+ }
+ return null;
+ }
+
+ @Override
+ public void onOpen(final SQLiteDatabase db) {
+ super.onOpen(db);
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase onOpen");
+ }
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase close");
+ }
+ }
+
+ private void rebuildTables(final SQLiteDatabase db) {
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase rebuildTables");
+ }
+ db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE + ";");
+ db.execSQL(APN_TABLE_SQL);
+ loadApnTable(db);
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase onUpgrade");
+ }
+ rebuildTables(db);
+ }
+
+ @Override
+ public void onDowngrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
+ if (DEBUG) {
+ LogUtil.d(TAG, "ApnDatabase onDowngrade");
+ }
+ rebuildTables(db);
+ }
+
+ /**
+ * Load APN table from app resources
+ */
+ private static void loadApnTable(final SQLiteDatabase db) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "ApnDatabase loadApnTable");
+ }
+ final Resources r = sContext.getResources();
+ final XmlResourceParser parser = r.getXml(R.xml.apns);
+ final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser);
+ processor.setApnHandler(new ApnsXmlProcessor.ApnHandler() {
+ @Override
+ public void process(final ContentValues apnValues) {
+ db.insert(APN_TABLE, null/*nullColumnHack*/, apnValues);
+ }
+ });
+ try {
+ processor.process();
+ } catch (final Exception e) {
+ Log.e(TAG, "Got exception while loading APN database.", e);
+ } finally {
+ parser.close();
+ }
+ }
+
+ public static void forceBuildAndLoadApnTables() {
+ final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
+ db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
+ // Table(s) always need for JB MR1 for APN support for MMS because JB MR1 throws
+ // a SecurityException when trying to access the carriers table (which holds the
+ // APNs). Some JB MR2 devices also throw the security exception, so we're building
+ // the table for JB MR2, too.
+ db.execSQL(APN_TABLE_SQL);
+
+ loadApnTable(db);
+ }
+
+ /**
+ * Clear all tables
+ */
+ public static void clearTables() {
+ final SQLiteDatabase db = getApnDatabase().getWritableDatabase();
+ db.execSQL("DROP TABLE IF EXISTS " + APN_TABLE);
+ db.execSQL(APN_TABLE_SQL);
+ }
+}
diff --git a/src/com/android/messaging/sms/ApnsXmlProcessor.java b/src/com/android/messaging/sms/ApnsXmlProcessor.java
new file mode 100644
index 0000000..976896c
--- /dev/null
+++ b/src/com/android/messaging/sms/ApnsXmlProcessor.java
@@ -0,0 +1,329 @@
+/*
+ * 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.sms;
+
+import android.content.ContentValues;
+import android.provider.Telephony;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.google.common.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Map;
+
+/*
+ * XML processor for the following files:
+ * 1. res/xml/apns.xml
+ * 2. res/xml/mms_config.xml (or related overlay files)
+ */
+class ApnsXmlProcessor {
+ public interface ApnHandler {
+ public void process(ContentValues apnValues);
+ }
+
+ public interface MmsConfigHandler {
+ public void process(String mccMnc, String key, String value, String type);
+ }
+
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final Map<String, String> APN_ATTRIBUTE_MAP = Maps.newHashMap();
+ static {
+ APN_ATTRIBUTE_MAP.put("mcc", Telephony.Carriers.MCC);
+ APN_ATTRIBUTE_MAP.put("mnc", Telephony.Carriers.MNC);
+ APN_ATTRIBUTE_MAP.put("carrier", Telephony.Carriers.NAME);
+ APN_ATTRIBUTE_MAP.put("apn", Telephony.Carriers.APN);
+ APN_ATTRIBUTE_MAP.put("mmsc", Telephony.Carriers.MMSC);
+ APN_ATTRIBUTE_MAP.put("mmsproxy", Telephony.Carriers.MMSPROXY);
+ APN_ATTRIBUTE_MAP.put("mmsport", Telephony.Carriers.MMSPORT);
+ APN_ATTRIBUTE_MAP.put("type", Telephony.Carriers.TYPE);
+ APN_ATTRIBUTE_MAP.put("user", Telephony.Carriers.USER);
+ APN_ATTRIBUTE_MAP.put("password", Telephony.Carriers.PASSWORD);
+ APN_ATTRIBUTE_MAP.put("authtype", Telephony.Carriers.AUTH_TYPE);
+ APN_ATTRIBUTE_MAP.put("mvno_match_data", Telephony.Carriers.MVNO_MATCH_DATA);
+ APN_ATTRIBUTE_MAP.put("mvno_type", Telephony.Carriers.MVNO_TYPE);
+ APN_ATTRIBUTE_MAP.put("protocol", Telephony.Carriers.PROTOCOL);
+ APN_ATTRIBUTE_MAP.put("bearer", Telephony.Carriers.BEARER);
+ APN_ATTRIBUTE_MAP.put("server", Telephony.Carriers.SERVER);
+ APN_ATTRIBUTE_MAP.put("roaming_protocol", Telephony.Carriers.ROAMING_PROTOCOL);
+ APN_ATTRIBUTE_MAP.put("proxy", Telephony.Carriers.PROXY);
+ APN_ATTRIBUTE_MAP.put("port", Telephony.Carriers.PORT);
+ APN_ATTRIBUTE_MAP.put("carrier_enabled", Telephony.Carriers.CARRIER_ENABLED);
+ }
+
+ private static final String TAG_APNS = "apns";
+ private static final String TAG_APN = "apn";
+ private static final String TAG_MMS_CONFIG = "mms_config";
+
+ // Handler to process one apn
+ private ApnHandler mApnHandler;
+ // Handler to process one mms_config key/value pair
+ private MmsConfigHandler mMmsConfigHandler;
+
+ private final StringBuilder mLogStringBuilder = new StringBuilder();
+
+ private final XmlPullParser mInputParser;
+
+ private ApnsXmlProcessor(XmlPullParser parser) {
+ mInputParser = parser;
+ mApnHandler = null;
+ mMmsConfigHandler = null;
+ }
+
+ public static ApnsXmlProcessor get(XmlPullParser parser) {
+ Assert.notNull(parser);
+ return new ApnsXmlProcessor(parser);
+ }
+
+ public ApnsXmlProcessor setApnHandler(ApnHandler handler) {
+ mApnHandler = handler;
+ return this;
+ }
+
+ public ApnsXmlProcessor setMmsConfigHandler(MmsConfigHandler handler) {
+ mMmsConfigHandler = handler;
+ return this;
+ }
+
+ /**
+ * Move XML parser forward to next event type or the end of doc
+ *
+ * @param eventType
+ * @return The final event type we meet
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ private int advanceToNextEvent(int eventType) throws XmlPullParserException, IOException {
+ for (;;) {
+ int nextEvent = mInputParser.next();
+ if (nextEvent == eventType
+ || nextEvent == XmlPullParser.END_DOCUMENT) {
+ return nextEvent;
+ }
+ }
+ }
+
+ public void process() {
+ try {
+ // Find the first element
+ if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("ApnsXmlProcessor: expecting start tag @"
+ + xmlParserDebugContext());
+ }
+ // A single ContentValues object for holding the parsing result of
+ // an apn element
+ final ContentValues values = new ContentValues();
+ String tagName = mInputParser.getName();
+ // Top level tag can be "apns" (apns.xml)
+ // or "mms_config" (mms_config.xml)
+ if (TAG_APNS.equals(tagName)) {
+ // For "apns", there could be "apn" or both "apn" and "mms_config"
+ for (;;) {
+ if (advanceToNextEvent(XmlPullParser.START_TAG) != XmlPullParser.START_TAG) {
+ break;
+ }
+ tagName = mInputParser.getName();
+ if (TAG_APN.equals(tagName)) {
+ processApn(values);
+ } else if (TAG_MMS_CONFIG.equals(tagName)) {
+ processMmsConfig();
+ }
+ }
+ } else if (TAG_MMS_CONFIG.equals(tagName)) {
+ // mms_config.xml resource
+ processMmsConfig();
+ }
+ } catch (IOException e) {
+ LogUtil.e(TAG, "ApnsXmlProcessor: I/O failure " + e, e);
+ } catch (XmlPullParserException e) {
+ LogUtil.e(TAG, "ApnsXmlProcessor: parsing failure " + e, e);
+ }
+ }
+
+ private Integer parseInt(String text, Integer defaultValue, String logHint) {
+ Integer value = defaultValue;
+ try {
+ value = Integer.parseInt(text);
+ } catch (Exception e) {
+ LogUtil.e(TAG,
+ "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext());
+ }
+ return value;
+ }
+
+ private Boolean parseBoolean(String text, Boolean defaultValue, String logHint) {
+ Boolean value = defaultValue;
+ try {
+ value = Boolean.parseBoolean(text);
+ } catch (Exception e) {
+ LogUtil.e(TAG,
+ "Invalid value " + text + "for" + logHint + " @" + xmlParserDebugContext());
+ }
+ return value;
+ }
+
+ private static String xmlParserEventString(int event) {
+ switch (event) {
+ case XmlPullParser.START_DOCUMENT: return "START_DOCUMENT";
+ case XmlPullParser.END_DOCUMENT: return "END_DOCUMENT";
+ case XmlPullParser.START_TAG: return "START_TAG";
+ case XmlPullParser.END_TAG: return "END_TAG";
+ case XmlPullParser.TEXT: return "TEXT";
+ }
+ return Integer.toString(event);
+ }
+
+ /**
+ * @return The debugging information of the parser's current position
+ */
+ private String xmlParserDebugContext() {
+ mLogStringBuilder.setLength(0);
+ if (mInputParser != null) {
+ try {
+ final int eventType = mInputParser.getEventType();
+ mLogStringBuilder.append(xmlParserEventString(eventType));
+ if (eventType == XmlPullParser.START_TAG
+ || eventType == XmlPullParser.END_TAG
+ || eventType == XmlPullParser.TEXT) {
+ mLogStringBuilder.append('<').append(mInputParser.getName());
+ for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
+ mLogStringBuilder.append(' ')
+ .append(mInputParser.getAttributeName(i))
+ .append('=')
+ .append(mInputParser.getAttributeValue(i));
+ }
+ mLogStringBuilder.append("/>");
+ }
+ return mLogStringBuilder.toString();
+ } catch (XmlPullParserException e) {
+ LogUtil.e(TAG, "xmlParserDebugContext: " + e, e);
+ }
+ }
+ return "Unknown";
+ }
+
+ /**
+ * Process one apn
+ *
+ * @param apnValues Where we store the parsed apn
+ * @throws IOException
+ * @throws XmlPullParserException
+ */
+ private void processApn(ContentValues apnValues) throws IOException, XmlPullParserException {
+ Assert.notNull(apnValues);
+ apnValues.clear();
+ // Collect all the attributes
+ for (int i = 0; i < mInputParser.getAttributeCount(); i++) {
+ final String key = APN_ATTRIBUTE_MAP.get(mInputParser.getAttributeName(i));
+ if (key != null) {
+ apnValues.put(key, mInputParser.getAttributeValue(i));
+ }
+ }
+ // Set numeric to be canonicalized mcc/mnc like "310120", always 6 digits
+ final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc(
+ apnValues.getAsString(Telephony.Carriers.MCC),
+ apnValues.getAsString(Telephony.Carriers.MNC));
+ apnValues.put(Telephony.Carriers.NUMERIC, canonicalMccMnc);
+ // Some of the values should not be string type, converting them to desired types
+ final String authType = apnValues.getAsString(Telephony.Carriers.AUTH_TYPE);
+ if (authType != null) {
+ apnValues.put(Telephony.Carriers.AUTH_TYPE, parseInt(authType, -1, "apn authtype"));
+ }
+ final String carrierEnabled = apnValues.getAsString(Telephony.Carriers.CARRIER_ENABLED);
+ if (carrierEnabled != null) {
+ apnValues.put(Telephony.Carriers.CARRIER_ENABLED,
+ parseBoolean(carrierEnabled, null, "apn carrierEnabled"));
+ }
+ final String bearer = apnValues.getAsString(Telephony.Carriers.BEARER);
+ if (bearer != null) {
+ apnValues.put(Telephony.Carriers.BEARER, parseInt(bearer, 0, "apn bearer"));
+ }
+ // We are at the end tag
+ if (mInputParser.next() != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException("Apn: expecting end tag @"
+ + xmlParserDebugContext());
+ }
+ // We are done parsing one APN, call the handler
+ if (mApnHandler != null) {
+ mApnHandler.process(apnValues);
+ }
+ }
+
+ /**
+ * Process one mms_config.
+ *
+ * @throws IOException
+ * @throws XmlPullParserException
+ */
+ private void processMmsConfig()
+ throws IOException, XmlPullParserException {
+ // Get the mcc and mnc attributes
+ final String canonicalMccMnc = PhoneUtils.canonicalizeMccMnc(
+ mInputParser.getAttributeValue(null, "mcc"),
+ mInputParser.getAttributeValue(null, "mnc"));
+ // We are at the start tag
+ for (;;) {
+ int nextEvent;
+ // Skipping spaces
+ while ((nextEvent = mInputParser.next()) == XmlPullParser.TEXT) {
+ }
+ if (nextEvent == XmlPullParser.START_TAG) {
+ // Parse one mms config key/value
+ processMmsConfigKeyValue(canonicalMccMnc);
+ } else if (nextEvent == XmlPullParser.END_TAG) {
+ break;
+ } else {
+ throw new XmlPullParserException("MmsConfig: expecting start or end tag @"
+ + xmlParserDebugContext());
+ }
+ }
+ }
+
+ /**
+ * Process one mms_config key/value pair
+ *
+ * @param mccMnc The mcc and mnc of this mms_config
+ * @throws IOException
+ * @throws XmlPullParserException
+ */
+ private void processMmsConfigKeyValue(String mccMnc)
+ throws IOException, XmlPullParserException {
+ final String key = mInputParser.getAttributeValue(null, "name");
+ // We are at the start tag, the name of the tag is the type
+ // e.g. <int name="key">value</int>
+ final String type = mInputParser.getName();
+ int nextEvent = mInputParser.next();
+ String value = null;
+ if (nextEvent == XmlPullParser.TEXT) {
+ value = mInputParser.getText();
+ nextEvent = mInputParser.next();
+ }
+ if (nextEvent != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException("ApnsXmlProcessor: expecting end tag @"
+ + xmlParserDebugContext());
+ }
+ // We are done parsing one mms_config key/value, call the handler
+ if (mMmsConfigHandler != null) {
+ mMmsConfigHandler.process(mccMnc, key, value, type);
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/BugleApnSettingsLoader.java b/src/com/android/messaging/sms/BugleApnSettingsLoader.java
new file mode 100644
index 0000000..43c95ac
--- /dev/null
+++ b/src/com/android/messaging/sms/BugleApnSettingsLoader.java
@@ -0,0 +1,646 @@
+/*
+ * 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.sms;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.support.v7.mms.ApnSettingsLoader;
+import android.support.v7.mms.MmsManager;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * APN loader for default SMS SIM
+ *
+ * This loader tries to load APNs from 3 sources in order:
+ * 1. Gservices setting
+ * 2. System APN table
+ * 3. Local APN table
+ */
+public class BugleApnSettingsLoader implements ApnSettingsLoader {
+ /**
+ * The base implementation of an APN
+ */
+ private static class BaseApn implements Apn {
+ /**
+ * Create a base APN from parameters
+ *
+ * @param typesIn the APN type field
+ * @param mmscIn the APN mmsc field
+ * @param proxyIn the APN mmsproxy field
+ * @param portIn the APN mmsport field
+ * @return an instance of base APN, or null if any of the parameter is invalid
+ */
+ public static BaseApn from(final String typesIn, final String mmscIn, final String proxyIn,
+ final String portIn) {
+ if (!isValidApnType(trimWithNullCheck(typesIn), APN_TYPE_MMS)) {
+ return null;
+ }
+ String mmsc = trimWithNullCheck(mmscIn);
+ if (TextUtils.isEmpty(mmsc)) {
+ return null;
+ }
+ mmsc = trimV4AddrZeros(mmsc);
+ try {
+ new URI(mmsc);
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+ String mmsProxy = trimWithNullCheck(proxyIn);
+ int mmsProxyPort = 80;
+ if (!TextUtils.isEmpty(mmsProxy)) {
+ mmsProxy = trimV4AddrZeros(mmsProxy);
+ final String portString = trimWithNullCheck(portIn);
+ if (portString != null) {
+ try {
+ mmsProxyPort = Integer.parseInt(portString);
+ } catch (final NumberFormatException e) {
+ // Ignore, just use 80 to try
+ }
+ }
+ }
+ return new BaseApn(mmsc, mmsProxy, mmsProxyPort);
+ }
+
+ private final String mMmsc;
+ private final String mMmsProxy;
+ private final int mMmsProxyPort;
+
+ public BaseApn(final String mmsc, final String proxy, final int port) {
+ mMmsc = mmsc;
+ mMmsProxy = proxy;
+ mMmsProxyPort = port;
+ }
+
+ @Override
+ public String getMmsc() {
+ return mMmsc;
+ }
+
+ @Override
+ public String getMmsProxy() {
+ return mMmsProxy;
+ }
+
+ @Override
+ public int getMmsProxyPort() {
+ return mMmsProxyPort;
+ }
+
+ @Override
+ public void setSuccess() {
+ // Do nothing
+ }
+
+ public boolean equals(final BaseApn other) {
+ return TextUtils.equals(mMmsc, other.getMmsc()) &&
+ TextUtils.equals(mMmsProxy, other.getMmsProxy()) &&
+ mMmsProxyPort == other.getMmsProxyPort();
+ }
+ }
+
+ /**
+ * The APN represented by the local APN table row
+ */
+ private static class DatabaseApn implements Apn {
+ private static final ContentValues CURRENT_NULL_VALUE;
+ private static final ContentValues CURRENT_SET_VALUE;
+ static {
+ CURRENT_NULL_VALUE = new ContentValues(1);
+ CURRENT_NULL_VALUE.putNull(Telephony.Carriers.CURRENT);
+ CURRENT_SET_VALUE = new ContentValues(1);
+ CURRENT_SET_VALUE.put(Telephony.Carriers.CURRENT, "1"); // 1 for auto selected APN
+ }
+ private static final String CLEAR_UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?";
+ private static final String[] CLEAR_UPDATE_SELECTION_ARGS = new String[] { "1" };
+ private static final String SET_UPDATE_SELECTION = Telephony.Carriers._ID + " =?";
+
+ /**
+ * Create an APN loaded from local database
+ *
+ * @param apns the in-memory APN list
+ * @param typesIn the APN type field
+ * @param mmscIn the APN mmsc field
+ * @param proxyIn the APN mmsproxy field
+ * @param portIn the APN mmsport field
+ * @param rowId the APN's row ID in database
+ * @param current the value of CURRENT column in database
+ * @return an in-memory APN instance for database APN row, null if parameter invalid
+ */
+ public static DatabaseApn from(final List<Apn> apns, final String typesIn,
+ final String mmscIn, final String proxyIn, final String portIn,
+ final long rowId, final int current) {
+ if (apns == null) {
+ return null;
+ }
+ final BaseApn base = BaseApn.from(typesIn, mmscIn, proxyIn, portIn);
+ if (base == null) {
+ return null;
+ }
+ for (final ApnSettingsLoader.Apn apn : apns) {
+ if (apn instanceof DatabaseApn && ((DatabaseApn) apn).equals(base)) {
+ return null;
+ }
+ }
+ return new DatabaseApn(apns, base, rowId, current);
+ }
+
+ private final List<Apn> mApns;
+ private final BaseApn mBase;
+ private final long mRowId;
+ private int mCurrent;
+
+ public DatabaseApn(final List<Apn> apns, final BaseApn base, final long rowId,
+ final int current) {
+ mApns = apns;
+ mBase = base;
+ mRowId = rowId;
+ mCurrent = current;
+ }
+
+ @Override
+ public String getMmsc() {
+ return mBase.getMmsc();
+ }
+
+ @Override
+ public String getMmsProxy() {
+ return mBase.getMmsProxy();
+ }
+
+ @Override
+ public int getMmsProxyPort() {
+ return mBase.getMmsProxyPort();
+ }
+
+ @Override
+ public void setSuccess() {
+ moveToListHead();
+ setCurrentInDatabase();
+ }
+
+ /**
+ * Try to move this APN to the head of in-memory list
+ */
+ private void moveToListHead() {
+ // If this is being marked as a successful APN, move it to the top of the list so
+ // next time it will be tried first
+ boolean moved = false;
+ synchronized (mApns) {
+ if (mApns.get(0) != this) {
+ mApns.remove(this);
+ mApns.add(0, this);
+ moved = true;
+ }
+ }
+ if (moved) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "Set APN ["
+ + "MMSC=" + getMmsc() + ", "
+ + "PROXY=" + getMmsProxy() + ", "
+ + "PORT=" + getMmsProxyPort() + "] to be first");
+ }
+ }
+
+ /**
+ * Try to set the APN to be CURRENT in its database table
+ */
+ private void setCurrentInDatabase() {
+ synchronized (this) {
+ if (mCurrent > 0) {
+ // Already current
+ return;
+ }
+ mCurrent = 1;
+ }
+ LogUtil.d(LogUtil.BUGLE_TAG, "Set APN @" + mRowId + " to be CURRENT in local db");
+ final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
+ database.beginTransaction();
+ try {
+ // clear the previous current=1 apn
+ // we don't clear current=2 apn since it is manually selected by user
+ // and we should not override it.
+ database.update(ApnDatabase.APN_TABLE, CURRENT_NULL_VALUE,
+ CLEAR_UPDATE_SELECTION, CLEAR_UPDATE_SELECTION_ARGS);
+ // set this one to be current (1)
+ database.update(ApnDatabase.APN_TABLE, CURRENT_SET_VALUE, SET_UPDATE_SELECTION,
+ new String[] { Long.toString(mRowId) });
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ public boolean equals(final BaseApn other) {
+ if (other == null) {
+ return false;
+ }
+ return mBase.equals(other);
+ }
+ }
+
+ /**
+ * APN_TYPE_ALL is a special type to indicate that this APN entry can
+ * service all data connections.
+ */
+ public static final String APN_TYPE_ALL = "*";
+ /** APN type for MMS traffic */
+ public static final String APN_TYPE_MMS = "mms";
+
+ private static final String[] APN_PROJECTION_SYSTEM = {
+ Telephony.Carriers.TYPE,
+ Telephony.Carriers.MMSC,
+ Telephony.Carriers.MMSPROXY,
+ Telephony.Carriers.MMSPORT,
+ };
+ private static final String[] APN_PROJECTION_LOCAL = {
+ Telephony.Carriers.TYPE,
+ Telephony.Carriers.MMSC,
+ Telephony.Carriers.MMSPROXY,
+ Telephony.Carriers.MMSPORT,
+ Telephony.Carriers.CURRENT,
+ Telephony.Carriers._ID,
+ };
+ private static final int COLUMN_TYPE = 0;
+ private static final int COLUMN_MMSC = 1;
+ private static final int COLUMN_MMSPROXY = 2;
+ private static final int COLUMN_MMSPORT = 3;
+ private static final int COLUMN_CURRENT = 4;
+ private static final int COLUMN_ID = 5;
+
+ private static final String SELECTION_APN = Telephony.Carriers.APN + "=?";
+ private static final String SELECTION_CURRENT = Telephony.Carriers.CURRENT + " IS NOT NULL";
+ private static final String SELECTION_NUMERIC = Telephony.Carriers.NUMERIC + "=?";
+ private static final String ORDER_BY = Telephony.Carriers.CURRENT + " DESC";
+
+ private final Context mContext;
+
+ // Cached APNs for subIds
+ private final SparseArray<List<ApnSettingsLoader.Apn>> mApnsCache;
+
+ public BugleApnSettingsLoader(final Context context) {
+ mContext = context;
+ mApnsCache = new SparseArray<>();
+ }
+
+ @Override
+ public List<ApnSettingsLoader.Apn> get(final String apnName) {
+ final int subId = PhoneUtils.getDefault().getEffectiveSubId(
+ ParticipantData.DEFAULT_SELF_SUB_ID);
+ List<ApnSettingsLoader.Apn> apns;
+ boolean didLoad = false;
+ synchronized (this) {
+ apns = mApnsCache.get(subId);
+ if (apns == null) {
+ apns = new ArrayList<>();
+ mApnsCache.put(subId, apns);
+ loadLocked(subId, apnName, apns);
+ didLoad = true;
+ }
+ }
+ if (didLoad) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Loaded " + apns.size() + " APNs");
+ }
+ return apns;
+ }
+
+ private void loadLocked(final int subId, final String apnName, final List<Apn> apns) {
+ // Try Gservices first
+ loadFromGservices(apns);
+ if (apns.size() > 0) {
+ return;
+ }
+ // Try system APN table
+ loadFromSystem(subId, apnName, apns);
+ if (apns.size() > 0) {
+ return;
+ }
+ // Try local APN table
+ loadFromLocalDatabase(apnName, apns);
+ if (apns.size() <= 0) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Failed to load any APN");
+ }
+ }
+
+ /**
+ * Load from Gservices if APN setting is set in Gservices
+ *
+ * @param apns the list used to return results
+ */
+ private void loadFromGservices(final List<Apn> apns) {
+ final BugleGservices gservices = BugleGservices.get();
+ final String mmsc = gservices.getString(BugleGservicesKeys.MMS_MMSC, null);
+ if (TextUtils.isEmpty(mmsc)) {
+ return;
+ }
+ LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from gservices");
+ final String proxy = gservices.getString(BugleGservicesKeys.MMS_PROXY_ADDRESS, null);
+ final int port = gservices.getInt(BugleGservicesKeys.MMS_PROXY_PORT, -1);
+ final Apn apn = BaseApn.from("mms", mmsc, proxy, Integer.toString(port));
+ if (apn != null) {
+ apns.add(apn);
+ }
+ }
+
+ /**
+ * Load matching APNs from telephony provider.
+ * We try different combinations of the query to work around some platform quirks.
+ *
+ * @param subId the SIM subId
+ * @param apnName the APN name to match
+ * @param apns the list used to return results
+ */
+ private void loadFromSystem(final int subId, final String apnName, final List<Apn> apns) {
+ Uri uri;
+ if (OsUtil.isAtLeastL_MR1() && subId != MmsManager.DEFAULT_SUB_ID) {
+ uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId);
+ } else {
+ uri = Telephony.Carriers.CONTENT_URI;
+ }
+ Cursor cursor = null;
+ try {
+ for (; ; ) {
+ // Try different combinations of queries. Some would work on some platforms.
+ // So we query each combination until we find one returns non-empty result.
+ cursor = querySystem(uri, true/*checkCurrent*/, apnName);
+ if (cursor != null) {
+ break;
+ }
+ cursor = querySystem(uri, false/*checkCurrent*/, apnName);
+ if (cursor != null) {
+ break;
+ }
+ cursor = querySystem(uri, true/*checkCurrent*/, null/*apnName*/);
+ if (cursor != null) {
+ break;
+ }
+ cursor = querySystem(uri, false/*checkCurrent*/, null/*apnName*/);
+ break;
+ }
+ } catch (final SecurityException e) {
+ // Can't access platform APN table, return directly
+ return;
+ }
+ if (cursor == null) {
+ return;
+ }
+ try {
+ if (cursor.moveToFirst()) {
+ final ApnSettingsLoader.Apn apn = BaseApn.from(
+ cursor.getString(COLUMN_TYPE),
+ cursor.getString(COLUMN_MMSC),
+ cursor.getString(COLUMN_MMSPROXY),
+ cursor.getString(COLUMN_MMSPORT));
+ if (apn != null) {
+ apns.add(apn);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Query system APN table
+ *
+ * @param uri The APN query URL to use
+ * @param checkCurrent If add "CURRENT IS NOT NULL" condition
+ * @param apnName The optional APN name for query condition
+ * @return A cursor of the query result. If a cursor is returned as not null, it is
+ * guaranteed to contain at least one row.
+ */
+ private Cursor querySystem(final Uri uri, final boolean checkCurrent, String apnName) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from system, "
+ + "checkCurrent=" + checkCurrent + " apnName=" + apnName);
+ final StringBuilder selectionBuilder = new StringBuilder();
+ String[] selectionArgs = null;
+ if (checkCurrent) {
+ selectionBuilder.append(SELECTION_CURRENT);
+ }
+ apnName = trimWithNullCheck(apnName);
+ if (!TextUtils.isEmpty(apnName)) {
+ if (selectionBuilder.length() > 0) {
+ selectionBuilder.append(" AND ");
+ }
+ selectionBuilder.append(SELECTION_APN);
+ selectionArgs = new String[] { apnName };
+ }
+ try {
+ final Cursor cursor = SqliteWrapper.query(
+ mContext,
+ mContext.getContentResolver(),
+ uri,
+ APN_PROJECTION_SYSTEM,
+ selectionBuilder.toString(),
+ selectionArgs,
+ null/*sortOrder*/);
+ if (cursor == null || cursor.getCount() < 1) {
+ if (cursor != null) {
+ cursor.close();
+ }
+ LogUtil.w(LogUtil.BUGLE_TAG, "Query " + uri + " with apn " + apnName + " and "
+ + (checkCurrent ? "checking CURRENT" : "not checking CURRENT")
+ + " returned empty");
+ return null;
+ }
+ return cursor;
+ } catch (final SQLiteException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "APN table query exception: " + e);
+ } catch (final SecurityException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Platform restricts APN table access: " + e);
+ throw e;
+ }
+ return null;
+ }
+
+ /**
+ * Load matching APNs from local APN table.
+ * We try both using the APN name and not using the APN name.
+ *
+ * @param apnName the APN name
+ * @param apns the list of results to return
+ */
+ private void loadFromLocalDatabase(final String apnName, final List<Apn> apns) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Loading APNs from local APN table");
+ final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
+ final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.getDefault().getMccMnc());
+ Cursor cursor = null;
+ cursor = queryLocalDatabase(database, mccMnc, apnName);
+ if (cursor == null) {
+ cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/);
+ }
+ if (cursor == null) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Could not find any APN in local table");
+ return;
+ }
+ try {
+ while (cursor.moveToNext()) {
+ final Apn apn = DatabaseApn.from(apns,
+ cursor.getString(COLUMN_TYPE),
+ cursor.getString(COLUMN_MMSC),
+ cursor.getString(COLUMN_MMSPROXY),
+ cursor.getString(COLUMN_MMSPORT),
+ cursor.getLong(COLUMN_ID),
+ cursor.getInt(COLUMN_CURRENT));
+ if (apn != null) {
+ apns.add(apn);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Make a query of local APN table based on MCC/MNC and APN name, sorted by CURRENT
+ * column in descending order
+ *
+ * @param db the local database
+ * @param numeric the MCC/MNC string
+ * @param apnName the optional APN name to match
+ * @return the cursor of the query, null if no result
+ */
+ private static Cursor queryLocalDatabase(final SQLiteDatabase db, final String numeric,
+ final String apnName) {
+ final String selection;
+ final String[] selectionArgs;
+ if (TextUtils.isEmpty(apnName)) {
+ selection = SELECTION_NUMERIC;
+ selectionArgs = new String[] { numeric };
+ } else {
+ selection = SELECTION_NUMERIC + " AND " + SELECTION_APN;
+ selectionArgs = new String[] { numeric, apnName };
+ }
+ Cursor cursor = null;
+ try {
+ cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs,
+ null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/);
+ } catch (final SQLiteException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Local APN table does not exist. Try rebuilding.", e);
+ ApnDatabase.forceBuildAndLoadApnTables();
+ cursor = db.query(ApnDatabase.APN_TABLE, APN_PROJECTION_LOCAL, selection, selectionArgs,
+ null/*groupBy*/, null/*having*/, ORDER_BY, null/*limit*/);
+ }
+ if (cursor == null || cursor.getCount() < 1) {
+ if (cursor != null) {
+ cursor.close();
+ }
+ LogUtil.w(LogUtil.BUGLE_TAG, "Query local APNs with apn " + apnName
+ + " returned empty");
+ return null;
+ }
+ return cursor;
+ }
+
+ private static String trimWithNullCheck(final String value) {
+ return value != null ? value.trim() : null;
+ }
+
+ /**
+ * Trim leading zeros from IPv4 address strings
+ * Our base libraries will interpret that as octel..
+ * Must leave non v4 addresses and host names alone.
+ * For example, 192.168.000.010 -> 192.168.0.10
+ *
+ * @param addr a string representing an ip addr
+ * @return a string propertly trimmed
+ */
+ private static String trimV4AddrZeros(final String addr) {
+ if (addr == null) {
+ return null;
+ }
+ final String[] octets = addr.split("\\.");
+ if (octets.length != 4) {
+ return addr;
+ }
+ final StringBuilder builder = new StringBuilder(16);
+ String result = null;
+ for (int i = 0; i < 4; i++) {
+ try {
+ if (octets[i].length() > 3) {
+ return addr;
+ }
+ builder.append(Integer.parseInt(octets[i]));
+ } catch (final NumberFormatException e) {
+ return addr;
+ }
+ if (i < 3) {
+ builder.append('.');
+ }
+ }
+ result = builder.toString();
+ return result;
+ }
+
+ /**
+ * Check if the APN contains the APN type we want
+ *
+ * @param types The string encodes a list of supported types
+ * @param requestType The type we want
+ * @return true if the input types string contains the requestType
+ */
+ public static boolean isValidApnType(final String types, final String requestType) {
+ // If APN type is unspecified, assume APN_TYPE_ALL.
+ if (TextUtils.isEmpty(types)) {
+ return true;
+ }
+ for (final String t : types.split(",")) {
+ if (t.equals(requestType) || t.equals(APN_TYPE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the ID of first APN to try
+ */
+ public static String getFirstTryApn(final SQLiteDatabase database, final String mccMnc) {
+ String key = null;
+ Cursor cursor = null;
+ try {
+ cursor = queryLocalDatabase(database, mccMnc, null/*apnName*/);
+ if (cursor.moveToFirst()) {
+ key = cursor.getString(ApnDatabase.COLUMN_ID);
+ }
+ } catch (final Exception e) {
+ // Nothing to do
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return key;
+ }
+}
diff --git a/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java b/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java
new file mode 100644
index 0000000..ac6c7e4
--- /dev/null
+++ b/src/com/android/messaging/sms/BugleCarrierConfigValuesLoader.java
@@ -0,0 +1,201 @@
+/*
+ * 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.sms;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.support.v7.mms.CarrierConfigValuesLoader;
+import android.util.SparseArray;
+
+import com.android.messaging.R;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Carrier configuration loader
+ *
+ * Loader tries to load from resources. If there is MMS API available, also
+ * load from system.
+ */
+public class BugleCarrierConfigValuesLoader implements CarrierConfigValuesLoader {
+ /*
+ * Key types
+ */
+ public static final String KEY_TYPE_INT = "int";
+ public static final String KEY_TYPE_BOOL = "bool";
+ public static final String KEY_TYPE_STRING = "string";
+
+ private final Context mContext;
+
+ // Cached values for subIds
+ private final SparseArray<Bundle> mValuesCache;
+
+ public BugleCarrierConfigValuesLoader(final Context context) {
+ mContext = context;
+ mValuesCache = new SparseArray<>();
+ }
+
+ @Override
+ public Bundle get(int subId) {
+ subId = PhoneUtils.getDefault().getEffectiveSubId(subId);
+ Bundle values;
+ String loadSource = null;
+ synchronized (this) {
+ values = mValuesCache.get(subId);
+ if (values == null) {
+ values = new Bundle();
+ mValuesCache.put(subId, values);
+ loadSource = loadLocked(subId, values);
+ }
+ }
+ if (loadSource != null) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Carrier configs loaded: " + values
+ + " from " + loadSource + " for subId=" + subId);
+ }
+ return values;
+ }
+
+ /**
+ * Clear the cache for reloading
+ */
+ public void reset() {
+ synchronized (this) {
+ mValuesCache.clear();
+ }
+ }
+
+ /**
+ * Loading carrier config values
+ *
+ * @param subId which SIM to load for
+ * @param values the result to add to
+ * @return the source of the config, could be "resources" or "resources+system"
+ */
+ private String loadLocked(final int subId, final Bundle values) {
+ // Load from resources in earlier platform
+ loadFromResources(subId, values);
+ if (OsUtil.isAtLeastL()) {
+ // Load from system to override if system API exists
+ loadFromSystem(subId, values);
+ return "resources+system";
+ }
+ return "resources";
+ }
+
+ /**
+ * Load from system, using MMS API
+ *
+ * @param subId which SIM to load for
+ * @param values the result to add to
+ */
+ private static void loadFromSystem(final int subId, final Bundle values) {
+ try {
+ final Bundle systemValues =
+ PhoneUtils.get(subId).getSmsManager().getCarrierConfigValues();
+ if (systemValues != null) {
+ values.putAll(systemValues);
+ }
+ } catch (final Exception e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Calling system getCarrierConfigValues exception", e);
+ }
+ }
+
+ /**
+ * Load from SIM-dependent resources
+ *
+ * @param subId which SIM to load for
+ * @param values the result to add to
+ */
+ private void loadFromResources(final int subId, final Bundle values) {
+ // Get a subscription-dependent context for loading the mms_config.xml
+ final Context subContext = getSubDepContext(mContext, subId);
+ // Load and parse the XML
+ XmlResourceParser parser = null;
+ try {
+ parser = subContext.getResources().getXml(R.xml.mms_config);
+ final ApnsXmlProcessor processor = ApnsXmlProcessor.get(parser);
+ processor.setMmsConfigHandler(new ApnsXmlProcessor.MmsConfigHandler() {
+ @Override
+ public void process(final String mccMnc, final String key, final String value,
+ final String type) {
+ update(values, type, key, value);
+ }
+ });
+ processor.process();
+ } catch (final Resources.NotFoundException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Can not find mms_config.xml");
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ /**
+ * Get a subscription's Context so we can load resources from it
+ *
+ * @param context the sub-independent Context
+ * @param subId the SIM's subId
+ * @return the sub-dependent Context
+ */
+ private static Context getSubDepContext(final Context context, final int subId) {
+ if (!OsUtil.isAtLeastL_MR1()) {
+ return context;
+ }
+ final int[] mccMnc = PhoneUtils.get(subId).getMccMnc();
+ final int mcc = mccMnc[0];
+ final int mnc = mccMnc[1];
+ final Configuration subConfig = new Configuration();
+ if (mcc == 0 && mnc == 0) {
+ Configuration config = context.getResources().getConfiguration();
+ subConfig.mcc = config.mcc;
+ subConfig.mnc = config.mnc;
+ } else {
+ subConfig.mcc = mcc;
+ subConfig.mnc = mnc;
+ }
+ return context.createConfigurationContext(subConfig);
+ }
+
+ /**
+ * Add or update a carrier config key/value pair to the Bundle
+ *
+ * @param values the result Bundle to add to
+ * @param type the value type
+ * @param key the key
+ * @param value the value
+ */
+ public static void update(final Bundle values, final String type, final String key,
+ final String value) {
+ try {
+ if (KEY_TYPE_INT.equals(type)) {
+ values.putInt(key, Integer.parseInt(value));
+ } else if (KEY_TYPE_BOOL.equals(type)) {
+ values.putBoolean(key, Boolean.parseBoolean(value));
+ } else if (KEY_TYPE_STRING.equals(type)){
+ values.putString(key, value);
+ }
+ } catch (final NumberFormatException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Add carrier values: "
+ + "invalid " + key + "," + value + "," + type);
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java b/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java
new file mode 100644
index 0000000..a8dc5c4
--- /dev/null
+++ b/src/com/android/messaging/sms/BugleUserAgentInfoLoader.java
@@ -0,0 +1,96 @@
+/*
+ * 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.sms;
+
+import android.content.Context;
+import android.support.v7.mms.UserAgentInfoLoader;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.VersionUtil;
+
+/**
+ * User agent and UA profile URL loader
+ */
+public class BugleUserAgentInfoLoader implements UserAgentInfoLoader {
+ private static final String DEFAULT_USER_AGENT_PREFIX = "Bugle/";
+
+ private Context mContext;
+ private boolean mLoaded;
+
+ private String mUserAgent;
+ private String mUAProfUrl;
+
+ public BugleUserAgentInfoLoader(final Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public String getUserAgent() {
+ load();
+ return mUserAgent;
+ }
+
+ @Override
+ public String getUAProfUrl() {
+ load();
+ return mUAProfUrl;
+ }
+
+ private void load() {
+ if (mLoaded) {
+ return;
+ }
+ boolean didLoad = false;
+ synchronized (this) {
+ if (!mLoaded) {
+ loadLocked();
+ mLoaded = true;
+ didLoad = true;
+ }
+ }
+ if (didLoad) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Loaded user agent info: "
+ + "UA=" + mUserAgent + ", UAProfUrl=" + mUAProfUrl);
+ }
+ }
+
+ private void loadLocked() {
+ if (OsUtil.isAtLeastKLP()) {
+ // load the MMS User agent and UaProfUrl from TelephonyManager APIs
+ final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ mUserAgent = telephonyManager.getMmsUserAgent();
+ mUAProfUrl = telephonyManager.getMmsUAProfUrl();
+ }
+ // if user agent string isn't set, use the format "Bugle/<app_version>".
+ if (TextUtils.isEmpty(mUserAgent)) {
+ final String simpleVersionName = VersionUtil.getInstance(mContext).getSimpleName();
+ mUserAgent = DEFAULT_USER_AGENT_PREFIX + simpleVersionName;
+ }
+ // if the UAProfUrl isn't set, get it from Gservices
+ if (TextUtils.isEmpty(mUAProfUrl)) {
+ mUAProfUrl = BugleGservices.get().getString(
+ BugleGservicesKeys.MMS_UA_PROFILE_URL,
+ BugleGservicesKeys.MMS_UA_PROFILE_URL_DEFAULT);
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/DatabaseMessages.java b/src/com/android/messaging/sms/DatabaseMessages.java
new file mode 100644
index 0000000..0f662e5
--- /dev/null
+++ b/src/com/android/messaging/sms/DatabaseMessages.java
@@ -0,0 +1,1006 @@
+/*
+ * 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.sms;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.media.VideoThumbnailRequest;
+import com.android.messaging.mmslib.pdu.CharacterSets;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaMetadataRetrieverWrapper;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.google.common.collect.Lists;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class contains various SMS/MMS database entities from telephony provider
+ */
+public class DatabaseMessages {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ public abstract static class DatabaseMessage {
+ public abstract int getProtocol();
+ public abstract String getUri();
+ public abstract long getTimestampInMillis();
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null || !(other instanceof DatabaseMessage)) {
+ return false;
+ }
+ final DatabaseMessage otherDbMsg = (DatabaseMessage) other;
+ // No need to check timestamp since we only need this when we compare
+ // messages at the same timestamp
+ return TextUtils.equals(getUri(), otherDbMsg.getUri());
+ }
+
+ @Override
+ public int hashCode() {
+ // No need to check timestamp since we only need this when we compare
+ // messages at the same timestamp
+ return getUri().hashCode();
+ }
+ }
+
+ /**
+ * SMS message
+ */
+ public static class SmsMessage extends DatabaseMessage implements Parcelable {
+ private static int sIota = 0;
+ public static final int INDEX_ID = sIota++;
+ public static final int INDEX_TYPE = sIota++;
+ public static final int INDEX_ADDRESS = sIota++;
+ public static final int INDEX_BODY = sIota++;
+ public static final int INDEX_DATE = sIota++;
+ public static final int INDEX_THREAD_ID = sIota++;
+ public static final int INDEX_STATUS = sIota++;
+ public static final int INDEX_READ = sIota++;
+ public static final int INDEX_SEEN = sIota++;
+ public static final int INDEX_DATE_SENT = sIota++;
+ public static final int INDEX_SUB_ID = sIota++;
+
+ private static String[] sProjection;
+
+ public static String[] getProjection() {
+ if (sProjection == null) {
+ String[] projection = new String[] {
+ Sms._ID,
+ Sms.TYPE,
+ Sms.ADDRESS,
+ Sms.BODY,
+ Sms.DATE,
+ Sms.THREAD_ID,
+ Sms.STATUS,
+ Sms.READ,
+ Sms.SEEN,
+ Sms.DATE_SENT,
+ Sms.SUBSCRIPTION_ID,
+ };
+ if (!MmsUtils.hasSmsDateSentColumn()) {
+ projection[INDEX_DATE_SENT] = Sms.DATE;
+ }
+ if (!OsUtil.isAtLeastL_MR1()) {
+ Assert.equals(INDEX_SUB_ID, projection.length - 1);
+ String[] withoutSubId = new String[projection.length - 1];
+ System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
+ projection = withoutSubId;
+ }
+
+ sProjection = projection;
+ }
+
+ return sProjection;
+ }
+
+ public String mUri;
+ public String mAddress;
+ public String mBody;
+ private long mRowId;
+ public long mTimestampInMillis;
+ public long mTimestampSentInMillis;
+ public int mType;
+ public long mThreadId;
+ public int mStatus;
+ public boolean mRead;
+ public boolean mSeen;
+ public int mSubId;
+
+ private SmsMessage() {
+ }
+
+ /**
+ * Load from a cursor of a query that returns the SMS to import
+ *
+ * @param cursor
+ */
+ private void load(final Cursor cursor) {
+ mRowId = cursor.getLong(INDEX_ID);
+ mAddress = cursor.getString(INDEX_ADDRESS);
+ mBody = cursor.getString(INDEX_BODY);
+ mTimestampInMillis = cursor.getLong(INDEX_DATE);
+ // Before ICS, there is no "date_sent" so use copy of "date" value
+ mTimestampSentInMillis = cursor.getLong(INDEX_DATE_SENT);
+ mType = cursor.getInt(INDEX_TYPE);
+ mThreadId = cursor.getLong(INDEX_THREAD_ID);
+ mStatus = cursor.getInt(INDEX_STATUS);
+ mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
+ mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
+ mUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mRowId).toString();
+ mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
+ }
+
+ /**
+ * Get a new SmsMessage by loading from the cursor of a query
+ * that returns the SMS to import
+ *
+ * @param cursor
+ * @return
+ */
+ public static SmsMessage get(final Cursor cursor) {
+ final SmsMessage msg = new SmsMessage();
+ msg.load(cursor);
+ return msg;
+ }
+
+ @Override
+ public String getUri() {
+ return mUri;
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ @Override
+ public int getProtocol() {
+ return MessageData.PROTOCOL_SMS;
+ }
+
+ @Override
+ public long getTimestampInMillis() {
+ return mTimestampInMillis;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private SmsMessage(final Parcel in) {
+ mUri = in.readString();
+ mRowId = in.readLong();
+ mTimestampInMillis = in.readLong();
+ mTimestampSentInMillis = in.readLong();
+ mType = in.readInt();
+ mThreadId = in.readLong();
+ mStatus = in.readInt();
+ mRead = in.readInt() != 0;
+ mSeen = in.readInt() != 0;
+ mSubId = in.readInt();
+
+ // SMS specific
+ mAddress = in.readString();
+ mBody = in.readString();
+ }
+
+ public static final Parcelable.Creator<SmsMessage> CREATOR
+ = new Parcelable.Creator<SmsMessage>() {
+ @Override
+ public SmsMessage createFromParcel(final Parcel in) {
+ return new SmsMessage(in);
+ }
+
+ @Override
+ public SmsMessage[] newArray(final int size) {
+ return new SmsMessage[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ out.writeString(mUri);
+ out.writeLong(mRowId);
+ out.writeLong(mTimestampInMillis);
+ out.writeLong(mTimestampSentInMillis);
+ out.writeInt(mType);
+ out.writeLong(mThreadId);
+ out.writeInt(mStatus);
+ out.writeInt(mRead ? 1 : 0);
+ out.writeInt(mSeen ? 1 : 0);
+ out.writeInt(mSubId);
+
+ // SMS specific
+ out.writeString(mAddress);
+ out.writeString(mBody);
+ }
+ }
+
+ /**
+ * MMS message
+ */
+ public static class MmsMessage extends DatabaseMessage implements Parcelable {
+ private static int sIota = 0;
+ public static final int INDEX_ID = sIota++;
+ public static final int INDEX_MESSAGE_BOX = sIota++;
+ public static final int INDEX_SUBJECT = sIota++;
+ public static final int INDEX_SUBJECT_CHARSET = sIota++;
+ public static final int INDEX_MESSAGE_SIZE = sIota++;
+ public static final int INDEX_DATE = sIota++;
+ public static final int INDEX_DATE_SENT = sIota++;
+ public static final int INDEX_THREAD_ID = sIota++;
+ public static final int INDEX_PRIORITY = sIota++;
+ public static final int INDEX_STATUS = sIota++;
+ public static final int INDEX_READ = sIota++;
+ public static final int INDEX_SEEN = sIota++;
+ public static final int INDEX_CONTENT_LOCATION = sIota++;
+ public static final int INDEX_TRANSACTION_ID = sIota++;
+ public static final int INDEX_MESSAGE_TYPE = sIota++;
+ public static final int INDEX_EXPIRY = sIota++;
+ public static final int INDEX_RESPONSE_STATUS = sIota++;
+ public static final int INDEX_RETRIEVE_STATUS = sIota++;
+ public static final int INDEX_SUB_ID = sIota++;
+
+ private static String[] sProjection;
+
+ public static String[] getProjection() {
+ if (sProjection == null) {
+ String[] projection = new String[] {
+ Mms._ID,
+ Mms.MESSAGE_BOX,
+ Mms.SUBJECT,
+ Mms.SUBJECT_CHARSET,
+ Mms.MESSAGE_SIZE,
+ Mms.DATE,
+ Mms.DATE_SENT,
+ Mms.THREAD_ID,
+ Mms.PRIORITY,
+ Mms.STATUS,
+ Mms.READ,
+ Mms.SEEN,
+ Mms.CONTENT_LOCATION,
+ Mms.TRANSACTION_ID,
+ Mms.MESSAGE_TYPE,
+ Mms.EXPIRY,
+ Mms.RESPONSE_STATUS,
+ Mms.RETRIEVE_STATUS,
+ Mms.SUBSCRIPTION_ID,
+ };
+
+ if (!OsUtil.isAtLeastL_MR1()) {
+ Assert.equals(INDEX_SUB_ID, projection.length - 1);
+ String[] withoutSubId = new String[projection.length - 1];
+ System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
+ projection = withoutSubId;
+ }
+
+ sProjection = projection;
+ }
+
+ return sProjection;
+ }
+
+ public String mUri;
+ private long mRowId;
+ public int mType;
+ public String mSubject;
+ public int mSubjectCharset;
+ private long mSize;
+ public long mTimestampInMillis;
+ public long mSentTimestampInMillis;
+ public long mThreadId;
+ public int mPriority;
+ public int mStatus;
+ public boolean mRead;
+ public boolean mSeen;
+ public String mContentLocation;
+ public String mTransactionId;
+ public int mMmsMessageType;
+ public long mExpiryInMillis;
+ public int mSubId;
+ public String mSender;
+ public int mResponseStatus;
+ public int mRetrieveStatus;
+
+ public List<MmsPart> mParts = Lists.newArrayList();
+ private boolean mPartsProcessed = false;
+
+ private MmsMessage() {
+ }
+
+ /**
+ * Load from a cursor of a query that returns the MMS to import
+ *
+ * @param cursor
+ */
+ public void load(final Cursor cursor) {
+ mRowId = cursor.getLong(INDEX_ID);
+ mType = cursor.getInt(INDEX_MESSAGE_BOX);
+ mSubject = cursor.getString(INDEX_SUBJECT);
+ mSubjectCharset = cursor.getInt(INDEX_SUBJECT_CHARSET);
+ if (!TextUtils.isEmpty(mSubject)) {
+ // PduPersister stores the subject using ISO_8859_1
+ // Let's load it using that encoding and convert it back to its original
+ // See PduPersister.persist and PduPersister.toIsoString
+ // (Refer to bug b/11162476)
+ mSubject = getDecodedString(
+ getStringBytes(mSubject, CharacterSets.ISO_8859_1), mSubjectCharset);
+ }
+ mSize = cursor.getLong(INDEX_MESSAGE_SIZE);
+ // MMS db times are in seconds
+ mTimestampInMillis = cursor.getLong(INDEX_DATE) * 1000;
+ mSentTimestampInMillis = cursor.getLong(INDEX_DATE_SENT) * 1000;
+ mThreadId = cursor.getLong(INDEX_THREAD_ID);
+ mPriority = cursor.getInt(INDEX_PRIORITY);
+ mStatus = cursor.getInt(INDEX_STATUS);
+ mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
+ mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
+ mContentLocation = cursor.getString(INDEX_CONTENT_LOCATION);
+ mTransactionId = cursor.getString(INDEX_TRANSACTION_ID);
+ mMmsMessageType = cursor.getInt(INDEX_MESSAGE_TYPE);
+ mExpiryInMillis = cursor.getLong(INDEX_EXPIRY) * 1000;
+ mResponseStatus = cursor.getInt(INDEX_RESPONSE_STATUS);
+ mRetrieveStatus = cursor.getInt(INDEX_RETRIEVE_STATUS);
+ // Clear all parts in case we reuse this object
+ mParts.clear();
+ mPartsProcessed = false;
+ mUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mRowId).toString();
+ mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
+ }
+
+ /**
+ * Get a new MmsMessage by loading from the cursor of a query
+ * that returns the MMS to import
+ *
+ * @param cursor
+ * @return
+ */
+ public static MmsMessage get(final Cursor cursor) {
+ final MmsMessage msg = new MmsMessage();
+ msg.load(cursor);
+ return msg;
+ }
+ /**
+ * Add a loaded MMS part
+ *
+ * @param part
+ */
+ public void addPart(final MmsPart part) {
+ mParts.add(part);
+ }
+
+ public List<MmsPart> getParts() {
+ return mParts;
+ }
+
+ public long getSize() {
+ if (!mPartsProcessed) {
+ processParts();
+ }
+ return mSize;
+ }
+
+ /**
+ * Process loaded MMS parts to obtain the combined text, the combined attachment url,
+ * the combined content type and the combined size.
+ */
+ private void processParts() {
+ if (mPartsProcessed) {
+ return;
+ }
+ mPartsProcessed = true;
+ // Remember the width and height of the first media part
+ // These are needed when building attachment list
+ long sizeOfParts = 0L;
+ for (final MmsPart part : mParts) {
+ sizeOfParts += part.mSize;
+ }
+ if (mSize <= 0) {
+ mSize = mSubject != null ? mSubject.getBytes().length : 0L;
+ mSize += sizeOfParts;
+ }
+ }
+
+ @Override
+ public String getUri() {
+ return mUri;
+ }
+
+ public long getId() {
+ return mRowId;
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ @Override
+ public int getProtocol() {
+ return MessageData.PROTOCOL_MMS;
+ }
+
+ @Override
+ public long getTimestampInMillis() {
+ return mTimestampInMillis;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void setSender(final String sender) {
+ mSender = sender;
+ }
+
+ private MmsMessage(final Parcel in) {
+ mUri = in.readString();
+ mRowId = in.readLong();
+ mTimestampInMillis = in.readLong();
+ mSentTimestampInMillis = in.readLong();
+ mType = in.readInt();
+ mThreadId = in.readLong();
+ mStatus = in.readInt();
+ mRead = in.readInt() != 0;
+ mSeen = in.readInt() != 0;
+ mSubId = in.readInt();
+
+ // MMS specific
+ mSubject = in.readString();
+ mContentLocation = in.readString();
+ mTransactionId = in.readString();
+ mSender = in.readString();
+
+ mSize = in.readLong();
+ mExpiryInMillis = in.readLong();
+
+ mSubjectCharset = in.readInt();
+ mPriority = in.readInt();
+ mMmsMessageType = in.readInt();
+ mResponseStatus = in.readInt();
+ mRetrieveStatus = in.readInt();
+
+ final int nParts = in.readInt();
+ mParts = new ArrayList<MmsPart>();
+ mPartsProcessed = false;
+ for (int i = 0; i < nParts; i++) {
+ mParts.add((MmsPart) in.readParcelable(getClass().getClassLoader()));
+ }
+ }
+
+ public static final Parcelable.Creator<MmsMessage> CREATOR
+ = new Parcelable.Creator<MmsMessage>() {
+ @Override
+ public MmsMessage createFromParcel(final Parcel in) {
+ return new MmsMessage(in);
+ }
+
+ @Override
+ public MmsMessage[] newArray(final int size) {
+ return new MmsMessage[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ out.writeString(mUri);
+ out.writeLong(mRowId);
+ out.writeLong(mTimestampInMillis);
+ out.writeLong(mSentTimestampInMillis);
+ out.writeInt(mType);
+ out.writeLong(mThreadId);
+ out.writeInt(mStatus);
+ out.writeInt(mRead ? 1 : 0);
+ out.writeInt(mSeen ? 1 : 0);
+ out.writeInt(mSubId);
+
+ out.writeString(mSubject);
+ out.writeString(mContentLocation);
+ out.writeString(mTransactionId);
+ out.writeString(mSender);
+
+ out.writeLong(mSize);
+ out.writeLong(mExpiryInMillis);
+
+ out.writeInt(mSubjectCharset);
+ out.writeInt(mPriority);
+ out.writeInt(mMmsMessageType);
+ out.writeInt(mResponseStatus);
+ out.writeInt(mRetrieveStatus);
+
+ out.writeInt(mParts.size());
+ for (final MmsPart part : mParts) {
+ out.writeParcelable(part, 0);
+ }
+ }
+ }
+
+ /**
+ * Part of an MMS message
+ */
+ public static class MmsPart implements Parcelable {
+ public static final String[] PROJECTION = new String[] {
+ Mms.Part._ID,
+ Mms.Part.MSG_ID,
+ Mms.Part.CHARSET,
+ Mms.Part.CONTENT_TYPE,
+ Mms.Part.TEXT,
+ };
+ private static int sIota = 0;
+ public static final int INDEX_ID = sIota++;
+ public static final int INDEX_MSG_ID = sIota++;
+ public static final int INDEX_CHARSET = sIota++;
+ public static final int INDEX_CONTENT_TYPE = sIota++;
+ public static final int INDEX_TEXT = sIota++;
+
+ public String mUri;
+ public long mRowId;
+ public long mMessageId;
+ public String mContentType;
+ public String mText;
+ public int mCharset;
+ private int mWidth;
+ private int mHeight;
+ public long mSize;
+
+ private MmsPart() {
+ }
+
+ /**
+ * Load from a cursor of a query that returns the MMS part to import
+ *
+ * @param cursor
+ */
+ public void load(final Cursor cursor, final boolean loadMedia) {
+ mRowId = cursor.getLong(INDEX_ID);
+ mMessageId = cursor.getLong(INDEX_MSG_ID);
+ mContentType = cursor.getString(INDEX_CONTENT_TYPE);
+ mText = cursor.getString(INDEX_TEXT);
+ mCharset = cursor.getInt(INDEX_CHARSET);
+ mWidth = 0;
+ mHeight = 0;
+ mSize = 0;
+ if (isMedia()) {
+ // For importing we don't load media since performance is critical
+ // For loading when we receive mms, we do load media to get enough
+ // information of the media file
+ if (loadMedia) {
+ if (ContentType.isImageType(mContentType)) {
+ loadImage();
+ } else if (ContentType.isVideoType(mContentType)) {
+ loadVideo();
+ } // No need to load audio for parsing
+ mSize = MmsUtils.getMediaFileSize(getDataUri());
+ }
+ } else {
+ // Load text if not media type
+ loadText();
+ }
+ mUri = Uri.withAppendedPath(Mms.CONTENT_URI, cursor.getString(INDEX_ID)).toString();
+ }
+
+ /**
+ * Get content type from file extension
+ */
+ private static String extractContentType(final Context context, final Uri uri) {
+ final String path = uri.getPath();
+ final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
+ String extension = MimeTypeMap.getFileExtensionFromUrl(path);
+ if (TextUtils.isEmpty(extension)) {
+ // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle
+ // urlEncoded strings. Let's try one last time at finding the extension.
+ final int dotPos = path.lastIndexOf('.');
+ if (0 <= dotPos) {
+ extension = path.substring(dotPos + 1);
+ }
+ }
+ return mimeTypeMap.getMimeTypeFromExtension(extension);
+ }
+
+ /**
+ * Get text of a text part
+ */
+ private void loadText() {
+ byte[] data = null;
+ if (isEmbeddedTextType()) {
+ // Embedded text, get from the "text" column
+ if (!TextUtils.isEmpty(mText)) {
+ data = getStringBytes(mText, mCharset);
+ }
+ } else {
+ // Not embedded, load from disk
+ final ContentResolver resolver =
+ Factory.get().getApplicationContext().getContentResolver();
+ final Uri uri = getDataUri();
+ InputStream is = null;
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ is = resolver.openInputStream(uri);
+ final byte[] buffer = new byte[256];
+ int len = is.read(buffer);
+ while (len >= 0) {
+ baos.write(buffer, 0, len);
+ len = is.read(buffer);
+ }
+ } catch (final IOException e) {
+ LogUtil.e(TAG,
+ "DatabaseMessages.MmsPart: loading text from file failed: " + e, e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "DatabaseMessages.MmsPart: close file failed: " + e, e);
+ }
+ }
+ }
+ data = baos.toByteArray();
+ }
+ if (data != null && data.length > 0) {
+ mSize = data.length;
+ mText = getDecodedString(data, mCharset);
+ }
+ }
+
+ /**
+ * Load image file of an image part and parse the dimensions and type
+ */
+ private void loadImage() {
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri uri = getDataUri();
+ // We have to get the width and height of the image -- they're needed when adding
+ // an attachment in bugle.
+ InputStream is = null;
+ try {
+ is = resolver.openInputStream(uri);
+ final BitmapFactory.Options opt = new BitmapFactory.Options();
+ opt.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, opt);
+ mContentType = opt.outMimeType;
+ mWidth = opt.outWidth;
+ mHeight = opt.outHeight;
+ if (TextUtils.isEmpty(mContentType)) {
+ // BitmapFactory couldn't figure out the image type. That's got to be a bad
+ // sign, but see if we can figure it out from the file extension.
+ mContentType = extractContentType(context, uri);
+ }
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "DatabaseMessages.MmsPart.loadImage: file not found", e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ Log.e(TAG, "IOException caught while closing stream", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load video file of a video part and parse the dimensions and type
+ */
+ private void loadVideo() {
+ // This is a coarse check, and should not be applied to outgoing messages. However,
+ // currently, this does not cause any problems.
+ if (!VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()) {
+ return;
+ }
+ final Uri uri = getDataUri();
+ final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
+ try {
+ retriever.setDataSource(uri);
+ // FLAG: This inadvertently fixes a problem with phone receiving audio
+ // messages on some carrier. We should handle this in a less accidental way so that
+ // we don't break it again. (The carrier changes the content type in the wrapper
+ // in-transit from audio/mp4 to video/3gpp without changing the data)
+ // Also note: There is a bug in some OEM device where mmr returns
+ // video/ffmpeg for image files. That shouldn't happen here but be aware.
+ mContentType =
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
+ final Bitmap bitmap = retriever.getFrameAtTime(-1);
+ if (bitmap != null) {
+ mWidth = bitmap.getWidth();
+ mHeight = bitmap.getHeight();
+ } else {
+ // Get here if it's not actually video (see above)
+ LogUtil.i(LogUtil.BUGLE_TAG, "loadVideo: Got null bitmap from " + uri);
+ }
+ } catch (IOException e) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting metadata from " + uri, e);
+ } finally {
+ retriever.release();
+ }
+ }
+
+ /**
+ * Get media file size
+ */
+ private long getMediaFileSize() {
+ final Context context = Factory.get().getApplicationContext();
+ final Uri uri = getDataUri();
+ AssetFileDescriptor fd = null;
+ try {
+ fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
+ if (fd != null) {
+ return fd.getParcelFileDescriptor().getStatSize();
+ }
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "DatabaseMessages.MmsPart: cound not find media file: " + e, e);
+ } finally {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "DatabaseMessages.MmsPart: failed to close " + e, e);
+ }
+ }
+ }
+ return 0L;
+ }
+
+ /**
+ * @return If the type is a text type that stores text embedded (i.e. in db table)
+ */
+ private boolean isEmbeddedTextType() {
+ return ContentType.TEXT_PLAIN.equals(mContentType)
+ || ContentType.APP_SMIL.equals(mContentType)
+ || ContentType.TEXT_HTML.equals(mContentType);
+ }
+
+ /**
+ * Get an instance of the MMS part from the part table cursor
+ *
+ * @param cursor
+ * @param loadMedia Whether to load the media file of the part
+ * @return
+ */
+ public static MmsPart get(final Cursor cursor, final boolean loadMedia) {
+ final MmsPart part = new MmsPart();
+ part.load(cursor, loadMedia);
+ return part;
+ }
+
+ public boolean isText() {
+ return ContentType.TEXT_PLAIN.equals(mContentType)
+ || ContentType.TEXT_HTML.equals(mContentType)
+ || ContentType.APP_WAP_XHTML.equals(mContentType);
+ }
+
+ public boolean isMedia() {
+ return ContentType.isImageType(mContentType)
+ || ContentType.isVideoType(mContentType)
+ || ContentType.isAudioType(mContentType)
+ || ContentType.isVCardType(mContentType);
+ }
+
+ public boolean isImage() {
+ return ContentType.isImageType(mContentType);
+ }
+
+ public Uri getDataUri() {
+ return Uri.parse("content://mms/part/" + mRowId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private MmsPart(final Parcel in) {
+ mUri = in.readString();
+ mRowId = in.readLong();
+ mMessageId = in.readLong();
+ mContentType = in.readString();
+ mText = in.readString();
+ mCharset = in.readInt();
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mSize = in.readLong();
+ }
+
+ public static final Parcelable.Creator<MmsPart> CREATOR
+ = new Parcelable.Creator<MmsPart>() {
+ @Override
+ public MmsPart createFromParcel(final Parcel in) {
+ return new MmsPart(in);
+ }
+
+ @Override
+ public MmsPart[] newArray(final int size) {
+ return new MmsPart[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ out.writeString(mUri);
+ out.writeLong(mRowId);
+ out.writeLong(mMessageId);
+ out.writeString(mContentType);
+ out.writeString(mText);
+ out.writeInt(mCharset);
+ out.writeInt(mWidth);
+ out.writeInt(mHeight);
+ out.writeLong(mSize);
+ }
+ }
+
+ /**
+ * This class provides the same DatabaseMessage interface over a local SMS db message
+ */
+ public static class LocalDatabaseMessage extends DatabaseMessage implements Parcelable {
+ private final int mProtocol;
+ private final String mUri;
+ private final long mTimestamp;
+ private final long mLocalId;
+ private final String mConversationId;
+
+ public LocalDatabaseMessage(final long localId, final int protocol, final String uri,
+ final long timestamp, final String conversationId) {
+ mLocalId = localId;
+ mProtocol = protocol;
+ mUri = uri;
+ mTimestamp = timestamp;
+ mConversationId = conversationId;
+ }
+
+ @Override
+ public int getProtocol() {
+ return mProtocol;
+ }
+
+ @Override
+ public long getTimestampInMillis() {
+ return mTimestamp;
+ }
+
+ @Override
+ public String getUri() {
+ return mUri;
+ }
+
+ public long getLocalId() {
+ return mLocalId;
+ }
+
+ public String getConversationId() {
+ return mConversationId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private LocalDatabaseMessage(final Parcel in) {
+ mUri = in.readString();
+ mConversationId = in.readString();
+ mLocalId = in.readLong();
+ mTimestamp = in.readLong();
+ mProtocol = in.readInt();
+ }
+
+ public static final Parcelable.Creator<LocalDatabaseMessage> CREATOR
+ = new Parcelable.Creator<LocalDatabaseMessage>() {
+ @Override
+ public LocalDatabaseMessage createFromParcel(final Parcel in) {
+ return new LocalDatabaseMessage(in);
+ }
+
+ @Override
+ public LocalDatabaseMessage[] newArray(final int size) {
+ return new LocalDatabaseMessage[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ out.writeString(mUri);
+ out.writeString(mConversationId);
+ out.writeLong(mLocalId);
+ out.writeLong(mTimestamp);
+ out.writeInt(mProtocol);
+ }
+ }
+
+ /**
+ * Address for MMS message
+ */
+ public static class MmsAddr {
+ public static final String[] PROJECTION = new String[] {
+ Mms.Addr.ADDRESS,
+ Mms.Addr.CHARSET,
+ };
+ private static int sIota = 0;
+ public static final int INDEX_ADDRESS = sIota++;
+ public static final int INDEX_CHARSET = sIota++;
+
+ public static String get(final Cursor cursor) {
+ final int charset = cursor.getInt(INDEX_CHARSET);
+ // PduPersister stores the addresses using ISO_8859_1
+ // Let's load it using that encoding and convert it back to its original
+ // See PduPersister.persistAddress
+ return getDecodedString(
+ getStringBytes(cursor.getString(INDEX_ADDRESS), CharacterSets.ISO_8859_1),
+ charset);
+ }
+ }
+
+ /**
+ * Decoded string by character set
+ */
+ public static String getDecodedString(final byte[] data, final int charset) {
+ if (CharacterSets.ANY_CHARSET == charset) {
+ return new String(data); // system default encoding.
+ } else {
+ try {
+ final String name = CharacterSets.getMimeName(charset);
+ return new String(data, name);
+ } catch (final UnsupportedEncodingException e) {
+ try {
+ return new String(data, CharacterSets.MIMENAME_ISO_8859_1);
+ } catch (final UnsupportedEncodingException exception) {
+ return new String(data); // system default encoding.
+ }
+ }
+ }
+ }
+
+ /**
+ * Unpack a given String into a byte[].
+ */
+ public static byte[] getStringBytes(final String data, final int charset) {
+ if (CharacterSets.ANY_CHARSET == charset) {
+ return data.getBytes();
+ } else {
+ try {
+ final String name = CharacterSets.getMimeName(charset);
+ return data.getBytes(name);
+ } catch (final UnsupportedEncodingException e) {
+ return data.getBytes();
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/MmsConfig.java b/src/com/android/messaging/sms/MmsConfig.java
new file mode 100755
index 0000000..f13d785
--- /dev/null
+++ b/src/com/android/messaging/sms/MmsConfig.java
@@ -0,0 +1,309 @@
+/*
+ * 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.sms;
+
+import android.os.Bundle;
+import android.support.v7.mms.CarrierConfigValuesLoader;
+import android.telephony.SubscriptionInfo;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.SafeAsyncTask;
+import com.google.common.collect.Maps;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * MMS configuration.
+ *
+ * This is now a wrapper around the BugleCarrierConfigValuesLoader, which does
+ * the actual loading and stores the values in a Bundle. This class provides getter
+ * methods for values used in the app, which is easier to use than the raw loader
+ * class.
+ */
+public class MmsConfig {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final int DEFAULT_MAX_TEXT_LENGTH = 2000;
+
+ /*
+ * Key types
+ */
+ public static final String KEY_TYPE_INT = "int";
+ public static final String KEY_TYPE_BOOL = "bool";
+ public static final String KEY_TYPE_STRING = "string";
+
+ private static final Map<String, String> sKeyTypeMap = Maps.newHashMap();
+ static {
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_MMS, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS,
+ KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+ KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+ KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_READ_REPORTS, KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ENABLE_MMS_DELIVERY_REPORTS,
+ KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+ KEY_TYPE_BOOL);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_SOCKET_TIMEOUT, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+ KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH, KEY_TYPE_INT);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_UA_PROF_TAG_NAME, KEY_TYPE_STRING);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_HTTP_PARAMS, KEY_TYPE_STRING);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER, KEY_TYPE_STRING);
+ sKeyTypeMap.put(CarrierConfigValuesLoader.CONFIG_NAI_SUFFIX, KEY_TYPE_STRING);
+ }
+
+ // A map that stores all MmsConfigs, one per active subscription. For pre-LMSim, this will
+ // contain just one entry with the default self sub id; for LMSim and above, this will contain
+ // all active sub ids but the default subscription id - the default subscription id will be
+ // resolved to an active sub id during runtime.
+ private static final Map<Integer, MmsConfig> sSubIdToMmsConfigMap = Maps.newHashMap();
+ // The fallback values
+ private static final MmsConfig sFallback =
+ new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, new Bundle());
+
+ // Per-subscription configuration values.
+ private final Bundle mValues;
+ private final int mSubId;
+
+ /**
+ * Retrieves the MmsConfig instance associated with the given {@code subId}
+ */
+ public static MmsConfig get(final int subId) {
+ final int realSubId = PhoneUtils.getDefault().getEffectiveSubId(subId);
+ synchronized (sSubIdToMmsConfigMap) {
+ final MmsConfig mmsConfig = sSubIdToMmsConfigMap.get(realSubId);
+ if (mmsConfig == null) {
+ // The subId is no longer valid. Fall back to the default config.
+ LogUtil.e(LogUtil.BUGLE_TAG, "Get mms config failed: invalid subId. subId=" + subId
+ + ", real subId=" + realSubId
+ + ", map=" + sSubIdToMmsConfigMap.keySet());
+ return sFallback;
+ }
+ return mmsConfig;
+ }
+ }
+
+ private MmsConfig(final int subId, final Bundle values) {
+ mSubId = subId;
+ mValues = values;
+ }
+
+ /**
+ * Same as load() but doing it using an async thread from SafeAsyncTask thread pool.
+ */
+ public static void loadAsync() {
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ load();
+ }
+ });
+ }
+
+ /**
+ * Reload the device and per-subscription settings.
+ */
+ public static synchronized void load() {
+ final BugleCarrierConfigValuesLoader loader = Factory.get().getCarrierConfigValuesLoader();
+ // Rebuild the entire MmsConfig map.
+ sSubIdToMmsConfigMap.clear();
+ loader.reset();
+ if (OsUtil.isAtLeastL_MR1()) {
+ final List<SubscriptionInfo> subInfoRecords =
+ PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
+ if (subInfoRecords == null) {
+ LogUtil.w(TAG, "Loading mms config failed: no active SIM");
+ return;
+ }
+ for (SubscriptionInfo subInfoRecord : subInfoRecords) {
+ final int subId = subInfoRecord.getSubscriptionId();
+ final Bundle values = loader.get(subId);
+ addMmsConfig(new MmsConfig(subId, values));
+ }
+ } else {
+ final Bundle values = loader.get(ParticipantData.DEFAULT_SELF_SUB_ID);
+ addMmsConfig(new MmsConfig(ParticipantData.DEFAULT_SELF_SUB_ID, values));
+ }
+ }
+
+ private static void addMmsConfig(MmsConfig mmsConfig) {
+ Assert.isTrue(OsUtil.isAtLeastL_MR1() !=
+ (mmsConfig.mSubId == ParticipantData.DEFAULT_SELF_SUB_ID));
+ sSubIdToMmsConfigMap.put(mmsConfig.mSubId, mmsConfig);
+ }
+
+ public int getSmsToMmsTextThreshold() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
+ CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_THRESHOLD_DEFAULT);
+ }
+
+ public int getSmsToMmsTextLengthThreshold() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+ CarrierConfigValuesLoader.CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_DEFAULT);
+ }
+
+ public int getMaxMessageSize() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE,
+ CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_SIZE_DEFAULT);
+ }
+
+ /**
+ * Return the largest MaxMessageSize for any subid
+ */
+ public static int getMaxMaxMessageSize() {
+ int maxMax = 0;
+ for (MmsConfig config : sSubIdToMmsConfigMap.values()) {
+ maxMax = Math.max(maxMax, config.getMaxMessageSize());
+ }
+ return maxMax > 0 ? maxMax : sFallback.getMaxMessageSize();
+ }
+
+ public boolean getTransIdEnabled() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID,
+ CarrierConfigValuesLoader.CONFIG_ENABLED_TRANS_ID_DEFAULT);
+ }
+
+ public String getEmailGateway() {
+ return mValues.getString(CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER,
+ CarrierConfigValuesLoader.CONFIG_EMAIL_GATEWAY_NUMBER_DEFAULT);
+ }
+
+ public int getMaxImageHeight() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT,
+ CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_HEIGHT_DEFAULT);
+ }
+
+ public int getMaxImageWidth() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH,
+ CarrierConfigValuesLoader.CONFIG_MAX_IMAGE_WIDTH_DEFAULT);
+ }
+
+ public int getRecipientLimit() {
+ final int limit = mValues.getInt(CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT,
+ CarrierConfigValuesLoader.CONFIG_RECIPIENT_LIMIT_DEFAULT);
+ return limit < 0 ? Integer.MAX_VALUE : limit;
+ }
+
+ public int getMaxTextLimit() {
+ final int max = mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE,
+ CarrierConfigValuesLoader.CONFIG_MAX_MESSAGE_TEXT_SIZE_DEFAULT);
+ return max > -1 ? max : DEFAULT_MAX_TEXT_LENGTH;
+ }
+
+ public boolean getMultipartSmsEnabled() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS,
+ CarrierConfigValuesLoader.CONFIG_ENABLE_MULTIPART_SMS_DEFAULT);
+ }
+
+ public boolean getSendMultipartSmsAsSeparateMessages() {
+ return mValues.getBoolean(
+ CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+ CarrierConfigValuesLoader.CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_DEFAULT);
+ }
+
+ public boolean getSMSDeliveryReportsEnabled() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS,
+ CarrierConfigValuesLoader.CONFIG_ENABLE_SMS_DELIVERY_REPORTS_DEFAULT);
+ }
+
+ public boolean getNotifyWapMMSC() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC,
+ CarrierConfigValuesLoader.CONFIG_ENABLED_NOTIFY_WAP_MMSC_DEFAULT);
+ }
+
+ public boolean isAliasEnabled() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED,
+ CarrierConfigValuesLoader.CONFIG_ALIAS_ENABLED_DEFAULT);
+ }
+
+ public int getAliasMinChars() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS,
+ CarrierConfigValuesLoader.CONFIG_ALIAS_MIN_CHARS_DEFAULT);
+ }
+
+ public int getAliasMaxChars() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS,
+ CarrierConfigValuesLoader.CONFIG_ALIAS_MAX_CHARS_DEFAULT);
+ }
+
+ public boolean getAllowAttachAudio() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO,
+ CarrierConfigValuesLoader.CONFIG_ALLOW_ATTACH_AUDIO_DEFAULT);
+ }
+
+ public int getMaxSubjectLength() {
+ return mValues.getInt(CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH,
+ CarrierConfigValuesLoader.CONFIG_MAX_SUBJECT_LENGTH_DEFAULT);
+ }
+
+ public boolean getGroupMmsEnabled() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS,
+ CarrierConfigValuesLoader.CONFIG_ENABLE_GROUP_MMS_DEFAULT);
+ }
+
+ public boolean getSupportMmsContentDisposition() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+ CarrierConfigValuesLoader.CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION_DEFAULT);
+ }
+
+ public boolean getShowCellBroadcast() {
+ return mValues.getBoolean(CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS,
+ CarrierConfigValuesLoader.CONFIG_CELL_BROADCAST_APP_LINKS_DEFAULT);
+ }
+
+ public Object getValue(final String key) {
+ return mValues.get(key);
+ }
+
+ public Set<String> keySet() {
+ return mValues.keySet();
+ }
+
+ public static String getKeyType(final String key) {
+ return sKeyTypeMap.get(key);
+ }
+
+ public void update(final String type, final String key, final String value) {
+ BugleCarrierConfigValuesLoader.update(mValues, type, key, value);
+ }
+}
diff --git a/src/com/android/messaging/sms/MmsFailureException.java b/src/com/android/messaging/sms/MmsFailureException.java
new file mode 100644
index 0000000..dd702ee
--- /dev/null
+++ b/src/com/android/messaging/sms/MmsFailureException.java
@@ -0,0 +1,102 @@
+/*
+ * 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.sms;
+
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.Assert;
+
+/**
+ * Exception for MMS failures
+ */
+public class MmsFailureException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Hint of how we should retry in case of failure. Take values defined in MmsUtils.
+ */
+ public final int retryHint;
+
+ /**
+ * If set, provides a more detailed reason for the failure.
+ */
+ public final int rawStatus;
+
+ private void checkRetryHint() {
+ Assert.isTrue(retryHint == MmsUtils.MMS_REQUEST_AUTO_RETRY
+ || retryHint == MmsUtils.MMS_REQUEST_MANUAL_RETRY
+ || retryHint == MmsUtils.MMS_REQUEST_NO_RETRY);
+ }
+ /**
+ * Creates a new MmsFailureException.
+ *
+ * @param retryHint Hint for how to retry
+ */
+ public MmsFailureException(final int retryHint) {
+ super();
+ this.retryHint = retryHint;
+ checkRetryHint();
+ this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
+ }
+
+ public MmsFailureException(final int retryHint, final int rawStatus) {
+ super();
+ this.retryHint = retryHint;
+ checkRetryHint();
+ this.rawStatus = rawStatus;
+ }
+
+ /**
+ * Creates a new MmsFailureException with the specified detail message.
+ *
+ * @param retryHint Hint for how to retry
+ * @param message the detail message.
+ */
+ public MmsFailureException(final int retryHint, String message) {
+ super(message);
+ this.retryHint = retryHint;
+ checkRetryHint();
+ this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
+ }
+
+ /**
+ * Creates a new MmsFailureException with the specified cause.
+ *
+ * @param retryHint Hint for how to retry
+ * @param cause the cause.
+ */
+ public MmsFailureException(final int retryHint, Throwable cause) {
+ super(cause);
+ this.retryHint = retryHint;
+ checkRetryHint();
+ this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
+ }
+
+ /**
+ * Creates a new MmsFailureException
+ * with the specified detail message and cause.
+ *
+ * @param retryHint Hint for how to retry
+ * @param message the detail message.
+ * @param cause the cause.
+ */
+ public MmsFailureException(final int retryHint, String message, Throwable cause) {
+ super(message, cause);
+ this.retryHint = retryHint;
+ checkRetryHint();
+ this.rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
+ }
+}
diff --git a/src/com/android/messaging/sms/MmsSender.java b/src/com/android/messaging/sms/MmsSender.java
new file mode 100644
index 0000000..6dfa81a
--- /dev/null
+++ b/src/com/android/messaging/sms/MmsSender.java
@@ -0,0 +1,312 @@
+/*
+ * 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.sms;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.mms.MmsManager;
+import android.telephony.SmsManager;
+
+import com.android.messaging.datamodel.MmsFileProvider;
+import com.android.messaging.datamodel.action.SendMessageAction;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+import com.android.messaging.mmslib.pdu.AcknowledgeInd;
+import com.android.messaging.mmslib.pdu.EncodedStringValue;
+import com.android.messaging.mmslib.pdu.GenericPdu;
+import com.android.messaging.mmslib.pdu.NotifyRespInd;
+import com.android.messaging.mmslib.pdu.PduComposer;
+import com.android.messaging.mmslib.pdu.PduHeaders;
+import com.android.messaging.mmslib.pdu.PduParser;
+import com.android.messaging.mmslib.pdu.RetrieveConf;
+import com.android.messaging.mmslib.pdu.SendConf;
+import com.android.messaging.mmslib.pdu.SendReq;
+import com.android.messaging.receiver.SendStatusReceiver;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Class that sends chat message via MMS.
+ *
+ * The interface emulates a blocking send similar to making an HTTP request.
+ */
+public class MmsSender {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ /**
+ * Send an MMS message.
+ *
+ * @param context Context
+ * @param messageUri The unique URI of the message for identifying it during sending
+ * @param sendReq The SendReq PDU of the message
+ * @throws MmsFailureException
+ */
+ public static void sendMms(final Context context, final int subId, final Uri messageUri,
+ final SendReq sendReq, final Bundle sentIntentExras) throws MmsFailureException {
+ sendMms(context,
+ subId,
+ messageUri,
+ null /* locationUrl */,
+ sendReq,
+ true /* responseImportant */,
+ sentIntentExras);
+ }
+
+ /**
+ * Send NotifyRespInd (response to mms auto download).
+ *
+ * @param context Context
+ * @param subId subscription to use to send the response
+ * @param transactionId The transaction id of the MMS message
+ * @param contentLocation The url of the MMS message
+ * @param status The status to send with the NotifyRespInd
+ * @throws MmsFailureException
+ * @throws InvalidHeaderValueException
+ */
+ public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
+ final byte[] transactionId, final String contentLocation, final int status)
+ throws MmsFailureException, InvalidHeaderValueException {
+ // Create the M-NotifyResp.ind
+ final NotifyRespInd notifyRespInd = new NotifyRespInd(
+ PduHeaders.CURRENT_MMS_VERSION, transactionId, status);
+ final Uri messageUri = Uri.parse(contentLocation);
+ // Pack M-NotifyResp.ind and send it
+ sendMms(context,
+ subId,
+ messageUri,
+ MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
+ notifyRespInd,
+ false /* responseImportant */,
+ null /* sentIntentExtras */);
+ }
+
+ /**
+ * Send AcknowledgeInd (response to mms manual download). Ignore failures.
+ *
+ * @param context Context
+ * @param subId The SIM's subId we are currently using
+ * @param transactionId The transaction id of the MMS message
+ * @param contentLocation The url of the MMS message
+ * @throws MmsFailureException
+ * @throws InvalidHeaderValueException
+ */
+ public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
+ final byte[] transactionId, final String contentLocation)
+ throws MmsFailureException, InvalidHeaderValueException {
+ final String selfNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
+ // Create the M-Acknowledge.ind
+ final AcknowledgeInd acknowledgeInd = new AcknowledgeInd(PduHeaders.CURRENT_MMS_VERSION,
+ transactionId);
+ acknowledgeInd.setFrom(new EncodedStringValue(selfNumber));
+ final Uri messageUri = Uri.parse(contentLocation);
+ // Sending
+ sendMms(context,
+ subId,
+ messageUri,
+ MmsConfig.get(subId).getNotifyWapMMSC() ? contentLocation : null,
+ acknowledgeInd,
+ false /*responseImportant*/,
+ null /* sentIntentExtras */);
+ }
+
+ /**
+ * Send a generic PDU.
+ *
+ * @param context Context
+ * @param messageUri The unique URI of the message for identifying it during sending
+ * @param locationUrl The optional URL to send to
+ * @param pdu The PDU to send
+ * @param responseImportant If the sending response is important. Responses to the
+ * Sending of AcknowledgeInd and NotifyRespInd are not important.
+ * @throws MmsFailureException
+ */
+ private static void sendMms(final Context context, final int subId, final Uri messageUri,
+ final String locationUrl, final GenericPdu pdu, final boolean responseImportant,
+ final Bundle sentIntentExtras) throws MmsFailureException {
+ // Write PDU to temporary file to send to platform
+ final Uri contentUri = writePduToTempFile(context, pdu, subId);
+
+ // Construct PendingIntent that will notify us when message sending is complete
+ final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
+ messageUri,
+ context,
+ SendStatusReceiver.class);
+ sentIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
+ sentIntent.putExtra(SendMessageAction.EXTRA_RESPONSE_IMPORTANT, responseImportant);
+ if (sentIntentExtras != null) {
+ sentIntent.putExtras(sentIntentExtras);
+ }
+ final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
+ context,
+ 0 /*request code*/,
+ sentIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ // Send the message
+ MmsManager.sendMultimediaMessage(subId, context, contentUri, locationUrl,
+ sentPendingIntent);
+ }
+
+ private static Uri writePduToTempFile(final Context context, final GenericPdu pdu, int subId)
+ throws MmsFailureException {
+ final Uri contentUri = MmsFileProvider.buildRawMmsUri();
+ final File tempFile = MmsFileProvider.getFile(contentUri);
+ FileOutputStream writer = null;
+ try {
+ // Ensure rawmms directory exists
+ tempFile.getParentFile().mkdirs();
+ writer = new FileOutputStream(tempFile);
+ final byte[] pduBytes = new PduComposer(context, pdu).make();
+ if (pduBytes == null) {
+ throw new MmsFailureException(
+ MmsUtils.MMS_REQUEST_NO_RETRY, "Failed to compose PDU");
+ }
+ if (pduBytes.length > MmsConfig.get(subId).getMaxMessageSize()) {
+ throw new MmsFailureException(
+ MmsUtils.MMS_REQUEST_NO_RETRY,
+ MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
+ }
+ writer.write(pduBytes);
+ } catch (final IOException e) {
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ LogUtil.e(TAG, "Cannot create temporary file " + tempFile.getAbsolutePath(), e);
+ throw new MmsFailureException(
+ MmsUtils.MMS_REQUEST_AUTO_RETRY, "Cannot create raw mms file");
+ } catch (final OutOfMemoryError e) {
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ LogUtil.e(TAG, "Out of memory in composing PDU", e);
+ throw new MmsFailureException(
+ MmsUtils.MMS_REQUEST_MANUAL_RETRY,
+ MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG);
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (final IOException e) {
+ // no action we can take here
+ }
+ }
+ }
+ return contentUri;
+ }
+
+ public static SendConf parseSendConf(byte[] response, int subId) {
+ if (response != null) {
+ final GenericPdu respPdu = new PduParser(
+ response, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
+ if (respPdu != null) {
+ if (respPdu instanceof SendConf) {
+ return (SendConf) respPdu;
+ } else {
+ LogUtil.e(TAG, "MmsSender: send response not SendConf");
+ }
+ } else {
+ // Invalid PDU
+ LogUtil.e(TAG, "MmsSender: send invalid response");
+ }
+ }
+ // Empty or invalid response
+ return null;
+ }
+
+ /**
+ * Download an MMS message.
+ *
+ * @param context Context
+ * @param contentLocation The url of the MMS message
+ * @throws MmsFailureException
+ * @throws InvalidHeaderValueException
+ */
+ public static void downloadMms(final Context context, final int subId,
+ final String contentLocation, Bundle extras) throws MmsFailureException,
+ InvalidHeaderValueException {
+ final Uri requestUri = Uri.parse(contentLocation);
+ final Uri contentUri = MmsFileProvider.buildRawMmsUri();
+
+ final Intent downloadedIntent = new Intent(SendStatusReceiver.MMS_DOWNLOADED_ACTION,
+ requestUri,
+ context,
+ SendStatusReceiver.class);
+ downloadedIntent.putExtra(SendMessageAction.EXTRA_CONTENT_URI, contentUri);
+ if (extras != null) {
+ downloadedIntent.putExtras(extras);
+ }
+ final PendingIntent downloadedPendingIntent = PendingIntent.getBroadcast(
+ context,
+ 0 /*request code*/,
+ downloadedIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ MmsManager.downloadMultimediaMessage(subId, context, contentLocation, contentUri,
+ downloadedPendingIntent);
+ }
+
+ public static RetrieveConf parseRetrieveConf(byte[] data, int subId) {
+ if (data != null) {
+ final GenericPdu pdu = new PduParser(
+ data, MmsConfig.get(subId).getSupportMmsContentDisposition()).parse();
+ if (pdu != null) {
+ if (pdu instanceof RetrieveConf) {
+ return (RetrieveConf) pdu;
+ } else {
+ LogUtil.e(TAG, "MmsSender: downloaded pdu not RetrieveConf: "
+ + pdu.getClass().getName());
+ }
+ } else {
+ LogUtil.e(TAG, "MmsSender: downloaded pdu could not be parsed (invalid)");
+ }
+ }
+ LogUtil.e(TAG, "MmsSender: downloaded pdu is empty");
+ return null;
+ }
+
+ // Process different result code from platform MMS service
+ public static int getErrorResultStatus(int resultCode, int httpStatusCode) {
+ Assert.isFalse(resultCode == Activity.RESULT_OK);
+ switch (resultCode) {
+ case SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS:
+ case SmsManager.MMS_ERROR_IO_ERROR:
+ return MmsUtils.MMS_REQUEST_AUTO_RETRY;
+ case SmsManager.MMS_ERROR_INVALID_APN:
+ case SmsManager.MMS_ERROR_CONFIGURATION_ERROR:
+ case SmsManager.MMS_ERROR_NO_DATA_NETWORK:
+ case SmsManager.MMS_ERROR_UNSPECIFIED:
+ return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ case SmsManager.MMS_ERROR_HTTP_FAILURE:
+ if (httpStatusCode == 404) {
+ return MmsUtils.MMS_REQUEST_NO_RETRY;
+ } else {
+ return MmsUtils.MMS_REQUEST_AUTO_RETRY;
+ }
+ default:
+ return MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/MmsSmsUtils.java b/src/com/android/messaging/sms/MmsSmsUtils.java
new file mode 100644
index 0000000..1a0ef99
--- /dev/null
+++ b/src/com/android/messaging/sms/MmsSmsUtils.java
@@ -0,0 +1,204 @@
+/*
+ * 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.sms;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.util.LogUtil;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility functions for the Messaging Service
+ */
+public class MmsSmsUtils {
+ private MmsSmsUtils() {
+ // Forbidden being instantiated.
+ }
+
+ // An alias (or commonly called "nickname") is:
+ // Nickname must begin with a letter.
+ // Only letters a-z, numbers 0-9, or . are allowed in Nickname field.
+ public static boolean isAlias(final String string, final int subId) {
+ if (!MmsConfig.get(subId).isAliasEnabled()) {
+ return false;
+ }
+
+ final int len = string == null ? 0 : string.length();
+
+ if (len < MmsConfig.get(subId).getAliasMinChars() ||
+ len > MmsConfig.get(subId).getAliasMaxChars()) {
+ return false;
+ }
+
+ if (!Character.isLetter(string.charAt(0))) { // Nickname begins with a letter
+ return false;
+ }
+ for (int i = 1; i < len; i++) {
+ final char c = string.charAt(i);
+ if (!(Character.isLetterOrDigit(c) || c == '.')) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * mailbox = name-addr
+ * name-addr = [display-name] angle-addr
+ * angle-addr = [CFWS] "<" addr-spec ">" [CFWS]
+ */
+ public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+ Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+ public static String extractAddrSpec(final String address) {
+ final Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+ if (match.matches()) {
+ return match.group(2);
+ }
+ return address;
+ }
+
+ /**
+ * Returns true if the address is an email address
+ *
+ * @param address the input address to be tested
+ * @return true if address is an email address
+ */
+ public static boolean isEmailAddress(final String address) {
+ if (TextUtils.isEmpty(address)) {
+ return false;
+ }
+
+ final String s = extractAddrSpec(address);
+ final Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
+ return match.matches();
+ }
+
+ /**
+ * Returns true if the number is a Phone number
+ *
+ * @param number the input number to be tested
+ * @return true if number is a Phone number
+ */
+ public static boolean isPhoneNumber(final String number) {
+ if (TextUtils.isEmpty(number)) {
+ return false;
+ }
+
+ final Matcher match = Patterns.PHONE.matcher(number);
+ return match.matches();
+ }
+
+ /**
+ * Check if MMS is required when sending to email address
+ *
+ * @param destinationHasEmailAddress destination includes an email address
+ * @return true if MMS is required.
+ */
+ public static boolean getRequireMmsForEmailAddress(final boolean destinationHasEmailAddress,
+ final int subId) {
+ if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway())) {
+ return false;
+ } else {
+ return destinationHasEmailAddress;
+ }
+ }
+
+ /**
+ * Helper functions for the "threads" table used by MMS and SMS.
+ */
+ public static final class Threads implements android.provider.Telephony.ThreadsColumns {
+ private static final String[] ID_PROJECTION = { BaseColumns._ID };
+ private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+ "content://mms-sms/threadID");
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ android.provider.Telephony.MmsSms.CONTENT_URI, "conversations");
+
+ // No one should construct an instance of this class.
+ private Threads() {
+ }
+
+ /**
+ * This is a single-recipient version of
+ * getOrCreateThreadId. It's convenient for use with SMS
+ * messages.
+ */
+ public static long getOrCreateThreadId(final Context context, final String recipient) {
+ final Set<String> recipients = new HashSet<String>();
+
+ recipients.add(recipient);
+ return getOrCreateThreadId(context, recipients);
+ }
+
+ /**
+ * Given the recipients list and subject of an unsaved message,
+ * return its thread ID. If the message starts a new thread,
+ * allocate a new thread ID. Otherwise, use the appropriate
+ * existing thread ID.
+ *
+ * Find the thread ID of the same set of recipients (in
+ * any order, without any additions). If one
+ * is found, return it. Otherwise, return a unique thread ID.
+ */
+ public static long getOrCreateThreadId(
+ final Context context, final Set<String> recipients) {
+ final Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+ for (String recipient : recipients) {
+ if (isEmailAddress(recipient)) {
+ recipient = extractAddrSpec(recipient);
+ }
+
+ uriBuilder.appendQueryParameter("recipient", recipient);
+ }
+
+ final Uri uri = uriBuilder.build();
+ //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri);
+
+ final Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+ uri, ID_PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ } else {
+ LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG,
+ "getOrCreateThreadId returned no rows!");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ LogUtil.e(LogUtil.BUGLE_DATAMODEL_TAG, "getOrCreateThreadId failed with "
+ + LogUtil.sanitizePII(recipients.toString()));
+ throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/MmsUtils.java b/src/com/android/messaging/sms/MmsUtils.java
new file mode 100644
index 0000000..913e9a6
--- /dev/null
+++ b/src/com/android/messaging/sms/MmsUtils.java
@@ -0,0 +1,2747 @@
+/*
+ * 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.sms;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.media.MediaMetadataRetriever;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.text.TextUtils;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.action.DownloadMmsAction;
+import com.android.messaging.datamodel.action.SendMessageAction;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+import com.android.messaging.mmslib.MmsException;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.mmslib.pdu.CharacterSets;
+import com.android.messaging.mmslib.pdu.EncodedStringValue;
+import com.android.messaging.mmslib.pdu.GenericPdu;
+import com.android.messaging.mmslib.pdu.NotificationInd;
+import com.android.messaging.mmslib.pdu.PduBody;
+import com.android.messaging.mmslib.pdu.PduComposer;
+import com.android.messaging.mmslib.pdu.PduHeaders;
+import com.android.messaging.mmslib.pdu.PduParser;
+import com.android.messaging.mmslib.pdu.PduPart;
+import com.android.messaging.mmslib.pdu.PduPersister;
+import com.android.messaging.mmslib.pdu.RetrieveConf;
+import com.android.messaging.mmslib.pdu.SendConf;
+import com.android.messaging.mmslib.pdu.SendReq;
+import com.android.messaging.sms.SmsSender.SendResult;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.EmailAddress;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.ImageUtils.ImageResizer;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaMetadataRetrieverWrapper;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.google.common.base.Joiner;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Utils for sending sms/mms messages.
+ */
+public class MmsUtils {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ public static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;
+ public static final boolean DEFAULT_READ_REPORT_MODE = false;
+ public static final long DEFAULT_EXPIRY_TIME_IN_SECONDS = 7 * 24 * 60 * 60;
+ public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
+
+ public static final int MAX_SMS_RETRY = 3;
+
+ /**
+ * MMS request succeeded
+ */
+ public static final int MMS_REQUEST_SUCCEEDED = 0;
+ /**
+ * MMS request failed with a transient error and can be retried automatically
+ */
+ public static final int MMS_REQUEST_AUTO_RETRY = 1;
+ /**
+ * MMS request failed with an error and can be retried manually
+ */
+ public static final int MMS_REQUEST_MANUAL_RETRY = 2;
+ /**
+ * MMS request failed with a specific error and should not be retried
+ */
+ public static final int MMS_REQUEST_NO_RETRY = 3;
+
+ public static final String getRequestStatusDescription(final int status) {
+ switch (status) {
+ case MMS_REQUEST_SUCCEEDED:
+ return "SUCCEEDED";
+ case MMS_REQUEST_AUTO_RETRY:
+ return "AUTO_RETRY";
+ case MMS_REQUEST_MANUAL_RETRY:
+ return "MANUAL_RETRY";
+ case MMS_REQUEST_NO_RETRY:
+ return "NO_RETRY";
+ default:
+ return String.valueOf(status) + " (check MmsUtils)";
+ }
+ }
+
+ public static final int PDU_HEADER_VALUE_UNDEFINED = 0;
+
+ private static final int DEFAULT_DURATION = 5000; //ms
+
+ // amount of space to leave in a MMS for text and overhead.
+ private static final int MMS_MAX_SIZE_SLOP = 1024;
+ public static final long INVALID_TIMESTAMP = 0L;
+ private static String[] sNoSubjectStrings;
+
+ public static class MmsInfo {
+ public Uri mUri;
+ public int mMessageSize;
+ public PduBody mPduBody;
+ }
+
+ // Sync all remote messages apart from drafts
+ private static final String REMOTE_SMS_SELECTION = String.format(
+ Locale.US,
+ "(%s IN (%d, %d, %d, %d, %d))",
+ Sms.TYPE,
+ Sms.MESSAGE_TYPE_INBOX,
+ Sms.MESSAGE_TYPE_OUTBOX,
+ Sms.MESSAGE_TYPE_QUEUED,
+ Sms.MESSAGE_TYPE_FAILED,
+ Sms.MESSAGE_TYPE_SENT);
+
+ private static final String REMOTE_MMS_SELECTION = String.format(
+ Locale.US,
+ "((%s IN (%d, %d, %d, %d)) AND (%s IN (%d, %d, %d)))",
+ Mms.MESSAGE_BOX,
+ Mms.MESSAGE_BOX_INBOX,
+ Mms.MESSAGE_BOX_OUTBOX,
+ Mms.MESSAGE_BOX_SENT,
+ Mms.MESSAGE_BOX_FAILED,
+ Mms.MESSAGE_TYPE,
+ PduHeaders.MESSAGE_TYPE_SEND_REQ,
+ PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND,
+ PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+
+ /**
+ * Type selection for importing sms messages.
+ *
+ * @return The SQL selection for importing sms messages
+ */
+ public static String getSmsTypeSelectionSql() {
+ return REMOTE_SMS_SELECTION;
+ }
+
+ /**
+ * Type selection for importing mms messages.
+ *
+ * @return The SQL selection for importing mms messages. This selects the message type,
+ * not including the selection on timestamp.
+ */
+ public static String getMmsTypeSelectionSql() {
+ return REMOTE_MMS_SELECTION;
+ }
+
+ // SMIL spec: http://www.w3.org/TR/SMIL3
+
+ private static final String sSmilImagePart =
+ "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
+ "<img src=\"%s\" region=\"Image\" />" +
+ "</par>";
+
+ private static final String sSmilVideoPart =
+ "<par dur=\"%2$dms\">" +
+ "<video src=\"%1$s\" dur=\"%2$dms\" region=\"Image\" />" +
+ "</par>";
+
+ private static final String sSmilAudioPart =
+ "<par dur=\"%2$dms\">" +
+ "<audio src=\"%1$s\" dur=\"%2$dms\" />" +
+ "</par>";
+
+ private static final String sSmilTextPart =
+ "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
+ "<text src=\"%s\" region=\"Text\" />" +
+ "</par>";
+
+ private static final String sSmilPart =
+ "<par dur=\"" + DEFAULT_DURATION + "ms\">" +
+ "<ref src=\"%s\" />" +
+ "</par>";
+
+ private static final String sSmilTextOnly =
+ "<smil>" +
+ "<head>" +
+ "<layout>" +
+ "<root-layout/>" +
+ "<region id=\"Text\" top=\"0\" left=\"0\" "
+ + "height=\"100%%\" width=\"100%%\"/>" +
+ "</layout>" +
+ "</head>" +
+ "<body>" +
+ "%s" + // constructed body goes here
+ "</body>" +
+ "</smil>";
+
+ private static final String sSmilVisualAttachmentsOnly =
+ "<smil>" +
+ "<head>" +
+ "<layout>" +
+ "<root-layout/>" +
+ "<region id=\"Image\" fit=\"meet\" top=\"0\" left=\"0\" "
+ + "height=\"100%%\" width=\"100%%\"/>" +
+ "</layout>" +
+ "</head>" +
+ "<body>" +
+ "%s" + // constructed body goes here
+ "</body>" +
+ "</smil>";
+
+ private static final String sSmilVisualAttachmentsWithText =
+ "<smil>" +
+ "<head>" +
+ "<layout>" +
+ "<root-layout/>" +
+ "<region id=\"Image\" fit=\"meet\" top=\"0\" left=\"0\" "
+ + "height=\"80%%\" width=\"100%%\"/>" +
+ "<region id=\"Text\" top=\"80%%\" left=\"0\" height=\"20%%\" "
+ + "width=\"100%%\"/>" +
+ "</layout>" +
+ "</head>" +
+ "<body>" +
+ "%s" + // constructed body goes here
+ "</body>" +
+ "</smil>";
+
+ private static final String sSmilNonVisualAttachmentsOnly =
+ "<smil>" +
+ "<head>" +
+ "<layout>" +
+ "<root-layout/>" +
+ "</layout>" +
+ "</head>" +
+ "<body>" +
+ "%s" + // constructed body goes here
+ "</body>" +
+ "</smil>";
+
+ private static final String sSmilNonVisualAttachmentsWithText = sSmilTextOnly;
+
+ public static final String MMS_DUMP_PREFIX = "mmsdump-";
+ public static final String SMS_DUMP_PREFIX = "smsdump-";
+
+ public static final int MIN_VIDEO_BYTES_PER_SECOND = 4 * 1024;
+ public static final int MIN_IMAGE_BYTE_SIZE = 16 * 1024;
+ public static final int MAX_VIDEO_ATTACHMENT_COUNT = 1;
+
+ public static MmsInfo makePduBody(final Context context, final MessageData message,
+ final int subId) {
+ final PduBody pb = new PduBody();
+
+ // Compute data size requirements for this message: count up images and total size of
+ // non-image attachments.
+ int totalLength = 0;
+ int countImage = 0;
+ for (final MessagePartData part : message.getParts()) {
+ if (part.isAttachment()) {
+ final String contentType = part.getContentType();
+ if (ContentType.isImageType(contentType)) {
+ countImage++;
+ } else if (ContentType.isVCardType(contentType)) {
+ totalLength += getDataLength(context, part.getContentUri());
+ } else {
+ totalLength += getMediaFileSize(part.getContentUri());
+ }
+ }
+ }
+ final long minSize = countImage * MIN_IMAGE_BYTE_SIZE;
+ final int byteBudget = MmsConfig.get(subId).getMaxMessageSize() - totalLength
+ - MMS_MAX_SIZE_SLOP;
+ final double budgetFactor =
+ minSize > 0 ? Math.max(1.0, byteBudget / ((double) minSize)) : 1;
+ final int bytesPerImage = (int) (budgetFactor * MIN_IMAGE_BYTE_SIZE);
+ final int widthLimit = MmsConfig.get(subId).getMaxImageWidth();
+ final int heightLimit = MmsConfig.get(subId).getMaxImageHeight();
+
+ // Actually add the attachments, shrinking images appropriately.
+ int index = 0;
+ totalLength = 0;
+ boolean hasVisualAttachment = false;
+ boolean hasNonVisualAttachment = false;
+ boolean hasText = false;
+ final StringBuilder smilBody = new StringBuilder();
+ for (final MessagePartData part : message.getParts()) {
+ String srcName;
+ if (part.isAttachment()) {
+ String contentType = part.getContentType();
+ if (ContentType.isImageType(contentType)) {
+ // There's a good chance that if we selected the image from our media picker the
+ // content type is image/*. Fix the content type here for gifs so that we only
+ // need to open the input stream once. All other gif vs static image checks will
+ // only have to do a string comparison which is much cheaper.
+ final boolean isGif = ImageUtils.isGif(contentType, part.getContentUri());
+ contentType = isGif ? ContentType.IMAGE_GIF : contentType;
+ srcName = String.format(isGif ? "image%06d.gif" : "image%06d.jpg", index);
+ smilBody.append(String.format(sSmilImagePart, srcName));
+ totalLength += addPicturePart(context, pb, index, part,
+ widthLimit, heightLimit, bytesPerImage, srcName, contentType);
+ hasVisualAttachment = true;
+ } else if (ContentType.isVideoType(contentType)) {
+ srcName = String.format("video%06d.mp4", index);
+ final int length = addVideoPart(context, pb, part, srcName);
+ totalLength += length;
+ smilBody.append(String.format(sSmilVideoPart, srcName,
+ getMediaDurationMs(context, part, DEFAULT_DURATION)));
+ hasVisualAttachment = true;
+ } else if (ContentType.isVCardType(contentType)) {
+ srcName = String.format("contact%06d.vcf", index);
+ totalLength += addVCardPart(context, pb, part, srcName);
+ smilBody.append(String.format(sSmilPart, srcName));
+ hasNonVisualAttachment = true;
+ } else if (ContentType.isAudioType(contentType)) {
+ srcName = String.format("recording%06d.amr", index);
+ totalLength += addOtherPart(context, pb, part, srcName);
+ final int duration = getMediaDurationMs(context, part, -1);
+ Assert.isTrue(duration != -1);
+ smilBody.append(String.format(sSmilAudioPart, srcName, duration));
+ hasNonVisualAttachment = true;
+ } else {
+ srcName = String.format("other%06d.dat", index);
+ totalLength += addOtherPart(context, pb, part, srcName);
+ smilBody.append(String.format(sSmilPart, srcName));
+ }
+ index++;
+ }
+ if (!TextUtils.isEmpty(part.getText())) {
+ hasText = true;
+ }
+ }
+
+ if (hasText) {
+ final String srcName = String.format("text.%06d.txt", index);
+ final String text = message.getMessageText();
+ totalLength += addTextPart(context, pb, text, srcName);
+
+ // Append appropriate SMIL to the body.
+ smilBody.append(String.format(sSmilTextPart, srcName));
+ }
+
+ final String smilTemplate = getSmilTemplate(hasVisualAttachment,
+ hasNonVisualAttachment, hasText);
+ addSmilPart(pb, smilTemplate, smilBody.toString());
+
+ final MmsInfo mmsInfo = new MmsInfo();
+ mmsInfo.mPduBody = pb;
+ mmsInfo.mMessageSize = totalLength;
+
+ return mmsInfo;
+ }
+
+ private static int getMediaDurationMs(final Context context, final MessagePartData part,
+ final int defaultDurationMs) {
+ Assert.notNull(context);
+ Assert.notNull(part);
+ Assert.isTrue(ContentType.isAudioType(part.getContentType()) ||
+ ContentType.isVideoType(part.getContentType()));
+
+ final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
+ try {
+ retriever.setDataSource(part.getContentUri());
+ return retriever.extractInteger(
+ MediaMetadataRetriever.METADATA_KEY_DURATION, defaultDurationMs);
+ } catch (final IOException e) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting duration from " + part.getContentUri(), e);
+ return defaultDurationMs;
+ } finally {
+ retriever.release();
+ }
+ }
+
+ private static void setPartContentLocationAndId(final PduPart part, final String srcName) {
+ // Set Content-Location.
+ part.setContentLocation(srcName.getBytes());
+
+ // Set Content-Id.
+ final int index = srcName.lastIndexOf(".");
+ final String contentId = (index == -1) ? srcName : srcName.substring(0, index);
+ part.setContentId(contentId.getBytes());
+ }
+
+ private static int addTextPart(final Context context, final PduBody pb,
+ final String text, final String srcName) {
+ final PduPart part = new PduPart();
+
+ // Set Charset if it's a text media.
+ part.setCharset(CharacterSets.UTF_8);
+
+ // Set Content-Type.
+ part.setContentType(ContentType.TEXT_PLAIN.getBytes());
+
+ // Set Content-Location.
+ setPartContentLocationAndId(part, srcName);
+
+ part.setData(text.getBytes());
+
+ pb.addPart(part);
+
+ return part.getData().length;
+ }
+
+ private static int addPicturePart(final Context context, final PduBody pb, final int index,
+ final MessagePartData messagePart, int widthLimit, int heightLimit,
+ final int maxPartSize, final String srcName, final String contentType) {
+ final Uri imageUri = messagePart.getContentUri();
+ final int width = messagePart.getWidth();
+ final int height = messagePart.getHeight();
+
+ // Swap the width and height limits to match the orientation of the image so we scale the
+ // picture as little as possible.
+ if ((height > width) != (heightLimit > widthLimit)) {
+ final int temp = widthLimit;
+ widthLimit = heightLimit;
+ heightLimit = temp;
+ }
+
+ final int orientation = ImageUtils.getOrientation(context, imageUri);
+ int imageSize = getDataLength(context, imageUri);
+ if (imageSize <= 0) {
+ LogUtil.e(TAG, "Can't get image", new Exception());
+ return 0;
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "addPicturePart size: " + imageSize + " width: "
+ + width + " widthLimit: " + widthLimit
+ + " height: " + height
+ + " heightLimit: " + heightLimit);
+ }
+
+ PduPart part;
+ // Check if we're already within the limits - in which case we don't need to resize.
+ // The size can be zero here, even when the media has content. See the comment in
+ // MediaModel.initMediaSize. Sometimes it'll compute zero and it's costly to read the
+ // whole stream to compute the size. When we call getResizedImageAsPart(), we'll correctly
+ // set the size.
+ if (imageSize <= maxPartSize &&
+ width <= widthLimit &&
+ height <= heightLimit &&
+ (orientation == android.media.ExifInterface.ORIENTATION_UNDEFINED ||
+ orientation == android.media.ExifInterface.ORIENTATION_NORMAL)) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "addPicturePart - already sized");
+ }
+ part = new PduPart();
+ part.setDataUri(imageUri);
+ part.setContentType(contentType.getBytes());
+ } else {
+ part = getResizedImageAsPart(widthLimit, heightLimit, maxPartSize,
+ width, height, orientation, imageUri, context, contentType);
+ if (part == null) {
+ final OutOfMemoryError e = new OutOfMemoryError();
+ LogUtil.e(TAG, "Can't resize image: not enough memory?", e);
+ throw e;
+ }
+ imageSize = part.getData().length;
+ }
+
+ setPartContentLocationAndId(part, srcName);
+
+ pb.addPart(index, part);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "addPicturePart size: " + imageSize);
+ }
+
+ return imageSize;
+ }
+
+ private static void addPartForUri(final Context context, final PduBody pb,
+ final String srcName, final Uri uri, final String contentType) {
+ final PduPart part = new PduPart();
+ part.setDataUri(uri);
+ part.setContentType(contentType.getBytes());
+
+ setPartContentLocationAndId(part, srcName);
+
+ pb.addPart(part);
+ }
+
+ private static int addVCardPart(final Context context, final PduBody pb,
+ final MessagePartData messagePart, final String srcName) {
+ final Uri vcardUri = messagePart.getContentUri();
+ final String contentType = messagePart.getContentType();
+ final int vcardSize = getDataLength(context, vcardUri);
+ if (vcardSize <= 0) {
+ LogUtil.e(TAG, "Can't get vcard", new Exception());
+ return 0;
+ }
+
+ addPartForUri(context, pb, srcName, vcardUri, contentType);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "addVCardPart size: " + vcardSize);
+ }
+
+ return vcardSize;
+ }
+
+ /**
+ * Add video part recompressing video if necessary. If recompression fails, part is not
+ * added.
+ */
+ private static int addVideoPart(final Context context, final PduBody pb,
+ final MessagePartData messagePart, final String srcName) {
+ final Uri attachmentUri = messagePart.getContentUri();
+ String contentType = messagePart.getContentType();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString());
+ }
+
+ if (TextUtils.isEmpty(contentType)) {
+ contentType = ContentType.VIDEO_3G2;
+ }
+
+ addPartForUri(context, pb, srcName, attachmentUri, contentType);
+ return (int) getMediaFileSize(attachmentUri);
+ }
+
+ private static int addOtherPart(final Context context, final PduBody pb,
+ final MessagePartData messagePart, final String srcName) {
+ final Uri attachmentUri = messagePart.getContentUri();
+ final String contentType = messagePart.getContentType();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "addPart attachmentUrl: " + attachmentUri.toString());
+ }
+
+ final int dataSize = (int) getMediaFileSize(attachmentUri);
+
+ addPartForUri(context, pb, srcName, attachmentUri, contentType);
+
+ return dataSize;
+ }
+
+ private static void addSmilPart(final PduBody pb, final String smilTemplate,
+ final String smilBody) {
+ final PduPart smilPart = new PduPart();
+ smilPart.setContentId("smil".getBytes());
+ smilPart.setContentLocation("smil.xml".getBytes());
+ smilPart.setContentType(ContentType.APP_SMIL.getBytes());
+ final String smil = String.format(smilTemplate, smilBody);
+ smilPart.setData(smil.getBytes());
+ pb.addPart(0, smilPart);
+ }
+
+ private static String getSmilTemplate(final boolean hasVisualAttachments,
+ final boolean hasNonVisualAttachments, final boolean hasText) {
+ if (hasVisualAttachments) {
+ return hasText ? sSmilVisualAttachmentsWithText : sSmilVisualAttachmentsOnly;
+ }
+ if (hasNonVisualAttachments) {
+ return hasText ? sSmilNonVisualAttachmentsWithText : sSmilNonVisualAttachmentsOnly;
+ }
+ return sSmilTextOnly;
+ }
+
+ private static int getDataLength(final Context context, final Uri uri) {
+ InputStream is = null;
+ try {
+ is = context.getContentResolver().openInputStream(uri);
+ try {
+ return is == null ? 0 : is.available();
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "getDataLength couldn't stream: " + uri, e);
+ }
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "getDataLength couldn't open: " + uri, e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "getDataLength couldn't close: " + uri, e);
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns {@code true} if group mms is turned on,
+ * {@code false} otherwise.
+ *
+ * For the group mms feature to be enabled, the following must be true:
+ * 1. the feature is enabled in mms_config.xml (currently on by default)
+ * 2. the feature is enabled in the SMS settings page
+ *
+ * @return true if group mms is supported
+ */
+ public static boolean groupMmsEnabled(final int subId) {
+ final Context context = Factory.get().getApplicationContext();
+ final Resources resources = context.getResources();
+ final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
+ final String groupMmsKey = resources.getString(R.string.group_mms_pref_key);
+ final boolean groupMmsEnabledDefault = resources.getBoolean(R.bool.group_mms_pref_default);
+ final boolean groupMmsPrefOn = prefs.getBoolean(groupMmsKey, groupMmsEnabledDefault);
+ return MmsConfig.get(subId).getGroupMmsEnabled() && groupMmsPrefOn;
+ }
+
+ /**
+ * Get a version of this image resized to fit the given dimension and byte-size limits. Note
+ * that the content type of the resulting PduPart may not be the same as the content type of
+ * this UriImage; always call {@link PduPart#getContentType()} to get the new content type.
+ *
+ * @param widthLimit The width limit, in pixels
+ * @param heightLimit The height limit, in pixels
+ * @param byteLimit The binary size limit, in bytes
+ * @param width The image width, in pixels
+ * @param height The image height, in pixels
+ * @param orientation Orientation constant from ExifInterface for rotating or flipping the
+ * image
+ * @param imageUri Uri to the image data
+ * @param context Needed to open the image
+ * @return A new PduPart containing the resized image data
+ */
+ private static PduPart getResizedImageAsPart(final int widthLimit,
+ final int heightLimit, final int byteLimit, final int width, final int height,
+ final int orientation, final Uri imageUri, final Context context, final String contentType) {
+ final PduPart part = new PduPart();
+
+ final byte[] data = ImageResizer.getResizedImageData(width, height, orientation,
+ widthLimit, heightLimit, byteLimit, imageUri, context, contentType);
+ if (data == null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Resize image failed.");
+ }
+ return null;
+ }
+
+ part.setData(data);
+ // Any static images will be compressed into a jpeg
+ final String contentTypeOfResizedImage = ImageUtils.isGif(contentType, imageUri)
+ ? ContentType.IMAGE_GIF : ContentType.IMAGE_JPEG;
+ part.setContentType(contentTypeOfResizedImage.getBytes());
+
+ return part;
+ }
+
+ /**
+ * Get media file size
+ */
+ public static long getMediaFileSize(final Uri uri) {
+ final Context context = Factory.get().getApplicationContext();
+ AssetFileDescriptor fd = null;
+ try {
+ fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
+ if (fd != null) {
+ return fd.getParcelFileDescriptor().getStatSize();
+ }
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "MmsUtils.getMediaFileSize: cound not find media file: " + e, e);
+ } finally {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "MmsUtils.getMediaFileSize: failed to close " + e, e);
+ }
+ }
+ }
+ return 0L;
+ }
+
+ // Code for extracting the actual phone numbers for the participants in a conversation,
+ // given a thread id.
+
+ private static final Uri ALL_THREADS_URI =
+ Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
+
+ private static final String[] RECIPIENTS_PROJECTION = {
+ Threads._ID,
+ Threads.RECIPIENT_IDS
+ };
+
+ private static final int RECIPIENT_IDS = 1;
+
+ public static List<String> getRecipientsByThread(final long threadId) {
+ final String spaceSepIds = getRawRecipientIdsForThread(threadId);
+ if (!TextUtils.isEmpty(spaceSepIds)) {
+ final Context context = Factory.get().getApplicationContext();
+ return getAddresses(context, spaceSepIds);
+ }
+ return null;
+ }
+
+ // NOTE: There are phones on which you can't get the recipients from the thread id for SMS
+ // until you have a message in the conversation!
+ public static String getRawRecipientIdsForThread(final long threadId) {
+ if (threadId <= 0) {
+ return null;
+ }
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver cr = context.getContentResolver();
+ final Cursor thread = cr.query(
+ ALL_THREADS_URI,
+ RECIPIENTS_PROJECTION, "_id=?", new String[] { String.valueOf(threadId) }, null);
+ if (thread != null) {
+ try {
+ if (thread.moveToFirst()) {
+ // recipientIds will be a space-separated list of ids into the
+ // canonical addresses table.
+ return thread.getString(RECIPIENT_IDS);
+ }
+ } finally {
+ thread.close();
+ }
+ }
+ return null;
+ }
+
+ private static final Uri SINGLE_CANONICAL_ADDRESS_URI =
+ Uri.parse("content://mms-sms/canonical-address");
+
+ private static List<String> getAddresses(final Context context, final String spaceSepIds) {
+ final List<String> numbers = new ArrayList<String>();
+ final String[] ids = spaceSepIds.split(" ");
+ for (final String id : ids) {
+ long longId;
+
+ try {
+ longId = Long.parseLong(id);
+ if (longId < 0) {
+ LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id " + longId);
+ continue;
+ }
+ } catch (final NumberFormatException ex) {
+ LogUtil.e(TAG, "MmsUtils.getAddresses: invalid id. " + ex, ex);
+ // skip this id
+ continue;
+ }
+
+ // TODO: build a single query where we get all the addresses at once.
+ Cursor c = null;
+ try {
+ c = context.getContentResolver().query(
+ ContentUris.withAppendedId(SINGLE_CANONICAL_ADDRESS_URI, longId),
+ null, null, null, null);
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "MmsUtils.getAddresses: query failed for id " + longId, e);
+ }
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ final String number = c.getString(0);
+ if (!TextUtils.isEmpty(number)) {
+ numbers.add(number);
+ } else {
+ LogUtil.w(TAG, "Canonical MMS/SMS address is empty for id: " + longId);
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+ }
+ if (numbers.isEmpty()) {
+ LogUtil.w(TAG, "No MMS addresses found from ids string [" + spaceSepIds + "]");
+ }
+ return numbers;
+ }
+
+ // Get telephony SMS thread ID
+ public static long getOrCreateSmsThreadId(final Context context, final String dest) {
+ // use destinations to determine threadId
+ final Set<String> recipients = new HashSet<String>();
+ recipients.add(dest);
+ try {
+ return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients);
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e);
+ return -1;
+ }
+ }
+
+ // Get telephony SMS thread ID
+ public static long getOrCreateThreadId(final Context context, final List<String> dests) {
+ if (dests == null || dests.size() == 0) {
+ return -1;
+ }
+ // use destinations to determine threadId
+ final Set<String> recipients = new HashSet<String>(dests);
+ try {
+ return MmsSmsUtils.Threads.getOrCreateThreadId(context, recipients);
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MmsUtils: getting thread id failed: " + e);
+ return -1;
+ }
+ }
+
+ /**
+ * Add an SMS to the given URI with thread_id specified.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param subId subId for the receiving sim
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param threadId the thread_id of the message
+ * @return the URI for the new message
+ */
+ private static Uri addMessageToUri(final ContentResolver resolver,
+ final Uri uri, final int subId, final String address, final String body,
+ final String subject, final Long date, final boolean read, final boolean seen,
+ final int status, final int type, final long threadId) {
+ final ContentValues values = new ContentValues(7);
+
+ values.put(Telephony.Sms.ADDRESS, address);
+ if (date != null) {
+ values.put(Telephony.Sms.DATE, date);
+ }
+ values.put(Telephony.Sms.READ, read ? 1 : 0);
+ values.put(Telephony.Sms.SEEN, seen ? 1 : 0);
+ values.put(Telephony.Sms.SUBJECT, subject);
+ values.put(Telephony.Sms.BODY, body);
+ if (OsUtil.isAtLeastL_MR1()) {
+ values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
+ }
+ if (status != Telephony.Sms.STATUS_NONE) {
+ values.put(Telephony.Sms.STATUS, status);
+ }
+ if (type != Telephony.Sms.MESSAGE_TYPE_ALL) {
+ values.put(Telephony.Sms.TYPE, type);
+ }
+ if (threadId != -1L) {
+ values.put(Telephony.Sms.THREAD_ID, threadId);
+ }
+ return resolver.insert(uri, values);
+ }
+
+ // Insert an SMS message to telephony
+ public static Uri insertSmsMessage(final Context context, final Uri uri, final int subId,
+ final String dest, final String text, final long timestamp, final int status,
+ final int type, final long threadId) {
+ Uri response = null;
+ try {
+ response = addMessageToUri(context.getContentResolver(), uri, subId, dest,
+ text, null /* subject */, timestamp, true /* read */,
+ true /* seen */, status, type, threadId);
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "Mmsutils: Inserted SMS message into telephony (type = " + type + ")"
+ + ", uri: " + response);
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e);
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MmsUtils: persist sms message failure " + e, e);
+ }
+ return response;
+ }
+
+ // Update SMS message type in telephony; returns true if it succeeded.
+ public static boolean updateSmsMessageSendingStatus(final Context context, final Uri uri,
+ final int type, final long date) {
+ try {
+ final ContentResolver resolver = context.getContentResolver();
+ final ContentValues values = new ContentValues(2);
+
+ values.put(Telephony.Sms.TYPE, type);
+ values.put(Telephony.Sms.DATE, date);
+ final int cnt = resolver.update(uri, values, null, null);
+ if (cnt == 1) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "Mmsutils: Updated sending SMS " + uri + "; type = " + type
+ + ", date = " + date + " (millis since epoch)");
+ }
+ return true;
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e);
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MmsUtils: update sms message failure " + e, e);
+ }
+ return false;
+ }
+
+ // Persist a sent MMS message in telephony
+ private static Uri insertSendReq(final Context context, final GenericPdu pdu, final int subId,
+ final String subPhoneNumber) {
+ final PduPersister persister = PduPersister.getPduPersister(context);
+ Uri uri = null;
+ try {
+ // Persist the PDU
+ uri = persister.persist(
+ pdu,
+ Mms.Sent.CONTENT_URI,
+ subId,
+ subPhoneNumber,
+ null/*preOpenedFiles*/);
+ // Update mms table to reflect sent messages are always seen and read
+ final ContentValues values = new ContentValues(1);
+ values.put(Mms.READ, 1);
+ values.put(Mms.SEEN, 1);
+ SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null);
+ } catch (final MmsException e) {
+ LogUtil.e(TAG, "MmsUtils: persist mms sent message failure " + e, e);
+ }
+ return uri;
+ }
+
+ // Persist a received MMS message in telephony
+ public static Uri insertReceivedMmsMessage(final Context context,
+ final RetrieveConf retrieveConf, final int subId, final String subPhoneNumber,
+ final long receivedTimestampInSeconds, final String contentLocation) {
+ final PduPersister persister = PduPersister.getPduPersister(context);
+ Uri uri = null;
+ try {
+ uri = persister.persist(
+ retrieveConf,
+ Mms.Inbox.CONTENT_URI,
+ subId,
+ subPhoneNumber,
+ null/*preOpenedFiles*/);
+
+ final ContentValues values = new ContentValues(2);
+ // Update mms table with local time instead of PDU time
+ values.put(Mms.DATE, receivedTimestampInSeconds);
+ // Also update the content location field from NotificationInd so that
+ // wap push dedup would work even after the wap push is deleted
+ values.put(Mms.CONTENT_LOCATION, contentLocation);
+ SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null);
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "MmsUtils: Inserted MMS message into telephony, uri: " + uri);
+ }
+ } catch (final MmsException e) {
+ LogUtil.e(TAG, "MmsUtils: persist mms received message failure " + e, e);
+ // Just returns empty uri to RetrieveMmsRequest, which triggers a permanent failure
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "MmsUtils: update mms received message failure " + e, e);
+ // Time update failure is ignored.
+ }
+ return uri;
+ }
+
+ // Update MMS message type in telephony; returns true if it succeeded.
+ public static boolean updateMmsMessageSendingStatus(final Context context, final Uri uri,
+ final int box, final long timestampInMillis) {
+ try {
+ final ContentResolver resolver = context.getContentResolver();
+ final ContentValues values = new ContentValues();
+
+ final long timestampInSeconds = timestampInMillis / 1000L;
+ values.put(Telephony.Mms.MESSAGE_BOX, box);
+ values.put(Telephony.Mms.DATE, timestampInSeconds);
+ final int cnt = resolver.update(uri, values, null, null);
+ if (cnt == 1) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "Mmsutils: Updated sending MMS " + uri + "; box = " + box
+ + ", date = " + timestampInSeconds + " (secs since epoch)");
+ }
+ return true;
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e);
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MmsUtils: update mms message failure " + e, e);
+ }
+ return false;
+ }
+
+ /**
+ * Parse values from a received sms message
+ *
+ * @param context
+ * @param msgs The received sms message content
+ * @param error The received sms error
+ * @return Parsed values from the message
+ */
+ public static ContentValues parseReceivedSmsMessage(
+ final Context context, final SmsMessage[] msgs, final int error) {
+ final SmsMessage sms = msgs[0];
+ final ContentValues values = new ContentValues();
+
+ values.put(Sms.ADDRESS, sms.getDisplayOriginatingAddress());
+ values.put(Sms.BODY, buildMessageBodyFromPdus(msgs));
+ if (MmsUtils.hasSmsDateSentColumn()) {
+ // TODO:: The boxing here seems unnecessary.
+ values.put(Sms.DATE_SENT, Long.valueOf(sms.getTimestampMillis()));
+ }
+ values.put(Sms.PROTOCOL, sms.getProtocolIdentifier());
+ if (sms.getPseudoSubject().length() > 0) {
+ values.put(Sms.SUBJECT, sms.getPseudoSubject());
+ }
+ values.put(Sms.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0);
+ values.put(Sms.SERVICE_CENTER, sms.getServiceCenterAddress());
+ // Error code
+ values.put(Sms.ERROR_CODE, error);
+
+ return values;
+ }
+
+ // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
+ private static String replaceFormFeeds(final String s) {
+ return s == null ? "" : s.replace('\f', '\n');
+ }
+
+ // Parse the message body from message PDUs
+ private static String buildMessageBodyFromPdus(final SmsMessage[] msgs) {
+ if (msgs.length == 1) {
+ // There is only one part, so grab the body directly.
+ return replaceFormFeeds(msgs[0].getDisplayMessageBody());
+ } else {
+ // Build up the body from the parts.
+ final StringBuilder body = new StringBuilder();
+ for (final SmsMessage msg : msgs) {
+ try {
+ // getDisplayMessageBody() can NPE if mWrappedMessage inside is null.
+ body.append(msg.getDisplayMessageBody());
+ } catch (final NullPointerException e) {
+ // Nothing to do
+ }
+ }
+ return replaceFormFeeds(body.toString());
+ }
+ }
+
+ // Parse the message date
+ public static Long getMessageDate(final SmsMessage sms, long now) {
+ // Use now for the timestamp to avoid confusion with clock
+ // drift between the handset and the SMSC.
+ // Check to make sure the system is giving us a non-bogus time.
+ final Calendar buildDate = new GregorianCalendar(2011, 8, 18); // 18 Sep 2011
+ final Calendar nowDate = new GregorianCalendar();
+ nowDate.setTimeInMillis(now);
+ if (nowDate.before(buildDate)) {
+ // It looks like our system clock isn't set yet because the current time right now
+ // is before an arbitrary time we made this build. Instead of inserting a bogus
+ // receive time in this case, use the timestamp of when the message was sent.
+ now = sms.getTimestampMillis();
+ }
+ return now;
+ }
+
+ /**
+ * cleanseMmsSubject will take a subject that's says, "<Subject: no subject>", and return
+ * a null string. Otherwise it will return the original subject string.
+ * @param resources So the function can grab string resources
+ * @param subject the raw subject
+ * @return
+ */
+ public static String cleanseMmsSubject(final Resources resources, final String subject) {
+ if (TextUtils.isEmpty(subject)) {
+ return null;
+ }
+ if (sNoSubjectStrings == null) {
+ sNoSubjectStrings =
+ resources.getStringArray(R.array.empty_subject_strings);
+ }
+ for (final String noSubjectString : sNoSubjectStrings) {
+ if (subject.equalsIgnoreCase(noSubjectString)) {
+ return null;
+ }
+ }
+ return subject;
+ }
+
+ // return a semicolon separated list of phone numbers from a smsto: uri.
+ public static String getSmsRecipients(final Uri uri) {
+ String recipients = uri.getSchemeSpecificPart();
+ final int pos = recipients.indexOf('?');
+ if (pos != -1) {
+ recipients = recipients.substring(0, pos);
+ }
+ recipients = replaceUnicodeDigits(recipients).replace(',', ';');
+ return recipients;
+ }
+
+ // This function was lifted from Telephony.PhoneNumberUtils because it was @hide
+ /**
+ * Replace arabic/unicode digits with decimal digits.
+ * @param number
+ * the number to be normalized.
+ * @return the replaced number.
+ */
+ private static String replaceUnicodeDigits(final String number) {
+ final StringBuilder normalizedDigits = new StringBuilder(number.length());
+ for (final char c : number.toCharArray()) {
+ final int digit = Character.digit(c, 10);
+ if (digit != -1) {
+ normalizedDigits.append(digit);
+ } else {
+ normalizedDigits.append(c);
+ }
+ }
+ return normalizedDigits.toString();
+ }
+
+ /**
+ * @return Whether the data roaming is enabled
+ */
+ private static boolean isDataRoamingEnabled() {
+ boolean dataRoamingEnabled = false;
+ final ContentResolver cr = Factory.get().getApplicationContext().getContentResolver();
+ if (OsUtil.isAtLeastJB_MR1()) {
+ dataRoamingEnabled = (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0);
+ } else {
+ dataRoamingEnabled = (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0);
+ }
+ return dataRoamingEnabled;
+ }
+
+ /**
+ * @return Whether to auto retrieve MMS
+ */
+ public static boolean allowMmsAutoRetrieve(final int subId) {
+ final Context context = Factory.get().getApplicationContext();
+ final Resources resources = context.getResources();
+ final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
+ final boolean autoRetrieve = prefs.getBoolean(
+ resources.getString(R.string.auto_retrieve_mms_pref_key),
+ resources.getBoolean(R.bool.auto_retrieve_mms_pref_default));
+ if (autoRetrieve) {
+ final boolean autoRetrieveInRoaming = prefs.getBoolean(
+ resources.getString(R.string.auto_retrieve_mms_when_roaming_pref_key),
+ resources.getBoolean(R.bool.auto_retrieve_mms_when_roaming_pref_default));
+ final PhoneUtils phoneUtils = PhoneUtils.get(subId);
+ if ((autoRetrieveInRoaming && phoneUtils.isDataRoamingEnabled())
+ || !phoneUtils.isRoaming()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Parse the message row id from a message Uri.
+ *
+ * @param messageUri The input Uri
+ * @return The message row id if valid, otherwise -1
+ */
+ public static long parseRowIdFromMessageUri(final Uri messageUri) {
+ try {
+ if (messageUri != null) {
+ return ContentUris.parseId(messageUri);
+ }
+ } catch (final UnsupportedOperationException e) {
+ // Nothing to do
+ } catch (final NumberFormatException e) {
+ // Nothing to do
+ }
+ return -1;
+ }
+
+ public static SmsMessage getSmsMessageFromDeliveryReport(final Intent intent) {
+ final byte[] pdu = intent.getByteArrayExtra("pdu");
+ return SmsMessage.createFromPdu(pdu);
+ }
+
+ /**
+ * Update the status and date_sent column of sms message in telephony provider
+ *
+ * @param smsMessageUri
+ * @param status
+ * @param timeSentInMillis
+ */
+ public static void updateSmsStatusAndDateSent(final Uri smsMessageUri, final int status,
+ final long timeSentInMillis) {
+ if (smsMessageUri == null) {
+ return;
+ }
+ final ContentValues values = new ContentValues();
+ values.put(Sms.STATUS, status);
+ if (MmsUtils.hasSmsDateSentColumn()) {
+ values.put(Sms.DATE_SENT, timeSentInMillis);
+ }
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ resolver.update(smsMessageUri, values, null/*where*/, null/*selectionArgs*/);
+ }
+
+ /**
+ * Get the SQL selection statement for matching messages with media.
+ *
+ * Example for MMS part table:
+ * "((ct LIKE 'image/%')
+ * OR (ct LIKE 'video/%')
+ * OR (ct LIKE 'audio/%')
+ * OR (ct='application/ogg'))
+ *
+ * @param contentTypeColumn The content-type column name
+ * @return The SQL selection statement for matching media types: image, video, audio
+ */
+ public static String getMediaTypeSelectionSql(final String contentTypeColumn) {
+ return String.format(
+ Locale.US,
+ "((%s LIKE '%s') OR (%s LIKE '%s') OR (%s LIKE '%s') OR (%s='%s'))",
+ contentTypeColumn,
+ "image/%",
+ contentTypeColumn,
+ "video/%",
+ contentTypeColumn,
+ "audio/%",
+ contentTypeColumn,
+ ContentType.AUDIO_OGG);
+ }
+
+ // Max number of operands per SQL query for deleting SMS messages
+ public static final int MAX_IDS_PER_QUERY = 128;
+
+ /**
+ * Delete MMS messages with media parts.
+ *
+ * Because the telephony provider constraints, we can't use JOIN and delete messages in one
+ * shot. We have to do a query first and then batch delete the messages based on IDs.
+ *
+ * @return The count of messages deleted.
+ */
+ public static int deleteMediaMessages() {
+ // Do a query first
+ //
+ // The WHERE clause has two parts:
+ // The first part is to select the exact same types of MMS messages as when we import them
+ // (so that we don't delete messages that are not in local database)
+ // The second part is to select MMS with media parts, including image, video and audio
+ final String selection = String.format(
+ Locale.US,
+ "%s AND (%s IN (SELECT %s FROM part WHERE %s))",
+ getMmsTypeSelectionSql(),
+ Mms._ID,
+ Mms.Part.MSG_ID,
+ getMediaTypeSelectionSql(Mms.Part.CONTENT_TYPE));
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ final Cursor cursor = resolver.query(Mms.CONTENT_URI,
+ new String[]{ Mms._ID },
+ selection,
+ null/*selectionArgs*/,
+ null/*sortOrder*/);
+ int deleted = 0;
+ if (cursor != null) {
+ final long[] messageIds = new long[cursor.getCount()];
+ try {
+ int i = 0;
+ while (cursor.moveToNext()) {
+ messageIds[i++] = cursor.getLong(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ final int totalIds = messageIds.length;
+ if (totalIds > 0) {
+ // Batch delete the messages using IDs
+ // We don't want to send all IDs at once since there is a limit on SQL statement
+ for (int start = 0; start < totalIds; start += MAX_IDS_PER_QUERY) {
+ final int end = Math.min(start + MAX_IDS_PER_QUERY, totalIds); // excluding
+ final int count = end - start;
+ final String batchSelection = String.format(
+ Locale.US,
+ "%s IN %s",
+ Mms._ID,
+ getSqlInOperand(count));
+ final String[] batchSelectionArgs =
+ getSqlInOperandArgs(messageIds, start, count);
+ final int deletedForBatch = resolver.delete(
+ Mms.CONTENT_URI,
+ batchSelection,
+ batchSelectionArgs);
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "deleteMediaMessages: deleting IDs = "
+ + Joiner.on(',').skipNulls().join(batchSelectionArgs)
+ + ", deleted = " + deletedForBatch);
+ }
+ deleted += deletedForBatch;
+ }
+ }
+ }
+ return deleted;
+ }
+
+ /**
+ * Get the (?,?,...) thing for the SQL IN operator by a count
+ *
+ * @param count
+ * @return
+ */
+ public static String getSqlInOperand(final int count) {
+ if (count <= 0) {
+ return null;
+ }
+ final StringBuilder sb = new StringBuilder();
+ sb.append("(?");
+ for (int i = 0; i < count - 1; i++) {
+ sb.append(",?");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ /**
+ * Get the args for SQL IN operator from a long ID array
+ *
+ * @param ids The original long id array
+ * @param start Start of the ids to fill the args
+ * @param count Number of ids to pack
+ * @return The long array with the id args
+ */
+ private static String[] getSqlInOperandArgs(
+ final long[] ids, final int start, final int count) {
+ if (count <= 0) {
+ return null;
+ }
+ final String[] args = new String[count];
+ for (int i = 0; i < count; i++) {
+ args[i] = Long.toString(ids[start + i]);
+ }
+ return args;
+ }
+
+ /**
+ * Delete SMS and MMS messages that are earlier than a specific timestamp
+ *
+ * @param cutOffTimestampInMillis The cut-off timestamp
+ * @return Total number of messages deleted.
+ */
+ public static int deleteMessagesOlderThan(final long cutOffTimestampInMillis) {
+ int deleted = 0;
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ // Delete old SMS
+ final String smsSelection = String.format(
+ Locale.US,
+ "%s AND (%s<=%d)",
+ getSmsTypeSelectionSql(),
+ Sms.DATE,
+ cutOffTimestampInMillis);
+ deleted += resolver.delete(Sms.CONTENT_URI, smsSelection, null/*selectionArgs*/);
+ // Delete old MMS
+ final String mmsSelection = String.format(
+ Locale.US,
+ "%s AND (%s<=%d)",
+ getMmsTypeSelectionSql(),
+ Mms.DATE,
+ cutOffTimestampInMillis / 1000L);
+ deleted += resolver.delete(Mms.CONTENT_URI, mmsSelection, null/*selectionArgs*/);
+ return deleted;
+ }
+
+ /**
+ * Update the read status of SMS/MMS messages by thread and timestamp
+ *
+ * @param threadId The thread of sms/mms to change
+ * @param timestampInMillis Change the status before this timestamp
+ */
+ public static void updateSmsReadStatus(final long threadId, final long timestampInMillis) {
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ final ContentValues values = new ContentValues();
+ values.put("read", 1);
+ values.put("seen", 1); /* If you read it you saw it */
+ final String smsSelection = String.format(
+ Locale.US,
+ "%s=%d AND %s<=%d AND %s=0",
+ Sms.THREAD_ID,
+ threadId,
+ Sms.DATE,
+ timestampInMillis,
+ Sms.READ);
+ resolver.update(
+ Sms.CONTENT_URI,
+ values,
+ smsSelection,
+ null/*selectionArgs*/);
+ final String mmsSelection = String.format(
+ Locale.US,
+ "%s=%d AND %s<=%d AND %s=0",
+ Mms.THREAD_ID,
+ threadId,
+ Mms.DATE,
+ timestampInMillis / 1000L,
+ Mms.READ);
+ resolver.update(
+ Mms.CONTENT_URI,
+ values,
+ mmsSelection,
+ null/*selectionArgs*/);
+ }
+
+ /**
+ * Update the read status of a single MMS message by its URI
+ *
+ * @param mmsUri
+ * @param read
+ */
+ public static void updateReadStatusForMmsMessage(final Uri mmsUri, final boolean read) {
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ final ContentValues values = new ContentValues();
+ values.put(Mms.READ, read ? 1 : 0);
+ resolver.update(mmsUri, values, null/*where*/, null/*selectionArgs*/);
+ }
+
+ public static class AttachmentInfo {
+ public String mUrl;
+ public String mContentType;
+ public int mWidth;
+ public int mHeight;
+ }
+
+ /**
+ * Convert byte array to Java String using a charset name
+ *
+ * @param bytes
+ * @param charsetName
+ * @return
+ */
+ public static String bytesToString(final byte[] bytes, final String charsetName) {
+ if (bytes == null) {
+ return null;
+ }
+ try {
+ return new String(bytes, charsetName);
+ } catch (final UnsupportedEncodingException e) {
+ LogUtil.e(TAG, "MmsUtils.bytesToString: " + e, e);
+ return new String(bytes);
+ }
+ }
+
+ /**
+ * Convert a Java String to byte array using a charset name
+ *
+ * @param string
+ * @param charsetName
+ * @return
+ */
+ public static byte[] stringToBytes(final String string, final String charsetName) {
+ if (string == null) {
+ return null;
+ }
+ try {
+ return string.getBytes(charsetName);
+ } catch (final UnsupportedEncodingException e) {
+ LogUtil.e(TAG, "MmsUtils.stringToBytes: " + e, e);
+ return string.getBytes();
+ }
+ }
+
+ private static final String[] TEST_DATE_SENT_PROJECTION = new String[] { Sms.DATE_SENT };
+ private static Boolean sHasSmsDateSentColumn = null;
+ /**
+ * Check if date_sent column exists on ICS and above devices. We need to do a test
+ * query to figure that out since on some ICS+ devices, somehow the date_sent column does
+ * not exist. http://b/17629135 tracks the associated compliance test.
+ *
+ * @return Whether "date_sent" column exists in sms table
+ */
+ public static boolean hasSmsDateSentColumn() {
+ if (sHasSmsDateSentColumn == null) {
+ Cursor cursor = null;
+ try {
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver resolver = context.getContentResolver();
+ cursor = SqliteWrapper.query(
+ context,
+ resolver,
+ Sms.CONTENT_URI,
+ TEST_DATE_SENT_PROJECTION,
+ null/*selection*/,
+ null/*selectionArgs*/,
+ Sms.DATE_SENT + " ASC LIMIT 1");
+ sHasSmsDateSentColumn = true;
+ } catch (final SQLiteException e) {
+ LogUtil.w(TAG, "date_sent in sms table does not exist", e);
+ sHasSmsDateSentColumn = false;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ return sHasSmsDateSentColumn;
+ }
+
+ private static final String[] TEST_CARRIERS_PROJECTION =
+ new String[] { Telephony.Carriers.MMSC };
+ private static Boolean sUseSystemApn = null;
+ /**
+ * Check if we can access the APN data in the Telephony provider. Access was restricted in
+ * JB MR1 (and some JB MR2) devices. If we can't access the APN, we have to fall back and use
+ * a private table in our own app.
+ *
+ * @return Whether we can access the system APN table
+ */
+ public static boolean useSystemApnTable() {
+ if (sUseSystemApn == null) {
+ Cursor cursor = null;
+ try {
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver resolver = context.getContentResolver();
+ cursor = SqliteWrapper.query(
+ context,
+ resolver,
+ Telephony.Carriers.CONTENT_URI,
+ TEST_CARRIERS_PROJECTION,
+ null/*selection*/,
+ null/*selectionArgs*/,
+ null);
+ sUseSystemApn = true;
+ } catch (final SecurityException e) {
+ LogUtil.w(TAG, "Can't access system APN, using internal table", e);
+ sUseSystemApn = false;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ return sUseSystemApn;
+ }
+
+ // For the internal debugger only
+ public static void setUseSystemApnTable(final boolean turnOn) {
+ if (!turnOn) {
+ // We're not turning on to the system table. Instead, we're using our internal table.
+ final int osVersion = OsUtil.getApiVersion();
+ if (osVersion != android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // We're turning on local APNs on a device where we wouldn't normally have the
+ // local APN table. Build it here.
+
+ final SQLiteDatabase database = ApnDatabase.getApnDatabase().getWritableDatabase();
+
+ // Do we already have the table?
+ Cursor cursor = null;
+ try {
+ cursor = database.query(ApnDatabase.APN_TABLE,
+ ApnDatabase.APN_PROJECTION,
+ null, null, null, null, null, null);
+ } catch (final Exception e) {
+ // Apparently there's no table, create it now.
+ ApnDatabase.forceBuildAndLoadApnTables();
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ }
+ sUseSystemApn = turnOn;
+ }
+
+ /**
+ * Checks if we should dump sms, based on both the setting and the global debug
+ * flag
+ *
+ * @return if dump sms is enabled
+ */
+ public static boolean isDumpSmsEnabled() {
+ if (!DebugUtils.isDebugEnabled()) {
+ return false;
+ }
+ return getDumpSmsOrMmsPref(R.string.dump_sms_pref_key, R.bool.dump_sms_pref_default);
+ }
+
+ /**
+ * Checks if we should dump mms, based on both the setting and the global debug
+ * flag
+ *
+ * @return if dump mms is enabled
+ */
+ public static boolean isDumpMmsEnabled() {
+ if (!DebugUtils.isDebugEnabled()) {
+ return false;
+ }
+ return getDumpSmsOrMmsPref(R.string.dump_mms_pref_key, R.bool.dump_mms_pref_default);
+ }
+
+ /**
+ * Load the value of dump sms or mms setting preference
+ */
+ private static boolean getDumpSmsOrMmsPref(final int prefKeyRes, final int defaultKeyRes) {
+ final Context context = Factory.get().getApplicationContext();
+ final Resources resources = context.getResources();
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final String key = resources.getString(prefKeyRes);
+ final boolean defaultValue = resources.getBoolean(defaultKeyRes);
+ return prefs.getBoolean(key, defaultValue);
+ }
+
+ public static final Uri MMS_PART_CONTENT_URI = Uri.parse("content://mms/part");
+
+ /**
+ * Load MMS from telephony
+ *
+ * @param mmsUri The MMS pdu Uri
+ * @return A memory copy of the MMS pdu including parts (but not addresses)
+ */
+ public static DatabaseMessages.MmsMessage loadMms(final Uri mmsUri) {
+ final Context context = Factory.get().getApplicationContext();
+ final ContentResolver resolver = context.getContentResolver();
+ DatabaseMessages.MmsMessage mms = null;
+ Cursor cursor = null;
+ // Load pdu first
+ try {
+ cursor = SqliteWrapper.query(context, resolver,
+ mmsUri,
+ DatabaseMessages.MmsMessage.getProjection(),
+ null/*selection*/, null/*selectionArgs*/, null/*sortOrder*/);
+ if (cursor != null && cursor.moveToFirst()) {
+ mms = DatabaseMessages.MmsMessage.get(cursor);
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "MmsLoader: query pdu failure: " + e, e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ if (mms == null) {
+ return null;
+ }
+ // Load parts except SMIL
+ // TODO: we may need to load SMIL part in the future.
+ final long rowId = MmsUtils.parseRowIdFromMessageUri(mmsUri);
+ final String selection = String.format(
+ Locale.US,
+ "%s != '%s' AND %s = ?",
+ Mms.Part.CONTENT_TYPE,
+ ContentType.APP_SMIL,
+ Mms.Part.MSG_ID);
+ cursor = null;
+ try {
+ cursor = SqliteWrapper.query(context, resolver,
+ MMS_PART_CONTENT_URI,
+ DatabaseMessages.MmsPart.PROJECTION,
+ selection,
+ new String[] { Long.toString(rowId) },
+ null/*sortOrder*/);
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ mms.addPart(DatabaseMessages.MmsPart.get(cursor, true/*loadMedia*/));
+ }
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "MmsLoader: query parts failure: " + e, e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return mms;
+ }
+
+ /**
+ * Get the sender of an MMS message
+ *
+ * @param recipients The recipient list of the message
+ * @param mmsUri The pdu uri of the MMS
+ * @return The sender phone number of the MMS
+ */
+ public static String getMmsSender(final List<String> recipients, final String mmsUri) {
+ final Context context = Factory.get().getApplicationContext();
+ // We try to avoid the database query.
+ // If this is a 1v1 conv., then the other party is the sender
+ if (recipients != null && recipients.size() == 1) {
+ return recipients.get(0);
+ }
+ // Otherwise, we have to query the MMS addr table for sender address
+ // This should only be done for a received group mms message
+ final Cursor cursor = SqliteWrapper.query(
+ context,
+ context.getContentResolver(),
+ Uri.withAppendedPath(Uri.parse(mmsUri), "addr"),
+ new String[] { Mms.Addr.ADDRESS, Mms.Addr.CHARSET },
+ Mms.Addr.TYPE + "=" + PduHeaders.FROM,
+ null/*selectionArgs*/,
+ null/*sortOrder*/);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return DatabaseMessages.MmsAddr.get(cursor);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ public static int bugleStatusForMms(final boolean isOutgoing, final boolean isNotification,
+ final int messageBox) {
+ int bugleStatus = MessageData.BUGLE_STATUS_UNKNOWN;
+ // For a message we sync either
+ if (isOutgoing) {
+ if (messageBox == Mms.MESSAGE_BOX_OUTBOX || messageBox == Mms.MESSAGE_BOX_FAILED) {
+ // Not sent counts as failed and available for manual resend
+ bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_FAILED;
+ } else {
+ // Otherwise outgoing message is complete
+ bugleStatus = MessageData.BUGLE_STATUS_OUTGOING_COMPLETE;
+ }
+ } else if (isNotification) {
+ // Incoming MMS notifications we sync count as failed and available for manual download
+ bugleStatus = MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD;
+ } else {
+ // Other incoming MMS messages are complete
+ bugleStatus = MessageData.BUGLE_STATUS_INCOMING_COMPLETE;
+ }
+ return bugleStatus;
+ }
+
+ public static MessageData createMmsMessage(final DatabaseMessages.MmsMessage mms,
+ final String conversationId, final String participantId, final String selfId,
+ final int bugleStatus) {
+ Assert.notNull(mms);
+ final boolean isNotification = (mms.mMmsMessageType ==
+ PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+ final int rawMmsStatus = (bugleStatus < MessageData.BUGLE_STATUS_FIRST_INCOMING
+ ? mms.mRetrieveStatus : mms.mResponseStatus);
+
+ final MessageData message = MessageData.createMmsMessage(mms.getUri(),
+ participantId, selfId, conversationId, isNotification, bugleStatus,
+ mms.mContentLocation, mms.mTransactionId, mms.mPriority, mms.mSubject,
+ mms.mSeen, mms.mRead, mms.getSize(), rawMmsStatus,
+ mms.mExpiryInMillis, mms.mSentTimestampInMillis, mms.mTimestampInMillis);
+
+ for (final DatabaseMessages.MmsPart part : mms.mParts) {
+ final MessagePartData messagePart = MmsUtils.createMmsMessagePart(part);
+ // Import media and text parts (skip SMIL and others)
+ if (messagePart != null) {
+ message.addPart(messagePart);
+ }
+ }
+
+ if (!message.getParts().iterator().hasNext()) {
+ message.addPart(MessagePartData.createEmptyMessagePart());
+ }
+
+ return message;
+ }
+
+ public static MessagePartData createMmsMessagePart(final DatabaseMessages.MmsPart part) {
+ MessagePartData messagePart = null;
+ if (part.isText()) {
+ final int mmsTextLengthLimit =
+ BugleGservices.get().getInt(BugleGservicesKeys.MMS_TEXT_LIMIT,
+ BugleGservicesKeys.MMS_TEXT_LIMIT_DEFAULT);
+ String text = part.mText;
+ if (text != null && text.length() > mmsTextLengthLimit) {
+ // Limit the text to a reasonable value. We ran into a situation where a vcard
+ // with a photo was sent as plain text. The massive amount of text caused the
+ // app to hang, ANR, and eventually crash in native text code.
+ text = text.substring(0, mmsTextLengthLimit);
+ }
+ messagePart = MessagePartData.createTextMessagePart(text);
+ } else if (part.isMedia()) {
+ messagePart = MessagePartData.createMediaMessagePart(part.mContentType,
+ part.getDataUri(), MessagePartData.UNSPECIFIED_SIZE,
+ MessagePartData.UNSPECIFIED_SIZE);
+ }
+ return messagePart;
+ }
+
+ public static class StatusPlusUri {
+ // The request status to be as the result of the operation
+ // e.g. MMS_REQUEST_MANUAL_RETRY
+ public final int status;
+ // The raw telephony status
+ public final int rawStatus;
+ // The raw telephony URI
+ public final Uri uri;
+ // The operation result code from system api invocation (sent by system)
+ // or mapped from internal exception (sent by app)
+ public final int resultCode;
+
+ public StatusPlusUri(final int status, final int rawStatus, final Uri uri) {
+ this.status = status;
+ this.rawStatus = rawStatus;
+ this.uri = uri;
+ resultCode = MessageData.UNKNOWN_RESULT_CODE;
+ }
+
+ public StatusPlusUri(final int status, final int rawStatus, final Uri uri,
+ final int resultCode) {
+ this.status = status;
+ this.rawStatus = rawStatus;
+ this.uri = uri;
+ this.resultCode = resultCode;
+ }
+ }
+
+ public static class SendReqResp {
+ public SendReq mSendReq;
+ public SendConf mSendConf;
+
+ public SendReqResp(final SendReq sendReq, final SendConf sendConf) {
+ mSendReq = sendReq;
+ mSendConf = sendConf;
+ }
+ }
+
+ /**
+ * Returned when sending/downloading MMS via platform APIs. In that case, we have to wait to
+ * receive the pending intent to determine status.
+ */
+ public static final StatusPlusUri STATUS_PENDING = new StatusPlusUri(-1, -1, null);
+
+ public static StatusPlusUri downloadMmsMessage(final Context context, final Uri notificationUri,
+ final int subId, final String subPhoneNumber, final String transactionId,
+ final String contentLocation, final boolean autoDownload,
+ final long receivedTimestampInSeconds, Bundle extras) {
+ if (TextUtils.isEmpty(contentLocation)) {
+ LogUtil.e(TAG, "MmsUtils: Download from empty content location URL");
+ return new StatusPlusUri(
+ MMS_REQUEST_NO_RETRY, MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, null);
+ }
+ if (!isMmsDataAvailable(subId)) {
+ LogUtil.e(TAG,
+ "MmsUtils: failed to download message, no data available");
+ return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY,
+ MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
+ null,
+ SmsManager.MMS_ERROR_NO_DATA_NETWORK);
+ }
+ int status = MMS_REQUEST_MANUAL_RETRY;
+ try {
+ RetrieveConf retrieveConf = null;
+ if (DebugUtils.isDebugEnabled() &&
+ MediaScratchFileProvider
+ .isMediaScratchSpaceUri(Uri.parse(contentLocation))) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "MmsUtils: Reading MMS from dump file: " + contentLocation);
+ }
+ final String fileName = Uri.parse(contentLocation).getPathSegments().get(1);
+ final byte[] data = DebugUtils.receiveFromDumpFile(fileName);
+ retrieveConf = receiveFromDumpFile(data);
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "MmsUtils: Downloading MMS via MMS lib API; notification "
+ + "message: " + notificationUri);
+ }
+ if (OsUtil.isAtLeastL_MR1()) {
+ if (subId < 0) {
+ LogUtil.e(TAG, "MmsUtils: Incoming MMS came from unknown SIM");
+ throw new MmsFailureException(MMS_REQUEST_NO_RETRY,
+ "Message from unknown SIM");
+ }
+ } else {
+ Assert.isTrue(subId == ParticipantData.DEFAULT_SELF_SUB_ID);
+ }
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putParcelable(DownloadMmsAction.EXTRA_NOTIFICATION_URI, notificationUri);
+ extras.putInt(DownloadMmsAction.EXTRA_SUB_ID, subId);
+ extras.putString(DownloadMmsAction.EXTRA_SUB_PHONE_NUMBER, subPhoneNumber);
+ extras.putString(DownloadMmsAction.EXTRA_TRANSACTION_ID, transactionId);
+ extras.putString(DownloadMmsAction.EXTRA_CONTENT_LOCATION, contentLocation);
+ extras.putBoolean(DownloadMmsAction.EXTRA_AUTO_DOWNLOAD, autoDownload);
+ extras.putLong(DownloadMmsAction.EXTRA_RECEIVED_TIMESTAMP,
+ receivedTimestampInSeconds);
+
+ MmsSender.downloadMms(context, subId, contentLocation, extras);
+ return STATUS_PENDING; // Download happens asynchronously; no status to return
+ }
+ return insertDownloadedMessageAndSendResponse(context, notificationUri, subId,
+ subPhoneNumber, transactionId, contentLocation, autoDownload,
+ receivedTimestampInSeconds, retrieveConf);
+
+ } catch (final MmsFailureException e) {
+ LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e);
+ status = e.retryHint;
+ } catch (final InvalidHeaderValueException e) {
+ LogUtil.e(TAG, "MmsUtils: failed to download message " + notificationUri, e);
+ }
+ return new StatusPlusUri(status, PDU_HEADER_VALUE_UNDEFINED, null);
+ }
+
+ public static StatusPlusUri insertDownloadedMessageAndSendResponse(final Context context,
+ final Uri notificationUri, final int subId, final String subPhoneNumber,
+ final String transactionId, final String contentLocation,
+ final boolean autoDownload, final long receivedTimestampInSeconds,
+ final RetrieveConf retrieveConf) {
+ final byte[] transactionIdBytes = stringToBytes(transactionId, "UTF-8");
+ Uri messageUri = null;
+ int status = MMS_REQUEST_MANUAL_RETRY;
+ int retrieveStatus = PDU_HEADER_VALUE_UNDEFINED;
+
+ retrieveStatus = retrieveConf.getRetrieveStatus();
+ if (retrieveStatus == PduHeaders.RETRIEVE_STATUS_OK) {
+ status = MMS_REQUEST_SUCCEEDED;
+ } else if (retrieveStatus >= PduHeaders.RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE &&
+ retrieveStatus < PduHeaders.RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE) {
+ status = MMS_REQUEST_AUTO_RETRY;
+ } else {
+ // else not meant to retry download
+ status = MMS_REQUEST_NO_RETRY;
+ LogUtil.e(TAG, "MmsUtils: failed to retrieve message; retrieveStatus: "
+ + retrieveStatus);
+ }
+ final ContentValues values = new ContentValues(1);
+ values.put(Mms.RETRIEVE_STATUS, retrieveConf.getRetrieveStatus());
+ SqliteWrapper.update(context, context.getContentResolver(),
+ notificationUri, values, null, null);
+
+ if (status == MMS_REQUEST_SUCCEEDED) {
+ // Send response of the notification
+ if (autoDownload) {
+ sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes,
+ contentLocation, PduHeaders.STATUS_RETRIEVED);
+ } else {
+ sendAcknowledgeForMmsDownload(context, subId, transactionIdBytes, contentLocation);
+ }
+
+ // Insert downloaded message into telephony
+ final Uri inboxUri = MmsUtils.insertReceivedMmsMessage(context, retrieveConf, subId,
+ subPhoneNumber, receivedTimestampInSeconds, contentLocation);
+ messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI, ContentUris.parseId(inboxUri));
+ } else if (status == MMS_REQUEST_AUTO_RETRY) {
+ // For a retry do nothing
+ } else if (status == MMS_REQUEST_MANUAL_RETRY && autoDownload) {
+ // Failure from autodownload - just treat like manual download
+ sendNotifyResponseForMmsDownload(context, subId, transactionIdBytes,
+ contentLocation, PduHeaders.STATUS_DEFERRED);
+ }
+ return new StatusPlusUri(status, retrieveStatus, messageUri);
+ }
+
+ /**
+ * Send response for MMS download - catches and ignores errors
+ */
+ public static void sendNotifyResponseForMmsDownload(final Context context, final int subId,
+ final byte[] transactionId, final String contentLocation, final int status) {
+ try {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "MmsUtils: Sending M-NotifyResp.ind for received MMS, status: "
+ + String.format("0x%X", status));
+ }
+ if (contentLocation == null) {
+ LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; contentLocation is null");
+ return;
+ }
+ if (transactionId == null) {
+ LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; transaction id is null");
+ return;
+ }
+ if (!isMmsDataAvailable(subId)) {
+ LogUtil.w(TAG, "MmsUtils: Can't send NotifyResp; no data available");
+ return;
+ }
+ MmsSender.sendNotifyResponseForMmsDownload(
+ context, subId, transactionId, contentLocation, status);
+ } catch (final MmsFailureException e) {
+ LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e);
+ } catch (final InvalidHeaderValueException e) {
+ LogUtil.e(TAG, "sendNotifyResponseForMmsDownload: failed to retrieve message " + e, e);
+ }
+ }
+
+ /**
+ * Send acknowledge for mms download - catched and ignores errors
+ */
+ public static void sendAcknowledgeForMmsDownload(final Context context, final int subId,
+ final byte[] transactionId, final String contentLocation) {
+ try {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "MmsUtils: Sending M-Acknowledge.ind for received MMS");
+ }
+ if (contentLocation == null) {
+ LogUtil.w(TAG, "MmsUtils: Can't send AckInd; contentLocation is null");
+ return;
+ }
+ if (transactionId == null) {
+ LogUtil.w(TAG, "MmsUtils: Can't send AckInd; transaction id is null");
+ return;
+ }
+ if (!isMmsDataAvailable(subId)) {
+ LogUtil.w(TAG, "MmsUtils: Can't send AckInd; no data available");
+ return;
+ }
+ MmsSender.sendAcknowledgeForMmsDownload(context, subId, transactionId, contentLocation);
+ } catch (final MmsFailureException e) {
+ LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e);
+ } catch (final InvalidHeaderValueException e) {
+ LogUtil.e(TAG, "sendAcknowledgeForMmsDownload: failed to retrieve message " + e, e);
+ }
+ }
+
+ /**
+ * Try parsing a PDU without knowing the carrier. This is useful for importing
+ * MMS or storing draft when carrier info is not available
+ *
+ * @param data The PDU data
+ * @return Parsed PDU, null if failed to parse
+ */
+ private static GenericPdu parsePduForAnyCarrier(final byte[] data) {
+ GenericPdu pdu = null;
+ try {
+ pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse();
+ } catch (final RuntimeException e) {
+ LogUtil.d(TAG, "parsePduForAnyCarrier: Failed to parse PDU with content disposition",
+ e);
+ }
+ if (pdu == null) {
+ try {
+ pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse();
+ } catch (final RuntimeException e) {
+ LogUtil.d(TAG,
+ "parsePduForAnyCarrier: Failed to parse PDU without content disposition",
+ e);
+ }
+ }
+ return pdu;
+ }
+
+ private static RetrieveConf receiveFromDumpFile(final byte[] data) throws MmsFailureException {
+ final GenericPdu pdu = parsePduForAnyCarrier(data);
+ if (pdu == null || !(pdu instanceof RetrieveConf)) {
+ LogUtil.e(TAG, "receiveFromDumpFile: Parsing retrieved PDU failure");
+ throw new MmsFailureException(MMS_REQUEST_MANUAL_RETRY, "Failed reading dump file");
+ }
+ return (RetrieveConf) pdu;
+ }
+
+ private static boolean isMmsDataAvailable(final int subId) {
+ if (OsUtil.isAtLeastL_MR1()) {
+ // L_MR1 above may support sending mms via wifi
+ return true;
+ }
+ final PhoneUtils phoneUtils = PhoneUtils.get(subId);
+ return !phoneUtils.isAirplaneModeOn() && phoneUtils.isMobileDataEnabled();
+ }
+
+ private static boolean isSmsDataAvailable(final int subId) {
+ if (OsUtil.isAtLeastL_MR1()) {
+ // L_MR1 above may support sending sms via wifi
+ return true;
+ }
+ final PhoneUtils phoneUtils = PhoneUtils.get(subId);
+ return !phoneUtils.isAirplaneModeOn();
+ }
+
+ public static boolean isMobileDataEnabled(final int subId) {
+ final PhoneUtils phoneUtils = PhoneUtils.get(subId);
+ return phoneUtils.isMobileDataEnabled();
+ }
+
+ public static boolean isAirplaneModeOn(final int subId) {
+ final PhoneUtils phoneUtils = PhoneUtils.get(subId);
+ return phoneUtils.isAirplaneModeOn();
+ }
+
+ public static StatusPlusUri sendMmsMessage(final Context context, final int subId,
+ final Uri messageUri, final Bundle extras) {
+ int status = MMS_REQUEST_MANUAL_RETRY;
+ int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
+ if (!isMmsDataAvailable(subId)) {
+ LogUtil.w(TAG, "MmsUtils: failed to send message, no data available");
+ return new StatusPlusUri(MMS_REQUEST_MANUAL_RETRY,
+ MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
+ messageUri,
+ SmsManager.MMS_ERROR_NO_DATA_NETWORK);
+ }
+ final PduPersister persister = PduPersister.getPduPersister(context);
+ try {
+ final SendReq sendReq = (SendReq) persister.load(messageUri);
+ if (sendReq == null) {
+ LogUtil.w(TAG, "MmsUtils: Sending MMS was deleted; uri = " + messageUri);
+ return new StatusPlusUri(MMS_REQUEST_NO_RETRY,
+ MessageData.RAW_TELEPHONY_STATUS_UNDEFINED, messageUri);
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, String.format("MmsUtils: Sending MMS, message uri: %s", messageUri));
+ }
+ extras.putInt(SendMessageAction.KEY_SUB_ID, subId);
+ MmsSender.sendMms(context, subId, messageUri, sendReq, extras);
+ return STATUS_PENDING;
+ } catch (final MmsFailureException e) {
+ status = e.retryHint;
+ rawStatus = e.rawStatus;
+ LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
+ } catch (final InvalidHeaderValueException e) {
+ LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MmsUtils: invalid message to send " + e, e);
+ } catch (final MmsException e) {
+ LogUtil.e(TAG, "MmsUtils: failed to send message " + e, e);
+ }
+ // If we get here, some exception occurred
+ return new StatusPlusUri(status, rawStatus, messageUri);
+ }
+
+ public static StatusPlusUri updateSentMmsMessageStatus(final Context context,
+ final Uri messageUri, final SendConf sendConf) {
+ int status = MMS_REQUEST_MANUAL_RETRY;
+ final int respStatus = sendConf.getResponseStatus();
+
+ final ContentValues values = new ContentValues(2);
+ values.put(Mms.RESPONSE_STATUS, respStatus);
+ final byte[] messageId = sendConf.getMessageId();
+ if (messageId != null && messageId.length > 0) {
+ values.put(Mms.MESSAGE_ID, PduPersister.toIsoString(messageId));
+ }
+ SqliteWrapper.update(context, context.getContentResolver(),
+ messageUri, values, null, null);
+ if (respStatus == PduHeaders.RESPONSE_STATUS_OK) {
+ status = MMS_REQUEST_SUCCEEDED;
+ } else if (respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE ||
+ respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM ||
+ respStatus == PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) {
+ status = MMS_REQUEST_AUTO_RETRY;
+ } else {
+ // else permanent failure
+ LogUtil.e(TAG, "MmsUtils: failed to send message; respStatus = "
+ + String.format("0x%X", respStatus));
+ }
+ return new StatusPlusUri(status, respStatus, messageUri);
+ }
+
+ public static void clearMmsStatus(final Context context, final Uri uri) {
+ // Messaging application can leave invalid values in STATUS field of M-Notification.ind
+ // messages. Take this opportunity to clear it.
+ // Downloading status just kept in local db and not reflected into telephony.
+ final ContentValues values = new ContentValues(1);
+ values.putNull(Mms.STATUS);
+ SqliteWrapper.update(context, context.getContentResolver(),
+ uri, values, null, null);
+ }
+
+ // Selection for new dedup algorithm:
+ // ((m_type<>130) OR (exp>NOW)) AND (date>NOW-7d) AND (date<NOW+7d) AND (ct_l=xxxxxx)
+ // i.e. If it is NotificationInd and not expired or not NotificationInd
+ // AND message is received with +/- 7 days from now
+ // AND content location is the input URL
+ private static final String DUP_NOTIFICATION_QUERY_SELECTION =
+ "((" + Mms.MESSAGE_TYPE + "<>?) OR (" + Mms.EXPIRY + ">?)) AND ("
+ + Mms.DATE + ">?) AND (" + Mms.DATE + "<?) AND (" + Mms.CONTENT_LOCATION +
+ "=?)";
+ // Selection for old behavior: only checks NotificationInd and its content location
+ private static final String DUP_NOTIFICATION_QUERY_SELECTION_OLD =
+ "(" + Mms.MESSAGE_TYPE + "=?) AND (" + Mms.CONTENT_LOCATION + "=?)";
+
+ private static final int MAX_RETURN = 32;
+ private static String[] getDupNotifications(final Context context, final NotificationInd nInd) {
+ final byte[] rawLocation = nInd.getContentLocation();
+ if (rawLocation != null) {
+ final String location = new String(rawLocation);
+ // We can not be sure if the content location of an MMS is globally and historically
+ // unique. So we limit the dedup time within the last 7 days
+ // (or configured by gservices remotely). If the same content location shows up after
+ // that, we will download regardless. Duplicated message is better than no message.
+ String selection;
+ String[] selectionArgs;
+ final long timeLimit = BugleGservices.get().getLong(
+ BugleGservicesKeys.MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS,
+ BugleGservicesKeys.MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS_DEFAULT);
+ if (timeLimit > 0) {
+ // New dedup algorithm
+ selection = DUP_NOTIFICATION_QUERY_SELECTION;
+ final long nowSecs = System.currentTimeMillis() / 1000;
+ final long timeLowerBoundSecs = nowSecs - timeLimit;
+ // Need upper bound to protect against clock change so that a message has a time
+ // stamp in the future
+ final long timeUpperBoundSecs = nowSecs + timeLimit;
+ selectionArgs = new String[] {
+ Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
+ Long.toString(nowSecs),
+ Long.toString(timeLowerBoundSecs),
+ Long.toString(timeUpperBoundSecs),
+ location
+ };
+ } else {
+ // If time limit is 0, we revert back to old behavior in case the new
+ // dedup algorithm behaves badly
+ selection = DUP_NOTIFICATION_QUERY_SELECTION_OLD;
+ selectionArgs = new String[] {
+ Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
+ location
+ };
+ }
+ Cursor cursor = null;
+ try {
+ cursor = SqliteWrapper.query(
+ context, context.getContentResolver(),
+ Mms.CONTENT_URI, new String[] { Mms._ID },
+ selection, selectionArgs, null);
+ final int dupCount = cursor.getCount();
+ if (dupCount > 0) {
+ // We already received the same notification before.
+ // Don't want to return too many dups. It is only for debugging.
+ final int returnCount = dupCount < MAX_RETURN ? dupCount : MAX_RETURN;
+ final String[] dups = new String[returnCount];
+ for (int i = 0; cursor.moveToNext() && i < returnCount; i++) {
+ dups[i] = cursor.getString(0);
+ }
+ return dups;
+ }
+ } catch (final SQLiteException e) {
+ LogUtil.e(TAG, "query failure: " + e, e);
+ } finally {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Try parse the address using RFC822 format. If it fails to parse, then return the
+ * original address
+ *
+ * @param address The MMS ind sender address to parse
+ * @return The real address. If in RFC822 format, returns the correct email.
+ */
+ private static String parsePotentialRfc822EmailAddress(final String address) {
+ if (address == null || !address.contains("@") || !address.contains("<")) {
+ return address;
+ }
+ final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
+ if (tokens != null && tokens.length > 0) {
+ for (final Rfc822Token token : tokens) {
+ if (token != null && !TextUtils.isEmpty(token.getAddress())) {
+ return token.getAddress();
+ }
+ }
+ }
+ return address;
+ }
+
+ public static DatabaseMessages.MmsMessage processReceivedPdu(final Context context,
+ final byte[] pushData, final int subId, final String subPhoneNumber) {
+ // Parse data
+
+ // Insert placeholder row to telephony and local db
+ // Get raw PDU push-data from the message and parse it
+ final PduParser parser = new PduParser(pushData,
+ MmsConfig.get(subId).getSupportMmsContentDisposition());
+ final GenericPdu pdu = parser.parse();
+
+ if (null == pdu) {
+ LogUtil.e(TAG, "Invalid PUSH data");
+ return null;
+ }
+
+ final PduPersister p = PduPersister.getPduPersister(context);
+ final int type = pdu.getMessageType();
+
+ Uri messageUri = null;
+ switch (type) {
+ case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+ case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: {
+ // TODO: Should this be commented out?
+// threadId = findThreadId(context, pdu, type);
+// if (threadId == -1) {
+// // The associated SendReq isn't found, therefore skip
+// // processing this PDU.
+// break;
+// }
+
+// Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true,
+// MessagingPreferenceActivity.getIsGroupMmsEnabled(mContext), null);
+// // Update thread ID for ReadOrigInd & DeliveryInd.
+// ContentValues values = new ContentValues(1);
+// values.put(Mms.THREAD_ID, threadId);
+// SqliteWrapper.update(mContext, cr, uri, values, null, null);
+ LogUtil.w(TAG, "Received unsupported WAP Push, type=" + type);
+ break;
+ }
+ case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: {
+ final NotificationInd nInd = (NotificationInd) pdu;
+
+ if (MmsConfig.get(subId).getTransIdEnabled()) {
+ final byte [] contentLocationTemp = nInd.getContentLocation();
+ if ('=' == contentLocationTemp[contentLocationTemp.length - 1]) {
+ final byte [] transactionIdTemp = nInd.getTransactionId();
+ final byte [] contentLocationWithId =
+ new byte [contentLocationTemp.length
+ + transactionIdTemp.length];
+ System.arraycopy(contentLocationTemp, 0, contentLocationWithId,
+ 0, contentLocationTemp.length);
+ System.arraycopy(transactionIdTemp, 0, contentLocationWithId,
+ contentLocationTemp.length, transactionIdTemp.length);
+ nInd.setContentLocation(contentLocationWithId);
+ }
+ }
+ final String[] dups = getDupNotifications(context, nInd);
+ if (dups == null) {
+ // TODO: Do we handle Rfc822 Email Addresses?
+ //final String contentLocation =
+ // MmsUtils.bytesToString(nInd.getContentLocation(), "UTF-8");
+ //final byte[] transactionId = nInd.getTransactionId();
+ //final long messageSize = nInd.getMessageSize();
+ //final long expiry = nInd.getExpiry();
+ //final String transactionIdString =
+ // MmsUtils.bytesToString(transactionId, "UTF-8");
+
+ //final EncodedStringValue fromEncoded = nInd.getFrom();
+ // An mms ind received from email address will have from address shown as
+ // "John Doe <johndoe@foobar.com>" but the actual received message will only
+ // have the email address. So let's try to parse the RFC822 format to get the
+ // real email. Otherwise we will create two conversations for the MMS
+ // notification and the actual MMS message if auto retrieve is disabled.
+ //final String from = parsePotentialRfc822EmailAddress(
+ // fromEncoded != null ? fromEncoded.getString() : null);
+
+ Uri inboxUri = null;
+ try {
+ inboxUri = p.persist(pdu, Mms.Inbox.CONTENT_URI, subId, subPhoneNumber,
+ null);
+ messageUri = ContentUris.withAppendedId(Mms.CONTENT_URI,
+ ContentUris.parseId(inboxUri));
+ } catch (final MmsException e) {
+ LogUtil.e(TAG, "Failed to save the data from PUSH: type=" + type, e);
+ }
+ } else {
+ LogUtil.w(TAG, "Received WAP Push is a dup: " + Joiner.on(',').join(dups));
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.w(TAG, "Dup WAP Push url=" + new String(nInd.getContentLocation()));
+ }
+ }
+ break;
+ }
+ default:
+ LogUtil.e(TAG, "Received unrecognized WAP Push, type=" + type);
+ }
+
+ DatabaseMessages.MmsMessage mms = null;
+ if (messageUri != null) {
+ mms = MmsUtils.loadMms(messageUri);
+ }
+ return mms;
+ }
+
+ public static Uri insertSendingMmsMessage(final Context context, final List<String> recipients,
+ final MessageData content, final int subId, final String subPhoneNumber,
+ final long timestamp) {
+ final SendReq sendReq = createMmsSendReq(
+ context, subId, recipients.toArray(new String[recipients.size()]), content,
+ DEFAULT_DELIVERY_REPORT_MODE,
+ DEFAULT_READ_REPORT_MODE,
+ DEFAULT_EXPIRY_TIME_IN_SECONDS,
+ DEFAULT_PRIORITY,
+ timestamp);
+ Uri messageUri = null;
+ if (sendReq != null) {
+ final Uri outboxUri = MmsUtils.insertSendReq(context, sendReq, subId, subPhoneNumber);
+ if (outboxUri != null) {
+ messageUri = ContentUris.withAppendedId(Telephony.Mms.CONTENT_URI,
+ ContentUris.parseId(outboxUri));
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "Mmsutils: Inserted sending MMS message into telephony, uri: "
+ + outboxUri);
+ }
+ } else {
+ LogUtil.e(TAG, "insertSendingMmsMessage: failed to persist message into telephony");
+ }
+ }
+ return messageUri;
+ }
+
+ public static MessageData readSendingMmsMessage(final Uri messageUri,
+ final String conversationId, final String participantId, final String selfId) {
+ MessageData message = null;
+ if (messageUri != null) {
+ final DatabaseMessages.MmsMessage mms = MmsUtils.loadMms(messageUri);
+
+ // Make sure that the message has not been deleted from the Telephony DB
+ if (mms != null) {
+ // Transform the message
+ message = MmsUtils.createMmsMessage(mms, conversationId, participantId, selfId,
+ MessageData.BUGLE_STATUS_OUTGOING_RESENDING);
+ }
+ }
+ return message;
+ }
+
+ /**
+ * Create an MMS message with subject, text and image
+ *
+ * @return Both the M-Send.req and the M-Send.conf for processing in the caller
+ * @throws MmsException
+ */
+ private static SendReq createMmsSendReq(final Context context, final int subId,
+ final String[] recipients, final MessageData message,
+ final boolean requireDeliveryReport, final boolean requireReadReport,
+ final long expiryTime, final int priority, final long timestampMillis) {
+ Assert.notNull(context);
+ if (recipients == null || recipients.length < 1) {
+ throw new IllegalArgumentException("MMS sendReq no recipient");
+ }
+
+ // Make a copy so we don't propagate changes to recipients to outside of this method
+ final String[] recipientsCopy = new String[recipients.length];
+ // Don't send phone number as is since some received phone number is malformed
+ // for sending. We need to strip the separators.
+ for (int i = 0; i < recipients.length; i++) {
+ final String recipient = recipients[i];
+ if (EmailAddress.isValidEmail(recipients[i])) {
+ // Don't do stripping for emails
+ recipientsCopy[i] = recipient;
+ } else {
+ recipientsCopy[i] = stripPhoneNumberSeparators(recipient);
+ }
+ }
+
+ SendReq sendReq = null;
+ try {
+ sendReq = createSendReq(context, subId, recipientsCopy,
+ message, requireDeliveryReport,
+ requireReadReport, expiryTime, priority, timestampMillis);
+ } catch (final InvalidHeaderValueException e) {
+ LogUtil.e(TAG, "InvalidHeaderValue creating sendReq PDU");
+ } catch (final OutOfMemoryError e) {
+ LogUtil.e(TAG, "Out of memory error creating sendReq PDU");
+ }
+ return sendReq;
+ }
+
+ /**
+ * Stripping out the invalid characters in a phone number before sending
+ * MMS. We only keep alphanumeric and '*', '#', '+'.
+ */
+ private static String stripPhoneNumberSeparators(final String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+ final int len = phoneNumber.length();
+ final StringBuilder ret = new StringBuilder(len);
+ for (int i = 0; i < len; i++) {
+ final char c = phoneNumber.charAt(i);
+ if (Character.isLetterOrDigit(c) || c == '+' || c == '*' || c == '#') {
+ ret.append(c);
+ }
+ }
+ return ret.toString();
+ }
+
+ /**
+ * Create M-Send.req for the MMS message to be sent.
+ *
+ * @return the M-Send.req
+ * @throws InvalidHeaderValueException if there is any error in parsing the input
+ */
+ static SendReq createSendReq(final Context context, final int subId,
+ final String[] recipients, final MessageData message,
+ final boolean requireDeliveryReport,
+ final boolean requireReadReport, final long expiryTime, final int priority,
+ final long timestampMillis)
+ throws InvalidHeaderValueException {
+ final SendReq req = new SendReq();
+ // From, per spec
+ final String lineNumber = PhoneUtils.get(subId).getCanonicalForSelf(true/*allowOverride*/);
+ if (!TextUtils.isEmpty(lineNumber)) {
+ req.setFrom(new EncodedStringValue(lineNumber));
+ }
+ // To
+ final EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipients);
+ if (encodedNumbers != null) {
+ req.setTo(encodedNumbers);
+ }
+ // Subject
+ if (!TextUtils.isEmpty(message.getMmsSubject())) {
+ req.setSubject(new EncodedStringValue(message.getMmsSubject()));
+ }
+ // Date
+ req.setDate(timestampMillis / 1000L);
+ // Body
+ final MmsInfo bodyInfo = MmsUtils.makePduBody(context, message, subId);
+ req.setBody(bodyInfo.mPduBody);
+ // Message size
+ req.setMessageSize(bodyInfo.mMessageSize);
+ // Message class
+ req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
+ // Expiry
+ req.setExpiry(expiryTime);
+ // Priority
+ req.setPriority(priority);
+ // Delivery report
+ req.setDeliveryReport(requireDeliveryReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
+ // Read report
+ req.setReadReport(requireReadReport ? PduHeaders.VALUE_YES : PduHeaders.VALUE_NO);
+ return req;
+ }
+
+ public static boolean isDeliveryReportRequired(final int subId) {
+ if (!MmsConfig.get(subId).getSMSDeliveryReportsEnabled()) {
+ return false;
+ }
+ final Context context = Factory.get().getApplicationContext();
+ final Resources res = context.getResources();
+ final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
+ final String deliveryReportKey = res.getString(R.string.delivery_reports_pref_key);
+ final boolean defaultValue = res.getBoolean(R.bool.delivery_reports_pref_default);
+ return prefs.getBoolean(deliveryReportKey, defaultValue);
+ }
+
+ public static int sendSmsMessage(final String recipient, final String messageText,
+ final Uri requestUri, final int subId,
+ final String smsServiceCenter, final boolean requireDeliveryReport) {
+ if (!isSmsDataAvailable(subId)) {
+ LogUtil.w(TAG, "MmsUtils: can't send SMS without radio");
+ return MMS_REQUEST_MANUAL_RETRY;
+ }
+ final Context context = Factory.get().getApplicationContext();
+ int status = MMS_REQUEST_MANUAL_RETRY;
+ try {
+ // Send a single message
+ final SendResult result = SmsSender.sendMessage(
+ context,
+ subId,
+ recipient,
+ messageText,
+ smsServiceCenter,
+ requireDeliveryReport,
+ requestUri);
+ if (!result.hasPending()) {
+ // not timed out, check failures
+ final int failureLevel = result.getHighestFailureLevel();
+ switch (failureLevel) {
+ case SendResult.FAILURE_LEVEL_NONE:
+ status = MMS_REQUEST_SUCCEEDED;
+ break;
+ case SendResult.FAILURE_LEVEL_TEMPORARY:
+ status = MMS_REQUEST_AUTO_RETRY;
+ LogUtil.e(TAG, "MmsUtils: SMS temporary failure");
+ break;
+ case SendResult.FAILURE_LEVEL_PERMANENT:
+ LogUtil.e(TAG, "MmsUtils: SMS permanent failure");
+ break;
+ }
+ } else {
+ // Timed out
+ LogUtil.e(TAG, "MmsUtils: sending SMS timed out");
+ }
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "MmsUtils: failed to send SMS " + e, e);
+ }
+ return status;
+ }
+
+ /**
+ * Delete SMS and MMS messages in a particular thread
+ *
+ * @return the number of messages deleted
+ */
+ public static int deleteThread(final long threadId, final long cutOffTimestampInMillis) {
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ final Uri threadUri = ContentUris.withAppendedId(Telephony.Threads.CONTENT_URI, threadId);
+ if (cutOffTimestampInMillis < Long.MAX_VALUE) {
+ return resolver.delete(threadUri, Sms.DATE + "<=?",
+ new String[] { Long.toString(cutOffTimestampInMillis) });
+ } else {
+ return resolver.delete(threadUri, null /* smsSelection */, null /* selectionArgs */);
+ }
+ }
+
+ /**
+ * Delete single SMS and MMS message
+ *
+ * @return number of rows deleted (should be 1 or 0)
+ */
+ public static int deleteMessage(final Uri messageUri) {
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ return resolver.delete(messageUri, null /* selection */, null /* selectionArgs */);
+ }
+
+ public static byte[] createDebugNotificationInd(final String fileName) {
+ byte[] pduData = null;
+ try {
+ final Context context = Factory.get().getApplicationContext();
+ // Load the message file
+ final byte[] data = DebugUtils.receiveFromDumpFile(fileName);
+ final RetrieveConf retrieveConf = receiveFromDumpFile(data);
+ // Create the notification
+ final NotificationInd notification = new NotificationInd();
+ final long expiry = System.currentTimeMillis() / 1000 + 600;
+ notification.setTransactionId(fileName.getBytes());
+ notification.setMmsVersion(retrieveConf.getMmsVersion());
+ notification.setFrom(retrieveConf.getFrom());
+ notification.setSubject(retrieveConf.getSubject());
+ notification.setExpiry(expiry);
+ notification.setMessageSize(data.length);
+ notification.setMessageClass(retrieveConf.getMessageClass());
+
+ final Uri.Builder builder = MediaScratchFileProvider.getUriBuilder();
+ builder.appendPath(fileName);
+ final Uri contentLocation = builder.build();
+ notification.setContentLocation(contentLocation.toString().getBytes());
+
+ // Serialize
+ pduData = new PduComposer(context, notification).make();
+ if (pduData == null || pduData.length < 1) {
+ throw new IllegalArgumentException("Empty or zero length PDU data");
+ }
+ } catch (final MmsFailureException e) {
+ // Nothing to do
+ } catch (final InvalidHeaderValueException e) {
+ // Nothing to do
+ }
+ return pduData;
+ }
+
+ public static int mapRawStatusToErrorResourceId(final int bugleStatus, final int rawStatus) {
+ int stringResId = R.string.message_status_send_failed;
+ switch (rawStatus) {
+ case PduHeaders.RESPONSE_STATUS_ERROR_SERVICE_DENIED:
+ case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID:
+ stringResId = R.string.mms_failure_outgoing_service;
+ break;
+ case PduHeaders.RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED:
+ case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED:
+ case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED:
+ stringResId = R.string.mms_failure_outgoing_address;
+ break;
+ case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT:
+ case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT:
+ stringResId = R.string.mms_failure_outgoing_corrupt;
+ break;
+ case PduHeaders.RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED:
+ case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED:
+ stringResId = R.string.mms_failure_outgoing_content;
+ break;
+ case PduHeaders.RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND:
+ //case PduHeaders.RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
+ stringResId = R.string.mms_failure_outgoing_unsupported;
+ break;
+ case MessageData.RAW_TELEPHONY_STATUS_MESSAGE_TOO_BIG:
+ stringResId = R.string.mms_failure_outgoing_too_large;
+ break;
+ }
+ return stringResId;
+ }
+
+ /**
+ * The absence of a connection type.
+ */
+ public static final int TYPE_NONE = -1;
+
+ public static int getConnectivityEventNetworkType(final Context context, final Intent intent) {
+ final ConnectivityManager connMgr = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (OsUtil.isAtLeastJB_MR1()) {
+ return intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, TYPE_NONE);
+ } else {
+ final NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ if (info != null) {
+ return info.getType();
+ }
+ }
+ return TYPE_NONE;
+ }
+
+ /**
+ * Dump the raw MMS data into a file
+ *
+ * @param rawPdu The raw pdu data
+ * @param pdu The parsed pdu, used to construct a dump file name
+ */
+ public static void dumpPdu(final byte[] rawPdu, final GenericPdu pdu) {
+ if (rawPdu == null || rawPdu.length < 1) {
+ return;
+ }
+ final String dumpFileName = MmsUtils.MMS_DUMP_PREFIX + getDumpFileId(pdu);
+ final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true);
+ if (dumpFile != null) {
+ try {
+ final FileOutputStream fos = new FileOutputStream(dumpFile);
+ final BufferedOutputStream bos = new BufferedOutputStream(fos);
+ try {
+ bos.write(rawPdu);
+ bos.flush();
+ } finally {
+ bos.close();
+ }
+ DebugUtils.ensureReadable(dumpFile);
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "dumpPdu: " + e, e);
+ }
+ }
+ }
+
+ /**
+ * Get the dump file id based on the parsed PDU
+ * 1. Use message id if not empty
+ * 2. Use transaction id if message id is empty
+ * 3. If all above is empty, use random UUID
+ *
+ * @param pdu the parsed PDU
+ * @return the id of the dump file
+ */
+ private static String getDumpFileId(final GenericPdu pdu) {
+ String fileId = null;
+ if (pdu != null && pdu instanceof RetrieveConf) {
+ final RetrieveConf retrieveConf = (RetrieveConf) pdu;
+ if (retrieveConf.getMessageId() != null) {
+ fileId = new String(retrieveConf.getMessageId());
+ } else if (retrieveConf.getTransactionId() != null) {
+ fileId = new String(retrieveConf.getTransactionId());
+ }
+ }
+ if (TextUtils.isEmpty(fileId)) {
+ fileId = UUID.randomUUID().toString();
+ }
+ return fileId;
+ }
+}
diff --git a/src/com/android/messaging/sms/SmsException.java b/src/com/android/messaging/sms/SmsException.java
new file mode 100644
index 0000000..728db8c
--- /dev/null
+++ b/src/com/android/messaging/sms/SmsException.java
@@ -0,0 +1,59 @@
+/*
+ * 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.sms;
+
+/**
+ * A generic Exception for errors in sending SMS
+ */
+class SmsException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new SmsException.
+ */
+ public SmsException() {
+ super();
+ }
+
+ /**
+ * Creates a new SmsException with the specified detail message.
+ *
+ * @param message the detail message.
+ */
+ public SmsException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new SmsException with the specified cause.
+ *
+ * @param cause the cause.
+ */
+ public SmsException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a new SmsException with the specified detail message and cause.
+ *
+ * @param message the detail message.
+ * @param cause the cause.
+ */
+ public SmsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/android/messaging/sms/SmsReleaseStorage.java b/src/com/android/messaging/sms/SmsReleaseStorage.java
new file mode 100644
index 0000000..13a6284
--- /dev/null
+++ b/src/com/android/messaging/sms/SmsReleaseStorage.java
@@ -0,0 +1,166 @@
+/*
+ * 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.sms;
+
+import android.content.res.Resources;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.LogUtil;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class handling message cleanup when storage is low
+ */
+public class SmsReleaseStorage {
+ /**
+ * Class representing a time duration specified by Gservices
+ */
+ public static class Duration {
+ // Time duration unit types
+ public static final int UNIT_WEEK = 'w';
+ public static final int UNIT_MONTH = 'm';
+ public static final int UNIT_YEAR = 'y';
+
+ // Number of units
+ public final int mCount;
+ // Unit type: week, month or year
+ public final int mUnit;
+
+ public Duration(final int count, final int unit) {
+ mCount = count;
+ mUnit = unit;
+ }
+ }
+
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final Duration DEFAULT_DURATION = new Duration(1, Duration.UNIT_MONTH);
+
+ private static final Pattern DURATION_PATTERN = Pattern.compile("([1-9]+\\d*)(w|m|y)");
+ /**
+ * Parse message retaining time duration specified by Gservices
+ *
+ * @return The parsed time duration from Gservices
+ */
+ public static Duration parseMessageRetainingDuration() {
+ final String smsAutoDeleteMessageRetainingDuration =
+ BugleGservices.get().getString(
+ BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION,
+ BugleGservicesKeys.SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT);
+ final Matcher matcher = DURATION_PATTERN.matcher(smsAutoDeleteMessageRetainingDuration);
+ try {
+ if (matcher.matches()) {
+ return new Duration(
+ Integer.parseInt(matcher.group(1)),
+ matcher.group(2).charAt(0));
+ }
+ } catch (final NumberFormatException e) {
+ // Nothing to do
+ }
+ LogUtil.e(TAG, "SmsAutoDelete: invalid duration " +
+ smsAutoDeleteMessageRetainingDuration);
+ return DEFAULT_DURATION;
+ }
+
+ /**
+ * Get string representation of the time duration
+ *
+ * @param duration
+ * @return
+ */
+ public static String getMessageRetainingDurationString(final Duration duration) {
+ final Resources resources = Factory.get().getApplicationContext().getResources();
+ switch (duration.mUnit) {
+ case Duration.UNIT_WEEK:
+ return resources.getQuantityString(
+ R.plurals.week_count, duration.mCount, duration.mCount);
+ case Duration.UNIT_MONTH:
+ return resources.getQuantityString(
+ R.plurals.month_count, duration.mCount, duration.mCount);
+ case Duration.UNIT_YEAR:
+ return resources.getQuantityString(
+ R.plurals.year_count, duration.mCount, duration.mCount);
+ }
+ throw new IllegalArgumentException(
+ "SmsAutoDelete: invalid duration unit " + duration.mUnit);
+ }
+
+ // Time conversations
+ private static final long WEEK_IN_MILLIS = 7 * 24 * 3600 * 1000L;
+ private static final long MONTH_IN_MILLIS = 30 * 24 * 3600 * 1000L;
+ private static final long YEAR_IN_MILLIS = 365 * 24 * 3600 * 1000L;
+
+ /**
+ * Convert time duration to time in milliseconds
+ *
+ * @param duration
+ * @return
+ */
+ public static long durationToTimeInMillis(final Duration duration) {
+ switch (duration.mUnit) {
+ case Duration.UNIT_WEEK:
+ return duration.mCount * WEEK_IN_MILLIS;
+ case Duration.UNIT_MONTH:
+ return duration.mCount * MONTH_IN_MILLIS;
+ case Duration.UNIT_YEAR:
+ return duration.mCount * YEAR_IN_MILLIS;
+ }
+ return -1L;
+ }
+
+ /**
+ * Delete message actions:
+ * 0: delete media messages
+ * 1: delete old messages
+ *
+ * @param actionIndex The index of the delete action to perform
+ * @param durationInMillis The time duration for retaining messages
+ */
+ public static void deleteMessages(final int actionIndex, final long durationInMillis) {
+ int deleted = 0;
+ switch (actionIndex) {
+ case 0: {
+ // Delete media
+ deleted = MmsUtils.deleteMediaMessages();
+ break;
+ }
+ case 1: {
+ // Delete old messages
+ final long now = System.currentTimeMillis();
+ final long cutOffTimestampInMillis = now - durationInMillis;
+ // Delete messages from telephony provider
+ deleted = MmsUtils.deleteMessagesOlderThan(cutOffTimestampInMillis);
+ break;
+ }
+ default: {
+ LogUtil.e(TAG, "SmsStorageStatusManager: invalid action " + actionIndex);
+ break;
+ }
+ }
+
+ if (deleted > 0) {
+ // Kick off a sync to update local db.
+ SyncManager.sync();
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/SmsSender.java b/src/com/android/messaging/sms/SmsSender.java
new file mode 100644
index 0000000..889973f
--- /dev/null
+++ b/src/com/android/messaging/sms/SmsSender.java
@@ -0,0 +1,315 @@
+/*
+ * 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.sms;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.receiver.SendStatusReceiver;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Class that sends chat message via SMS.
+ *
+ * The interface emulates a blocking sending similar to making an HTTP request.
+ * It calls the SmsManager to send a (potentially multipart) message and waits
+ * on the sent status on each part. The waiting has a timeout so it won't wait
+ * forever. Once the sent status of all parts received, the call returns.
+ * A successful sending requires success status for all parts. Otherwise, we
+ * pick the highest level of failure as the error for the whole message, which
+ * is used to determine if we need to retry the sending.
+ */
+public class SmsSender {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ public static final String EXTRA_PART_ID = "part_id";
+
+ /*
+ * A map for pending sms messages. The key is the random request UUID.
+ */
+ private static ConcurrentHashMap<Uri, SendResult> sPendingMessageMap =
+ new ConcurrentHashMap<Uri, SendResult>();
+
+ private static final Random RANDOM = new Random();
+
+ // Whether we should send multipart SMS as separate messages
+ private static Boolean sSendMultipartSmsAsSeparateMessages = null;
+
+ /**
+ * Class that holds the sent status for all parts of a multipart message sending
+ */
+ public static class SendResult {
+ // Failure levels, used by the caller of the sender.
+ // For temporary failures, possibly we could retry the sending
+ // For permanent failures, we probably won't retry
+ public static final int FAILURE_LEVEL_NONE = 0;
+ public static final int FAILURE_LEVEL_TEMPORARY = 1;
+ public static final int FAILURE_LEVEL_PERMANENT = 2;
+
+ // Tracking the remaining pending parts in sending
+ private int mPendingParts;
+ // Tracking the highest level of failure among all parts
+ private int mHighestFailureLevel;
+
+ public SendResult(final int numOfParts) {
+ Assert.isTrue(numOfParts > 0);
+ mPendingParts = numOfParts;
+ mHighestFailureLevel = FAILURE_LEVEL_NONE;
+ }
+
+ // Update the sent status of one part
+ public void setPartResult(final int resultCode) {
+ mPendingParts--;
+ setHighestFailureLevel(resultCode);
+ }
+
+ public boolean hasPending() {
+ return mPendingParts > 0;
+ }
+
+ public int getHighestFailureLevel() {
+ return mHighestFailureLevel;
+ }
+
+ private int getFailureLevel(final int resultCode) {
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ return FAILURE_LEVEL_NONE;
+ case SmsManager.RESULT_ERROR_NO_SERVICE:
+ return FAILURE_LEVEL_TEMPORARY;
+ case SmsManager.RESULT_ERROR_RADIO_OFF:
+ return FAILURE_LEVEL_PERMANENT;
+ case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
+ return FAILURE_LEVEL_PERMANENT;
+ default: {
+ LogUtil.e(TAG, "SmsSender: Unexpected sent intent resultCode = " + resultCode);
+ return FAILURE_LEVEL_PERMANENT;
+ }
+ }
+ }
+
+ private void setHighestFailureLevel(final int resultCode) {
+ final int level = getFailureLevel(resultCode);
+ if (level > mHighestFailureLevel) {
+ mHighestFailureLevel = level;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("SendResult:");
+ sb.append("Pending=").append(mPendingParts).append(",");
+ sb.append("HighestFailureLevel=").append(mHighestFailureLevel);
+ return sb.toString();
+ }
+ }
+
+ public static void setResult(final Uri requestId, final int resultCode,
+ final int errorCode, final int partId, int subId) {
+ if (resultCode != Activity.RESULT_OK) {
+ LogUtil.e(TAG, "SmsSender: failure in sending message part. "
+ + " requestId=" + requestId + " partId=" + partId
+ + " resultCode=" + resultCode + " errorCode=" + errorCode);
+ if (errorCode != SendStatusReceiver.NO_ERROR_CODE) {
+ final Context context = Factory.get().getApplicationContext();
+ UiUtils.showToastAtBottom(getSendErrorToastMessage(context, subId, errorCode));
+ }
+ } else {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SmsSender: received sent result. " + " requestId=" + requestId
+ + " partId=" + partId + " resultCode=" + resultCode);
+ }
+ }
+ if (requestId != null) {
+ final SendResult result = sPendingMessageMap.get(requestId);
+ if (result != null) {
+ synchronized (result) {
+ result.setPartResult(resultCode);
+ if (!result.hasPending()) {
+ result.notifyAll();
+ }
+ }
+ } else {
+ LogUtil.e(TAG, "SmsSender: ignoring sent result. " + " requestId=" + requestId
+ + " partId=" + partId + " resultCode=" + resultCode);
+ }
+ }
+ }
+
+ private static String getSendErrorToastMessage(final Context context, final int subId,
+ final int errorCode) {
+ final String carrierName = PhoneUtils.get(subId).getCarrierName();
+ if (TextUtils.isEmpty(carrierName)) {
+ return context.getString(R.string.carrier_send_error_unknown_carrier, errorCode);
+ } else {
+ return context.getString(R.string.carrier_send_error, carrierName, errorCode);
+ }
+ }
+
+ // This should be called from a RequestWriter queue thread
+ public static SendResult sendMessage(final Context context, final int subId, String dest,
+ String message, final String serviceCenter, final boolean requireDeliveryReport,
+ final Uri messageUri) throws SmsException {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SmsSender: sending message. " +
+ "dest=" + dest + " message=" + message +
+ " serviceCenter=" + serviceCenter +
+ " requireDeliveryReport=" + requireDeliveryReport +
+ " requestId=" + messageUri);
+ }
+ if (TextUtils.isEmpty(message)) {
+ throw new SmsException("SmsSender: empty text message");
+ }
+ // Get the real dest and message for email or alias if dest is email or alias
+ // Or sanitize the dest if dest is a number
+ if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway()) &&
+ (MmsSmsUtils.isEmailAddress(dest) || MmsSmsUtils.isAlias(dest, subId))) {
+ // The original destination (email address) goes with the message
+ message = dest + " " + message;
+ // the new address is the email gateway #
+ dest = MmsConfig.get(subId).getEmailGateway();
+ } else {
+ // remove spaces and dashes from destination number
+ // (e.g. "801 555 1212" -> "8015551212")
+ // (e.g. "+8211-123-4567" -> "+82111234567")
+ dest = PhoneNumberUtils.stripSeparators(dest);
+ }
+ if (TextUtils.isEmpty(dest)) {
+ throw new SmsException("SmsSender: empty destination address");
+ }
+ // Divide the input message by SMS length limit
+ final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
+ final ArrayList<String> messages = smsManager.divideMessage(message);
+ if (messages == null || messages.size() < 1) {
+ throw new SmsException("SmsSender: fails to divide message");
+ }
+ // Prepare the send result, which collects the send status for each part
+ final SendResult pendingResult = new SendResult(messages.size());
+ sPendingMessageMap.put(messageUri, pendingResult);
+ // Actually send the sms
+ sendInternal(
+ context, subId, dest, messages, serviceCenter, requireDeliveryReport, messageUri);
+ // Wait for pending intent to come back
+ synchronized (pendingResult) {
+ final long smsSendTimeoutInMillis = BugleGservices.get().getLong(
+ BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS,
+ BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT);
+ final long beginTime = SystemClock.elapsedRealtime();
+ long waitTime = smsSendTimeoutInMillis;
+ // We could possibly be woken up while still pending
+ // so make sure we wait the full timeout period unless
+ // we have the send results of all parts.
+ while (pendingResult.hasPending() && waitTime > 0) {
+ try {
+ pendingResult.wait(waitTime);
+ } catch (final InterruptedException e) {
+ LogUtil.e(TAG, "SmsSender: sending wait interrupted");
+ }
+ waitTime = smsSendTimeoutInMillis - (SystemClock.elapsedRealtime() - beginTime);
+ }
+ }
+ // Either we timed out or have all the results (success or failure)
+ sPendingMessageMap.remove(messageUri);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "SmsSender: sending completed. " +
+ "dest=" + dest + " message=" + message + " result=" + pendingResult);
+ }
+ return pendingResult;
+ }
+
+ // Actually sending the message using SmsManager
+ private static void sendInternal(final Context context, final int subId, String dest,
+ final ArrayList<String> messages, final String serviceCenter,
+ final boolean requireDeliveryReport, final Uri messageUri) throws SmsException {
+ Assert.notNull(context);
+ final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
+ final int messageCount = messages.size();
+ final ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
+ final ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
+ for (int i = 0; i < messageCount; i++) {
+ // Make pending intents different for each message part
+ final int partId = (messageCount <= 1 ? 0 : i + 1);
+ if (requireDeliveryReport && (i == (messageCount - 1))) {
+ // TODO we only care about the delivery status of the last part
+ // Shall we have better tracking of delivery status of all parts?
+ deliveryIntents.add(PendingIntent.getBroadcast(
+ context,
+ partId,
+ getSendStatusIntent(context, SendStatusReceiver.MESSAGE_DELIVERED_ACTION,
+ messageUri, partId, subId),
+ 0/*flag*/));
+ } else {
+ deliveryIntents.add(null);
+ }
+ sentIntents.add(PendingIntent.getBroadcast(
+ context,
+ partId,
+ getSendStatusIntent(context, SendStatusReceiver.MESSAGE_SENT_ACTION,
+ messageUri, partId, subId),
+ 0/*flag*/));
+ }
+ if (sSendMultipartSmsAsSeparateMessages == null) {
+ sSendMultipartSmsAsSeparateMessages = MmsConfig.get(subId)
+ .getSendMultipartSmsAsSeparateMessages();
+ }
+ try {
+ if (sSendMultipartSmsAsSeparateMessages) {
+ // If multipart sms is not supported, send them as separate messages
+ for (int i = 0; i < messageCount; i++) {
+ smsManager.sendTextMessage(dest,
+ serviceCenter,
+ messages.get(i),
+ sentIntents.get(i),
+ deliveryIntents.get(i));
+ }
+ } else {
+ smsManager.sendMultipartTextMessage(
+ dest, serviceCenter, messages, sentIntents, deliveryIntents);
+ }
+ } catch (final Exception e) {
+ throw new SmsException("SmsSender: caught exception in sending " + e);
+ }
+ }
+
+ private static Intent getSendStatusIntent(final Context context, final String action,
+ final Uri requestUri, final int partId, final int subId) {
+ // Encode requestId in intent data
+ final Intent intent = new Intent(action, requestUri, context, SendStatusReceiver.class);
+ intent.putExtra(SendStatusReceiver.EXTRA_PART_ID, partId);
+ intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId);
+ return intent;
+ }
+}
diff --git a/src/com/android/messaging/sms/SmsStorageStatusManager.java b/src/com/android/messaging/sms/SmsStorageStatusManager.java
new file mode 100644
index 0000000..ff7b79d
--- /dev/null
+++ b/src/com/android/messaging/sms/SmsStorageStatusManager.java
@@ -0,0 +1,102 @@
+/*
+ * 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.sms;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.PendingIntentConstants;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Class that handles SMS auto delete and notification when storage is low
+ */
+public class SmsStorageStatusManager {
+ /**
+ * Handles storage low signal for SMS
+ */
+ public static void handleStorageLow() {
+ if (!PhoneUtils.getDefault().isSmsEnabled()) {
+ return;
+ }
+
+ // TODO: Auto-delete messages, when that setting exists and is enabled
+
+ // Notify low storage for SMS
+ postStorageLowNotification();
+ }
+
+ /**
+ * Handles storage OK signal for SMS
+ */
+ public static void handleStorageOk() {
+ if (!PhoneUtils.getDefault().isSmsEnabled()) {
+ return;
+ }
+ cancelStorageLowNotification();
+ }
+
+ /**
+ * Post sms storage low notification
+ */
+ private static void postStorageLowNotification() {
+ final Context context = Factory.get().getApplicationContext();
+ final Resources resources = context.getResources();
+ final PendingIntent pendingIntent = UIIntents.get()
+ .getPendingIntentForLowStorageNotifications(context);
+
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
+ builder.setContentTitle(resources.getString(R.string.sms_storage_low_title))
+ .setTicker(resources.getString(R.string.sms_storage_low_notification_ticker))
+ .setSmallIcon(R.drawable.ic_failed_light)
+ .setPriority(Notification.PRIORITY_DEFAULT)
+ .setOngoing(true) // Can't be swiped off
+ .setAutoCancel(false) // Don't auto cancel
+ .setContentIntent(pendingIntent);
+
+ final NotificationCompat.BigTextStyle bigTextStyle =
+ new NotificationCompat.BigTextStyle(builder);
+ bigTextStyle.bigText(resources.getString(R.string.sms_storage_low_text));
+ final Notification notification = bigTextStyle.build();
+
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(Factory.get().getApplicationContext());
+
+ notificationManager.notify(getNotificationTag(),
+ PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Cancel the notification
+ */
+ public static void cancelStorageLowNotification() {
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(Factory.get().getApplicationContext());
+ notificationManager.cancel(getNotificationTag(),
+ PendingIntentConstants.SMS_STORAGE_LOW_NOTIFICATION_ID);
+ }
+
+ private static String getNotificationTag() {
+ return Factory.get().getApplicationContext().getPackageName() + ":smsstoragelow";
+ }
+}
diff --git a/src/com/android/messaging/sms/SystemProperties.java b/src/com/android/messaging/sms/SystemProperties.java
new file mode 100644
index 0000000..669e448
--- /dev/null
+++ b/src/com/android/messaging/sms/SystemProperties.java
@@ -0,0 +1,54 @@
+/*
+ * 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.sms;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Hacky way to call the hidden SystemProperties class API
+ */
+class SystemProperties {
+ private static Method sSystemPropertiesGetMethod = null;
+
+ public static String get(final String name) {
+ if (sSystemPropertiesGetMethod == null) {
+ try {
+ final Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
+ if (systemPropertiesClass != null) {
+ sSystemPropertiesGetMethod =
+ systemPropertiesClass.getMethod("get", String.class);
+ }
+ } catch (final ClassNotFoundException e) {
+ // Nothing to do
+ } catch (final NoSuchMethodException e) {
+ // Nothing to do
+ }
+ }
+ if (sSystemPropertiesGetMethod != null) {
+ try {
+ return (String) sSystemPropertiesGetMethod.invoke(null, name);
+ } catch (final IllegalArgumentException e) {
+ // Nothing to do
+ } catch (final IllegalAccessException e) {
+ // Nothing to do
+ } catch (final InvocationTargetException e) {
+ // Nothing to do
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/ui/AsyncImageView.java b/src/com/android/messaging/ui/AsyncImageView.java
new file mode 100644
index 0000000..9aaf0b1
--- /dev/null
+++ b/src/com/android/messaging/ui/AsyncImageView.java
@@ -0,0 +1,457 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.rastermill.FrameSequenceDrawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.media.BindableMediaRequest;
+import com.android.messaging.datamodel.media.GifImageResource;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.datamodel.media.ImageRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageResource;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.ThreadUtil;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+
+/**
+ * An ImageView used to asynchronously request an image from MediaResourceManager and render it.
+ */
+public class AsyncImageView extends ImageView implements MediaResourceLoadListener<ImageResource> {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ // 100ms delay before disposing the image in case the AsyncImageView is re-added to the UI
+ private static final int DISPOSE_IMAGE_DELAY = 100;
+
+ // AsyncImageView has a 1-1 binding relationship with an ImageRequest instance that requests
+ // the image from the MediaResourceManager. Since the request is done asynchronously, we
+ // want to make sure the image view is always bound to the latest image request that it
+ // issues, so that when the image is loaded, the ImageRequest (which extends BindableData)
+ // will be able to figure out whether the binding is still valid and whether the loaded image
+ // should be delivered to the AsyncImageView via onMediaResourceLoaded() callback.
+ @VisibleForTesting
+ public final Binding<BindableMediaRequest<ImageResource>> mImageRequestBinding;
+
+ /** True if we want the image to fade in when it loads */
+ private boolean mFadeIn;
+
+ /** True if we want the image to reveal (scale) when it loads. When set to true, this
+ * will take precedence over {@link #mFadeIn} */
+ private final boolean mReveal;
+
+ // The corner radius for drawing rounded corners around bitmap. The default value is zero
+ // (no rounded corners)
+ private final int mCornerRadius;
+ private final Path mRoundedCornerClipPath;
+ private int mClipPathWidth;
+ private int mClipPathHeight;
+
+ // A placeholder drawable that takes the spot of the image when it's loading. The default
+ // setting is null (no placeholder).
+ private final Drawable mPlaceholderDrawable;
+ protected ImageResource mImageResource;
+ private final Runnable mDisposeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mImageRequestBinding.isBound()) {
+ mDetachedRequestDescriptor = (ImageRequestDescriptor)
+ mImageRequestBinding.getData().getDescriptor();
+ }
+ unbindView();
+ releaseImageResource();
+ }
+ };
+
+ private AsyncImageViewDelayLoader mDelayLoader;
+ private ImageRequestDescriptor mDetachedRequestDescriptor;
+
+ public AsyncImageView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mImageRequestBinding = BindingBase.createBinding(this);
+ final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView,
+ 0, 0);
+ mFadeIn = attr.getBoolean(R.styleable.AsyncImageView_fadeIn, true);
+ mReveal = attr.getBoolean(R.styleable.AsyncImageView_reveal, false);
+ mPlaceholderDrawable = attr.getDrawable(R.styleable.AsyncImageView_placeholderDrawable);
+ mCornerRadius = attr.getDimensionPixelSize(R.styleable.AsyncImageView_cornerRadius, 0);
+ mRoundedCornerClipPath = new Path();
+
+ attr.recycle();
+ }
+
+ /**
+ * The main entrypoint for AsyncImageView to load image resource given an ImageRequestDescriptor
+ * @param descriptor the request descriptor, or null if no image should be displayed
+ */
+ public void setImageResourceId(@Nullable final ImageRequestDescriptor descriptor) {
+ final String requestKey = (descriptor == null) ? null : descriptor.getKey();
+ if (mImageRequestBinding.isBound()) {
+ if (TextUtils.equals(mImageRequestBinding.getData().getKey(), requestKey)) {
+ // Don't re-request the bitmap if the new request is for the same resource.
+ return;
+ }
+ unbindView();
+ }
+ setImage(null);
+ resetTransientViewStates();
+ if (!TextUtils.isEmpty(requestKey)) {
+ maybeSetupPlaceholderDrawable(descriptor);
+ final BindableMediaRequest<ImageResource> imageRequest =
+ descriptor.buildAsyncMediaRequest(getContext(), this);
+ requestImage(imageRequest);
+ }
+ }
+
+ /**
+ * Sets a delay loader that centrally manages image request delay loading logic.
+ */
+ public void setDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
+ Assert.isTrue(mDelayLoader == null);
+ mDelayLoader = delayLoader;
+ }
+
+ /**
+ * Called by the delay loader when we can resume image loading.
+ */
+ public void resumeLoading() {
+ Assert.notNull(mDelayLoader);
+ Assert.isTrue(mImageRequestBinding.isBound());
+ MediaResourceManager.get().requestMediaResourceAsync(mImageRequestBinding.getData());
+ }
+
+ /**
+ * Setup the placeholder drawable if:
+ * 1. There's an image to be loaded AND
+ * 2. We are given a placeholder drawable AND
+ * 3. The descriptor provided us with source width and height.
+ */
+ private void maybeSetupPlaceholderDrawable(final ImageRequestDescriptor descriptor) {
+ if (!TextUtils.isEmpty(descriptor.getKey()) && mPlaceholderDrawable != null) {
+ if (descriptor.sourceWidth != ImageRequest.UNSPECIFIED_SIZE &&
+ descriptor.sourceHeight != ImageRequest.UNSPECIFIED_SIZE) {
+ // Set a transparent inset drawable to the foreground so it will mimick the final
+ // size of the image, and use the background to show the actual placeholder
+ // drawable.
+ setImageDrawable(PlaceholderInsetDrawable.fromDrawable(
+ new ColorDrawable(Color.TRANSPARENT),
+ descriptor.sourceWidth, descriptor.sourceHeight));
+ }
+ setBackground(mPlaceholderDrawable);
+ }
+ }
+
+ protected void setImage(final ImageResource resource) {
+ setImage(resource, false /* isCached */);
+ }
+
+ protected void setImage(final ImageResource resource, final boolean isCached) {
+ // Switch reference to the new ImageResource. Make sure we release the current
+ // resource and addRef() on the new resource so that the underlying bitmaps don't
+ // get leaked or get recycled by the bitmap cache.
+ releaseImageResource();
+ // Ensure that any pending dispose runnables get removed.
+ ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
+ // The drawable may require work to get if its a static object so try to only make this call
+ // once.
+ final Drawable drawable = (resource != null) ? resource.getDrawable(getResources()) : null;
+ if (drawable != null) {
+ mImageResource = resource;
+ mImageResource.addRef();
+ setImageDrawable(drawable);
+ if (drawable instanceof FrameSequenceDrawable) {
+ ((FrameSequenceDrawable) drawable).start();
+ }
+
+ if (getVisibility() == VISIBLE) {
+ if (mReveal) {
+ setVisibility(INVISIBLE);
+ UiUtils.revealOrHideViewWithAnimation(this, VISIBLE, null);
+ } else if (mFadeIn && !isCached) {
+ // Hide initially to avoid flash.
+ setAlpha(0F);
+ animate().alpha(1F).start();
+ }
+ }
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ if (mImageResource instanceof GifImageResource) {
+ LogUtil.v(TAG, "setImage size unknown -- it's a GIF");
+ } else {
+ LogUtil.v(TAG, "setImage size: " + mImageResource.getMediaSize() +
+ " width: " + mImageResource.getBitmap().getWidth() +
+ " heigh: " + mImageResource.getBitmap().getHeight());
+ }
+ }
+ }
+ invalidate();
+ }
+
+ private void requestImage(final BindableMediaRequest<ImageResource> request) {
+ mImageRequestBinding.bind(request);
+ if (mDelayLoader == null || !mDelayLoader.isDelayLoadingImage()) {
+ MediaResourceManager.get().requestMediaResourceAsync(request);
+ } else {
+ mDelayLoader.registerView(this);
+ }
+ }
+
+ @Override
+ public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
+ final ImageResource resource, final boolean isCached) {
+ if (mImageResource != resource) {
+ setImage(resource, isCached);
+ }
+ }
+
+ @Override
+ public void onMediaResourceLoadError(
+ final MediaRequest<ImageResource> request, final Exception exception) {
+ // Media load failed, unbind and reset bitmap to default.
+ unbindView();
+ setImage(null);
+ }
+
+ private void releaseImageResource() {
+ final Drawable drawable = getDrawable();
+ if (drawable instanceof FrameSequenceDrawable) {
+ ((FrameSequenceDrawable) drawable).stop();
+ ((FrameSequenceDrawable) drawable).destroy();
+ }
+ if (mImageResource != null) {
+ mImageResource.release();
+ mImageResource = null;
+ }
+ setImageDrawable(null);
+ setBackground(null);
+ }
+
+ /**
+ * Resets transient view states (eg. alpha, animations) before rebinding/reusing the view.
+ */
+ private void resetTransientViewStates() {
+ clearAnimation();
+ setAlpha(1F);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ // If it was recently removed, then cancel disposing, we're still using it.
+ ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
+
+ // When the image view gets detached and immediately re-attached, any fade-in animation
+ // will be terminated, leaving the view in a semi-transparent state. Make sure we restore
+ // alpha when the view is re-attached.
+ if (mFadeIn) {
+ setAlpha(1F);
+ }
+
+ // Check whether we are in a simple reuse scenario: detached from window, and reattached
+ // later without rebinding. This may be done by containers such as the RecyclerView to
+ // reuse the views. In this case, we would like to rebind the original image request.
+ if (!mImageRequestBinding.isBound() && mDetachedRequestDescriptor != null) {
+ setImageResourceId(mDetachedRequestDescriptor);
+ }
+ mDetachedRequestDescriptor = null;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // Dispose the bitmap, but if an AysncImageView is removed from the window, then quickly
+ // re-added, we shouldn't dispose, so wait a short time before disposing
+ ThreadUtil.getMainThreadHandler().postDelayed(mDisposeRunnable, DISPOSE_IMAGE_DELAY);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // The base implementation does not honor the minimum sizes. We try to to honor it here.
+
+ final int measuredWidth = getMeasuredWidth();
+ final int measuredHeight = getMeasuredHeight();
+ if (measuredWidth >= getMinimumWidth() || measuredHeight >= getMinimumHeight()) {
+ // We are ok if either of the minimum sizes is honored. Note that satisfying both the
+ // sizes may not be possible, depending on the aspect ratio of the image and whether
+ // a maximum size has been specified. This implementation only tries to handle the case
+ // where both the minimum sizes are not being satisfied.
+ return;
+ }
+
+ if (!getAdjustViewBounds()) {
+ // The base implementation is reasonable in this case. If the view bounds cannot be
+ // changed, it is not possible to satisfy the minimum sizes anyway.
+ return;
+ }
+
+ final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+ // The base implementation is reasonable in this case.
+ return;
+ }
+
+ int width = measuredWidth;
+ int height = measuredHeight;
+ // Get the minimum sizes that will honor other constraints as well.
+ final int minimumWidth = resolveSize(
+ getMinimumWidth(), getMaxWidth(), widthMeasureSpec);
+ final int minimumHeight = resolveSize(
+ getMinimumHeight(), getMaxHeight(), heightMeasureSpec);
+ final float aspectRatio = measuredWidth / (float) measuredHeight;
+ if (aspectRatio == 0) {
+ // If the image is (close to) infinitely high, there is not much we can do.
+ return;
+ }
+
+ if (width < minimumWidth) {
+ height = resolveSize((int) (minimumWidth / aspectRatio),
+ getMaxHeight(), heightMeasureSpec);
+ width = (int) (height * aspectRatio);
+ }
+
+ if (height < minimumHeight) {
+ width = resolveSize((int) (minimumHeight * aspectRatio),
+ getMaxWidth(), widthMeasureSpec);
+ height = (int) (width / aspectRatio);
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ private static int resolveSize(int desiredSize, int maxSize, int measureSpec) {
+ final int specMode = MeasureSpec.getMode(measureSpec);
+ final int specSize = MeasureSpec.getSize(measureSpec);
+ switch(specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ return Math.min(desiredSize, maxSize);
+
+ case MeasureSpec.AT_MOST:
+ return Math.min(Math.min(desiredSize, specSize), maxSize);
+
+ default:
+ Assert.fail("Unreachable");
+ return specSize;
+ }
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ if (mCornerRadius > 0) {
+ final int currentWidth = this.getWidth();
+ final int currentHeight = this.getHeight();
+ if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
+ final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
+ mRoundedCornerClipPath.reset();
+ mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
+ Path.Direction.CW);
+ mClipPathWidth = currentWidth;
+ mClipPathHeight = currentHeight;
+ }
+
+ final int saveCount = canvas.getSaveCount();
+ canvas.save();
+ canvas.clipPath(mRoundedCornerClipPath);
+ super.onDraw(canvas);
+ canvas.restoreToCount(saveCount);
+ } else {
+ super.onDraw(canvas);
+ }
+ }
+
+ private void unbindView() {
+ if (mImageRequestBinding.isBound()) {
+ mImageRequestBinding.unbind();
+ if (mDelayLoader != null) {
+ mDelayLoader.unregisterView(this);
+ }
+ }
+ }
+
+ /**
+ * As a performance optimization, the consumer of the AsyncImageView may opt to delay loading
+ * the image when it's busy doing other things (such as when a list view is scrolling). In
+ * order to do this, the consumer can create a new AsyncImageViewDelayLoader instance to be
+ * shared among all relevant AsyncImageViews (through setDelayLoader() method), and call
+ * onStartDelayLoading() and onStopDelayLoading() to start and stop delay loading, respectively.
+ */
+ public static class AsyncImageViewDelayLoader {
+ private boolean mShouldDelayLoad;
+ private final HashSet<AsyncImageView> mAttachedViews;
+
+ public AsyncImageViewDelayLoader() {
+ mAttachedViews = new HashSet<AsyncImageView>();
+ }
+
+ private void registerView(final AsyncImageView view) {
+ mAttachedViews.add(view);
+ }
+
+ private void unregisterView(final AsyncImageView view) {
+ mAttachedViews.remove(view);
+ }
+
+ public boolean isDelayLoadingImage() {
+ return mShouldDelayLoad;
+ }
+
+ /**
+ * Called by the consumer of this view to delay loading images
+ */
+ public void onDelayLoading() {
+ // Don't need to explicitly tell the AsyncImageView to stop loading since
+ // ImageRequests are not cancellable.
+ mShouldDelayLoad = true;
+ }
+
+ /**
+ * Called by the consumer of this view to resume loading images
+ */
+ public void onResumeLoading() {
+ if (mShouldDelayLoad) {
+ mShouldDelayLoad = false;
+
+ // Notify all attached views to resume loading.
+ for (final AsyncImageView view : mAttachedViews) {
+ view.resumeLoading();
+ }
+ mAttachedViews.clear();
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/AttachmentPreview.java b/src/com/android/messaging/ui/AttachmentPreview.java
new file mode 100644
index 0000000..7eea14b
--- /dev/null
+++ b/src/com/android/messaging/ui/AttachmentPreview.java
@@ -0,0 +1,331 @@
+/*
+ * 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;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ScrollView;
+
+import com.android.messaging.R;
+import com.android.messaging.annotation.VisibleForAnimation;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
+import com.android.messaging.ui.animation.PopupTransitionAnimation;
+import com.android.messaging.ui.conversation.ComposeMessageView;
+import com.android.messaging.ui.conversation.ConversationFragment;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ThreadUtil;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AttachmentPreview extends ScrollView implements OnAttachmentClickListener {
+ private FrameLayout mAttachmentView;
+ private ComposeMessageView mComposeMessageView;
+ private ImageButton mCloseButton;
+ private int mAnimatedHeight = -1;
+ private Animator mCloseGapAnimator;
+ private boolean mPendingFirstUpdate;
+ private Handler mHandler;
+ private Runnable mHideRunnable;
+ private boolean mPendingHideCanceled;
+
+ private static final int CLOSE_BUTTON_REVEAL_STAGGER_MILLIS = 300;
+
+ public AttachmentPreview(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mCloseButton = (ImageButton) findViewById(R.id.close_button);
+ mCloseButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ mComposeMessageView.clearAttachments();
+ }
+ });
+
+ mAttachmentView = (FrameLayout) findViewById(R.id.attachment_view);
+
+ // The attachment preview is a scroll view so that it can show the bottom portion of the
+ // attachment whenever the space is tight (e.g. when in landscape mode). Per design
+ // request we'd like to make the attachment view always scrolled to the bottom.
+ addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(final View v, final int left, final int top, final int right,
+ final int bottom, final int oldLeft, final int oldTop, final int oldRight,
+ final int oldBottom) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ final int childCount = getChildCount();
+ if (childCount > 0) {
+ final View lastChild = getChildAt(childCount - 1);
+ scrollTo(getScrollX(), lastChild.getBottom() - getHeight());
+ }
+ }
+ });
+ }
+ });
+ mPendingFirstUpdate = true;
+ }
+
+ public void setComposeMessageView(final ComposeMessageView composeMessageView) {
+ mComposeMessageView = composeMessageView;
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mAnimatedHeight >= 0) {
+ setMeasuredDimension(getMeasuredWidth(), mAnimatedHeight);
+ }
+ }
+
+ private void cancelPendingHide() {
+ mPendingHideCanceled = true;
+ }
+
+ public void hideAttachmentPreview() {
+ if (getVisibility() != GONE) {
+ UiUtils.revealOrHideViewWithAnimation(mCloseButton, GONE,
+ null /* onFinishRunnable */);
+ startCloseGapAnimationOnAttachmentClear();
+
+ if (mAttachmentView.getChildCount() > 0) {
+ mPendingHideCanceled = false;
+ final View viewToHide = mAttachmentView.getChildCount() > 1 ?
+ mAttachmentView : mAttachmentView.getChildAt(0);
+ UiUtils.revealOrHideViewWithAnimation(viewToHide, INVISIBLE,
+ new Runnable() {
+ @Override
+ public void run() {
+ // Only hide if we are didn't get overruled by showing
+ if (!mPendingHideCanceled) {
+ mAttachmentView.removeAllViews();
+ setVisibility(GONE);
+ }
+ }
+ });
+ } else {
+ mAttachmentView.removeAllViews();
+ setVisibility(GONE);
+ }
+ }
+ }
+
+ // returns true if we have attachments
+ public boolean onAttachmentsChanged(final DraftMessageData draftMessageData) {
+ final boolean isFirstUpdate = mPendingFirstUpdate;
+ final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
+ final List<PendingAttachmentData> pendingAttachments =
+ draftMessageData.getReadOnlyPendingAttachments();
+
+ // Any change in attachments would invalidate the animated height animation.
+ cancelCloseGapAnimation();
+ mPendingFirstUpdate = false;
+
+ final int combinedAttachmentCount = attachments.size() + pendingAttachments.size();
+ mCloseButton.setContentDescription(getResources()
+ .getQuantityString(R.plurals.attachment_preview_close_content_description,
+ combinedAttachmentCount));
+ if (combinedAttachmentCount == 0) {
+ mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mHideRunnable = null;
+ // Only start the hiding if there are still no attachments
+ if (attachments.size() + pendingAttachments.size() == 0) {
+ hideAttachmentPreview();
+ }
+ }
+ };
+ if (draftMessageData.isSending()) {
+ // Wait to hide until the message is ready to start animating
+ // We'll execute immediately when the animation triggers
+ mHandler.postDelayed(mHideRunnable,
+ ConversationFragment.MESSAGE_ANIMATION_MAX_WAIT);
+ } else {
+ // Run immediately when clearing attachments
+ mHideRunnable.run();
+ }
+ return false;
+ }
+
+ cancelPendingHide(); // We're showing
+ if (getVisibility() != VISIBLE) {
+ setVisibility(VISIBLE);
+ mAttachmentView.setVisibility(VISIBLE);
+
+ // Don't animate in the close button if this is the first update after view creation.
+ // This is the initial draft load from database for pre-existing drafts.
+ if (!isFirstUpdate) {
+ // Reveal the close button after the view animates in.
+ mCloseButton.setVisibility(INVISIBLE);
+ ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ UiUtils.revealOrHideViewWithAnimation(mCloseButton, VISIBLE,
+ null /* onFinishRunnable */);
+ }
+ }, UiUtils.MEDIAPICKER_TRANSITION_DURATION + CLOSE_BUTTON_REVEAL_STAGGER_MILLIS);
+ }
+ }
+
+ // Merge the pending attachment list with real attachment. Design would prefer these be
+ // in LIFO order user can see added images past the 5th one but we also want them to be in
+ // order and we want it to be WYSIWYG.
+ final List<MessagePartData> combinedAttachments = new ArrayList<>();
+ combinedAttachments.addAll(attachments);
+ combinedAttachments.addAll(pendingAttachments);
+
+ final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ if (combinedAttachmentCount > 1) {
+ MultiAttachmentLayout multiAttachmentLayout = null;
+ Rect transitionRect = null;
+ if (mAttachmentView.getChildCount() > 0) {
+ final View firstChild = mAttachmentView.getChildAt(0);
+ if (firstChild instanceof MultiAttachmentLayout) {
+ Assert.equals(1, mAttachmentView.getChildCount());
+ multiAttachmentLayout = (MultiAttachmentLayout) firstChild;
+ multiAttachmentLayout.bindAttachments(combinedAttachments,
+ null /* transitionRect */, combinedAttachmentCount);
+ } else {
+ transitionRect = new Rect(firstChild.getLeft(), firstChild.getTop(),
+ firstChild.getRight(), firstChild.getBottom());
+ }
+ }
+ if (multiAttachmentLayout == null) {
+ multiAttachmentLayout = AttachmentPreviewFactory.createMultiplePreview(
+ getContext(), this);
+ multiAttachmentLayout.bindAttachments(combinedAttachments, transitionRect,
+ combinedAttachmentCount);
+ mAttachmentView.removeAllViews();
+ mAttachmentView.addView(multiAttachmentLayout);
+ }
+ } else {
+ final MessagePartData attachment = combinedAttachments.get(0);
+ boolean shouldAnimate = true;
+ if (mAttachmentView.getChildCount() > 0) {
+ // If we are going from N->1 attachments, try to use the current bounds
+ // bounds as the starting rect.
+ shouldAnimate = false;
+ final View firstChild = mAttachmentView.getChildAt(0);
+ if (firstChild instanceof MultiAttachmentLayout &&
+ attachment instanceof MediaPickerMessagePartData) {
+ final View leftoverView = ((MultiAttachmentLayout) firstChild)
+ .findViewForAttachment(attachment);
+ if (leftoverView != null) {
+ final Rect currentRect = UiUtils.getMeasuredBoundsOnScreen(leftoverView);
+ if (!currentRect.isEmpty() &&
+ attachment instanceof MediaPickerMessagePartData) {
+ ((MediaPickerMessagePartData) attachment).setStartRect(currentRect);
+ shouldAnimate = true;
+ }
+ }
+ }
+ }
+ mAttachmentView.removeAllViews();
+ final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(
+ layoutInflater, attachment, mAttachmentView,
+ AttachmentPreviewFactory.TYPE_SINGLE, true /* startImageRequest */, this);
+ if (attachmentView != null) {
+ mAttachmentView.addView(attachmentView);
+ if (shouldAnimate) {
+ tryAnimateViewIn(attachment, attachmentView);
+ }
+ }
+ }
+ return true;
+ }
+
+ public void onMessageAnimationStart() {
+ if (mHideRunnable == null) {
+ return;
+ }
+
+ // Run the hide animation at the same time as the message animation
+ mHandler.removeCallbacks(mHideRunnable);
+ setVisibility(View.INVISIBLE);
+ mHideRunnable.run();
+ }
+
+ static void tryAnimateViewIn(final MessagePartData attachmentData, final View view) {
+ if (attachmentData instanceof MediaPickerMessagePartData) {
+ final Rect startRect = ((MediaPickerMessagePartData) attachmentData).getStartRect();
+ new PopupTransitionAnimation(startRect, view).startAfterLayoutComplete();
+ }
+ }
+
+ @VisibleForAnimation
+ public void setAnimatedHeight(final int animatedHeight) {
+ if (mAnimatedHeight != animatedHeight) {
+ mAnimatedHeight = animatedHeight;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Kicks off an animation to animate the layout change for closing the gap between the
+ * message list and the compose message box when the attachments are cleared.
+ */
+ private void startCloseGapAnimationOnAttachmentClear() {
+ // Cancel existing animation.
+ cancelCloseGapAnimation();
+ mCloseGapAnimator = ObjectAnimator.ofInt(this, "animatedHeight", getHeight(), 0);
+ mCloseGapAnimator.start();
+ }
+
+ private void cancelCloseGapAnimation() {
+ if (mCloseGapAnimator != null) {
+ mCloseGapAnimator.cancel();
+ mCloseGapAnimator = null;
+ }
+ mAnimatedHeight = -1;
+ }
+
+ @Override
+ public boolean onAttachmentClick(final MessagePartData attachment,
+ final Rect viewBoundsOnScreen, final boolean longPress) {
+ if (longPress) {
+ mComposeMessageView.onAttachmentPreviewLongClicked();
+ return true;
+ }
+
+ if (!(attachment instanceof PendingAttachmentData) && attachment.isImage()) {
+ mComposeMessageView.displayPhoto(attachment.getContentUri(), viewBoundsOnScreen);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/messaging/ui/AttachmentPreviewFactory.java b/src/com/android/messaging/ui/AttachmentPreviewFactory.java
new file mode 100644
index 0000000..ed5d4d7
--- /dev/null
+++ b/src/com/android/messaging/ui/AttachmentPreviewFactory.java
@@ -0,0 +1,299 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.datamodel.data.PersonItemData;
+import com.android.messaging.datamodel.data.VCardContactItemData;
+import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.datamodel.media.ImageRequestDescriptor;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
+import com.android.messaging.ui.PersonItemView.PersonItemViewListener;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.UriUtil;
+
+/**
+ * A view factory that creates previews for single/multiple attachments.
+ */
+public class AttachmentPreviewFactory {
+ /** Standalone attachment preview */
+ public static final int TYPE_SINGLE = 1;
+
+ /** Attachment preview displayed in a multi-attachment layout */
+ public static final int TYPE_MULTIPLE = 2;
+
+ /** Attachment preview displayed in the attachment chooser grid view */
+ public static final int TYPE_CHOOSER_GRID = 3;
+
+ public static View createAttachmentPreview(final LayoutInflater layoutInflater,
+ final MessagePartData attachmentData, final ViewGroup parent,
+ final int viewType, final boolean startImageRequest,
+ @Nullable final OnAttachmentClickListener clickListener) {
+ final String contentType = attachmentData.getContentType();
+ View attachmentView = null;
+ if (attachmentData instanceof PendingAttachmentData) {
+ attachmentView = createPendingAttachmentPreview(layoutInflater, parent,
+ (PendingAttachmentData) attachmentData);
+ } else if (ContentType.isImageType(contentType)) {
+ attachmentView = createImagePreview(layoutInflater, attachmentData, parent, viewType,
+ startImageRequest);
+ } else if (ContentType.isAudioType(contentType)) {
+ attachmentView = createAudioPreview(layoutInflater, attachmentData, parent, viewType);
+ } else if (ContentType.isVideoType(contentType)) {
+ attachmentView = createVideoPreview(layoutInflater, attachmentData, parent, viewType);
+ } else if (ContentType.isVCardType(contentType)) {
+ attachmentView = createVCardPreview(layoutInflater, attachmentData, parent, viewType);
+ } else {
+ Assert.fail("unsupported attachment type: " + contentType);
+ return null;
+ }
+
+ // Some views have a caption, set the text/visibility if one exists
+ final TextView captionView = (TextView) attachmentView.findViewById(R.id.caption);
+ if (captionView != null) {
+ final String caption = attachmentData.getText();
+ captionView.setVisibility(TextUtils.isEmpty(caption) ? View.GONE : View.VISIBLE);
+ captionView.setText(caption);
+ }
+
+ if (attachmentView != null && clickListener != null) {
+ attachmentView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
+ clickListener.onAttachmentClick(attachmentData, bounds,
+ false /* longPress */);
+ }
+ });
+ attachmentView.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View view) {
+ final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
+ return clickListener.onAttachmentClick(attachmentData, bounds,
+ true /* longPress */);
+ }
+ });
+ }
+ return attachmentView;
+ }
+
+ public static MultiAttachmentLayout createMultiplePreview(final Context context,
+ final OnAttachmentClickListener listener) {
+ final MultiAttachmentLayout multiAttachmentLayout =
+ new MultiAttachmentLayout(context, null);
+ final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ multiAttachmentLayout.setLayoutParams(layoutParams);
+ multiAttachmentLayout.setOnAttachmentClickListener(listener);
+ return multiAttachmentLayout;
+ }
+
+ public static ImageRequestDescriptor getImageRequestDescriptorForAttachment(
+ final MessagePartData attachmentData, final int desiredWidth, final int desiredHeight) {
+ final Uri uri = attachmentData.getContentUri();
+ final String contentType = attachmentData.getContentType();
+ if (ContentType.isImageType(contentType)) {
+ final String filePath = UriUtil.getFilePathFromUri(uri);
+ if (filePath != null) {
+ return new FileImageRequestDescriptor(filePath, desiredWidth, desiredHeight,
+ attachmentData.getWidth(), attachmentData.getHeight(),
+ false /* canUseThumbnail */, true /* allowCompression */,
+ false /* isStatic */);
+ } else {
+ return new UriImageRequestDescriptor(uri, desiredWidth, desiredHeight,
+ attachmentData.getWidth(), attachmentData.getHeight(),
+ true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ }
+ }
+ return null;
+ }
+
+ private static View createImagePreview(final LayoutInflater layoutInflater,
+ final MessagePartData attachmentData, final ViewGroup parent,
+ final int viewType, final boolean startImageRequest) {
+ int layoutId = R.layout.attachment_single_image;
+ switch (viewType) {
+ case AttachmentPreviewFactory.TYPE_SINGLE:
+ layoutId = R.layout.attachment_single_image;
+ break;
+ case AttachmentPreviewFactory.TYPE_MULTIPLE:
+ layoutId = R.layout.attachment_multiple_image;
+ break;
+ case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
+ layoutId = R.layout.attachment_chooser_image;
+ break;
+ default:
+ Assert.fail("unsupported attachment view type!");
+ break;
+ }
+ final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
+ final AsyncImageView imageView = (AsyncImageView) view.findViewById(
+ R.id.attachment_image_view);
+ int maxWidth = imageView.getMaxWidth();
+ int maxHeight = imageView.getMaxHeight();
+ if (viewType == TYPE_CHOOSER_GRID) {
+ final Resources resources = layoutInflater.getContext().getResources();
+ maxWidth = maxHeight = resources.getDimensionPixelSize(
+ R.dimen.attachment_grid_image_cell_size);
+ }
+ if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) {
+ maxWidth = ImageRequest.UNSPECIFIED_SIZE;
+ }
+ if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) {
+ maxHeight = ImageRequest.UNSPECIFIED_SIZE;
+ }
+ if (startImageRequest) {
+ imageView.setImageResourceId(getImageRequestDescriptorForAttachment(attachmentData,
+ maxWidth, maxHeight));
+ }
+ imageView.setContentDescription(
+ parent.getResources().getString(R.string.message_image_content_description));
+ return view;
+ }
+
+ private static View createPendingAttachmentPreview(final LayoutInflater layoutInflater,
+ final ViewGroup parent, final PendingAttachmentData attachmentData) {
+ final View pendingItemView = layoutInflater.inflate(R.layout.attachment_pending_item,
+ parent, false);
+ final ImageView imageView = (ImageView)
+ pendingItemView.findViewById(R.id.pending_item_view);
+ final ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
+ final int defaultSize = layoutInflater.getContext().getResources().getDimensionPixelSize(
+ R.dimen.pending_attachment_size);
+ layoutParams.width = attachmentData.getWidth() == MessagePartData.UNSPECIFIED_SIZE ?
+ defaultSize : attachmentData.getWidth();
+ layoutParams.height = attachmentData.getHeight() == MessagePartData.UNSPECIFIED_SIZE ?
+ defaultSize : attachmentData.getHeight();
+ return pendingItemView;
+ }
+
+ private static View createVCardPreview(final LayoutInflater layoutInflater,
+ final MessagePartData attachmentData, final ViewGroup parent,
+ final int viewType) {
+ int layoutId = R.layout.attachment_single_vcard;
+ switch (viewType) {
+ case AttachmentPreviewFactory.TYPE_SINGLE:
+ layoutId = R.layout.attachment_single_vcard;
+ break;
+ case AttachmentPreviewFactory.TYPE_MULTIPLE:
+ layoutId = R.layout.attachment_multiple_vcard;
+ break;
+ case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
+ layoutId = R.layout.attachment_chooser_vcard;
+ break;
+ default:
+ Assert.fail("unsupported attachment view type!");
+ break;
+ }
+ final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
+ final PersonItemView vcardPreview = (PersonItemView) view.findViewById(
+ R.id.vcard_attachment_view);
+ vcardPreview.setAvatarOnly(viewType != AttachmentPreviewFactory.TYPE_SINGLE);
+ vcardPreview.bind(DataModel.get().createVCardContactItemData(layoutInflater.getContext(),
+ attachmentData));
+ vcardPreview.setListener(new PersonItemViewListener() {
+ @Override
+ public void onPersonClicked(final PersonItemData data) {
+ Assert.isTrue(data instanceof VCardContactItemData);
+ final VCardContactItemData vCardData = (VCardContactItemData) data;
+ if (vCardData.hasValidVCard()) {
+ final Uri vCardUri = vCardData.getVCardUri();
+ UIIntents.get().launchVCardDetailActivity(vcardPreview.getContext(), vCardUri);
+ }
+ }
+
+ @Override
+ public boolean onPersonLongClicked(final PersonItemData data) {
+ return false;
+ }
+ });
+ return view;
+ }
+
+ private static View createAudioPreview(final LayoutInflater layoutInflater,
+ final MessagePartData attachmentData, final ViewGroup parent,
+ final int viewType) {
+ int layoutId = R.layout.attachment_single_audio;
+ switch (viewType) {
+ case AttachmentPreviewFactory.TYPE_SINGLE:
+ layoutId = R.layout.attachment_single_audio;
+ break;
+ case AttachmentPreviewFactory.TYPE_MULTIPLE:
+ layoutId = R.layout.attachment_multiple_audio;
+ break;
+ case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
+ layoutId = R.layout.attachment_chooser_audio;
+ break;
+ default:
+ Assert.fail("unsupported attachment view type!");
+ break;
+ }
+ final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
+ final AudioAttachmentView audioView = (AudioAttachmentView)
+ view.findViewById(R.id.audio_attachment_view);
+ audioView.bindMessagePartData(attachmentData, false /* incoming */);
+ return view;
+ }
+
+ private static View createVideoPreview(final LayoutInflater layoutInflater,
+ final MessagePartData attachmentData, final ViewGroup parent,
+ final int viewType) {
+ int layoutId = R.layout.attachment_single_video;
+ switch (viewType) {
+ case AttachmentPreviewFactory.TYPE_SINGLE:
+ layoutId = R.layout.attachment_single_video;
+ break;
+ case AttachmentPreviewFactory.TYPE_MULTIPLE:
+ layoutId = R.layout.attachment_multiple_video;
+ break;
+ case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
+ layoutId = R.layout.attachment_chooser_video;
+ break;
+ default:
+ Assert.fail("unsupported attachment view type!");
+ break;
+ }
+ final VideoThumbnailView videoThumbnail = (VideoThumbnailView) layoutInflater.inflate(
+ layoutId, parent, false /* attachToRoot */);
+ videoThumbnail.setSource(attachmentData, false /* incomingMessage */);
+ return videoThumbnail;
+ }
+}
diff --git a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java b/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java
new file mode 100644
index 0000000..724c4fe
--- /dev/null
+++ b/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.ViewSwitcher;
+
+import com.android.messaging.R;
+
+/**
+ * Shows a tinted play pause button.
+ */
+public class AudioAttachmentPlayPauseButton extends ViewSwitcher {
+ private ImageView mPlayButton;
+ private ImageView mPauseButton;
+
+ private boolean mShowAsIncoming;
+
+ public AudioAttachmentPlayPauseButton(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mPlayButton = (ImageView) findViewById(R.id.play_button);
+ mPauseButton = (ImageView) findViewById(R.id.pause_button);
+ updateAppearance();
+ }
+
+ public void setVisualStyle(final boolean showAsIncoming) {
+ if (mShowAsIncoming != showAsIncoming) {
+ mShowAsIncoming = showAsIncoming;
+ updateAppearance();
+ }
+ }
+
+ private void updateAppearance() {
+ mPlayButton.setImageDrawable(ConversationDrawables.get()
+ .getPlayButtonDrawable(mShowAsIncoming));
+ mPauseButton.setImageDrawable(ConversationDrawables.get()
+ .getPauseButtonDrawable(mShowAsIncoming));
+ }
+}
diff --git a/src/com/android/messaging/ui/AudioAttachmentView.java b/src/com/android/messaging/ui/AudioAttachmentView.java
new file mode 100644
index 0000000..bb649b0
--- /dev/null
+++ b/src/com/android/messaging/ui/AudioAttachmentView.java
@@ -0,0 +1,329 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.ui.mediapicker.PausableChronometer;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * A reusable widget that hosts an audio player for audio attachment playback. This widget is used
+ * by both the media picker and the conversation message view to show audio attachments.
+ */
+public class AudioAttachmentView extends LinearLayout {
+ /** The normal layout mode where we have the play button, timer and progress bar */
+ private static final int LAYOUT_MODE_NORMAL = 0;
+
+ /** The compact layout mode with only the play button and the timer beneath it. Suitable
+ * for displaying in limited space such as multi-attachment layout */
+ private static final int LAYOUT_MODE_COMPACT = 1;
+
+ /** The sub-compact layout mode with only the play button. */
+ private static final int LAYOUT_MODE_SUB_COMPACT = 2;
+
+ private static final int PLAY_BUTTON = 0;
+ private static final int PAUSE_BUTTON = 1;
+
+ private AudioAttachmentPlayPauseButton mPlayPauseButton;
+ private PausableChronometer mChronometer;
+ private AudioPlaybackProgressBar mProgressBar;
+ private MediaPlayer mMediaPlayer;
+
+ private Uri mDataSourceUri;
+
+ // The corner radius for drawing rounded corners. The default value is zero (no rounded corners)
+ private final int mCornerRadius;
+ private final Path mRoundedCornerClipPath;
+ private int mClipPathWidth;
+ private int mClipPathHeight;
+
+ // Indicates whether the attachment view is to be styled as a part of an incoming message.
+ private boolean mShowAsIncoming;
+
+ private boolean mPrepared;
+ private boolean mPlaybackFinished;
+ private final int mMode;
+
+ public AudioAttachmentView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ final TypedArray typedAttributes =
+ context.obtainStyledAttributes(attrs, R.styleable.AudioAttachmentView);
+ mMode = typedAttributes.getInt(R.styleable.AudioAttachmentView_layoutMode,
+ LAYOUT_MODE_NORMAL);
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ inflater.inflate(R.layout.audio_attachment_view, this, true);
+ typedAttributes.recycle();
+
+ setWillNotDraw(mMode != LAYOUT_MODE_SUB_COMPACT);
+ mRoundedCornerClipPath = new Path();
+ mCornerRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.conversation_list_image_preview_corner_radius);
+ setContentDescription(context.getString(R.string.audio_attachment_content_description));
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mPlayPauseButton = (AudioAttachmentPlayPauseButton) findViewById(R.id.play_pause_button);
+ mChronometer = (PausableChronometer) findViewById(R.id.timer);
+ mProgressBar = (AudioPlaybackProgressBar) findViewById(R.id.progress);
+ mPlayPauseButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ setupMediaPlayer();
+ if (mMediaPlayer != null && mPrepared) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ mChronometer.pause();
+ mProgressBar.pause();
+ } else {
+ playAudio();
+ }
+ }
+ updatePlayPauseButtonState();
+ }
+ });
+ updatePlayPauseButtonState();
+ initializeViewsForMode();
+ }
+
+ /**
+ * Bind the audio attachment view with a MessagePartData.
+ * @param incoming indicates whether the attachment view is to be styled as a part of an
+ * incoming message.
+ */
+ public void bindMessagePartData(final MessagePartData messagePartData,
+ final boolean incoming) {
+ Assert.isTrue(messagePartData == null ||
+ ContentType.isAudioType(messagePartData.getContentType()));
+ final Uri contentUri = (messagePartData == null) ? null : messagePartData.getContentUri();
+ bind(contentUri, incoming);
+ }
+
+ public void bind(final Uri dataSourceUri, final boolean incoming) {
+ final String currentUriString = (mDataSourceUri == null) ? "" : mDataSourceUri.toString();
+ final String newUriString = (dataSourceUri == null) ? "" : dataSourceUri.toString();
+ mShowAsIncoming = incoming;
+ if (!TextUtils.equals(currentUriString, newUriString)) {
+ mDataSourceUri = dataSourceUri;
+ resetToZeroState();
+ }
+ }
+
+ private void playAudio() {
+ Assert.notNull(mMediaPlayer);
+ if (mPlaybackFinished) {
+ mMediaPlayer.seekTo(0);
+ mChronometer.restart();
+ mProgressBar.restart();
+ mPlaybackFinished = false;
+ } else {
+ mChronometer.resume();
+ mProgressBar.resume();
+ }
+ mMediaPlayer.start();
+ }
+
+ private void onAudioReplayError(final int what, final int extra, final Exception exception) {
+ if (exception == null) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, what=" + what +
+ ", extra=" + extra);
+ } else {
+ LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, exception=" + exception);
+ }
+ UiUtils.showToastAtBottom(R.string.audio_recording_replay_failed);
+ releaseMediaPlayer();
+ }
+
+ private void setupMediaPlayer() {
+ Assert.notNull(mDataSourceUri);
+ if (mMediaPlayer == null) {
+ Assert.isTrue(!mPrepared);
+ mMediaPlayer = new MediaPlayer();
+ try {
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri);
+ mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ @Override
+ public void onCompletion(final MediaPlayer mp) {
+ updatePlayPauseButtonState();
+ mChronometer.reset();
+ mChronometer.setBase(SystemClock.elapsedRealtime() -
+ mMediaPlayer.getDuration());
+ mProgressBar.reset();
+
+ mPlaybackFinished = true;
+ }
+ });
+
+ mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
+ @Override
+ public void onPrepared(final MediaPlayer mp) {
+ // Set base on the chronometer so we can show the full length of the audio.
+ mChronometer.setBase(SystemClock.elapsedRealtime() -
+ mMediaPlayer.getDuration());
+ mProgressBar.setDuration(mMediaPlayer.getDuration());
+ mMediaPlayer.seekTo(0);
+ mPrepared = true;
+ }
+ });
+
+ mMediaPlayer.setOnErrorListener(new OnErrorListener() {
+ @Override
+ public boolean onError(final MediaPlayer mp, final int what, final int extra) {
+ onAudioReplayError(what, extra, null);
+ return true;
+ }
+ });
+ mMediaPlayer.prepareAsync();
+ } catch (final Exception exception) {
+ onAudioReplayError(0, 0, exception);
+ releaseMediaPlayer();
+ }
+ }
+ }
+
+ private void releaseMediaPlayer() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ mPrepared = false;
+ mPlaybackFinished = false;
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ releaseMediaPlayer();
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ if (mMode != LAYOUT_MODE_SUB_COMPACT) {
+ return;
+ }
+
+ final int currentWidth = this.getWidth();
+ final int currentHeight = this.getHeight();
+ if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
+ final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
+ mRoundedCornerClipPath.reset();
+ mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
+ Path.Direction.CW);
+ mClipPathWidth = currentWidth;
+ mClipPathHeight = currentHeight;
+ }
+
+ canvas.clipPath(mRoundedCornerClipPath);
+ super.onDraw(canvas);
+ }
+
+ private void updatePlayPauseButtonState() {
+ if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
+ mPlayPauseButton.setDisplayedChild(PLAY_BUTTON);
+ } else {
+ mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON);
+ }
+ }
+
+ private void resetToZeroState() {
+ // Release the media player so it may be set up with the new audio source.
+ releaseMediaPlayer();
+ mChronometer.reset();
+ mProgressBar.reset();
+ updateVisualStyle();
+
+ if (mDataSourceUri != null) {
+ // Re-ensure the media player, so we can read the duration of the audio.
+ setupMediaPlayer();
+ }
+ }
+
+ private void updateVisualStyle() {
+ if (mMode == LAYOUT_MODE_SUB_COMPACT) {
+ // Sub-compact mode has static visual appearance already set up during initialization.
+ return;
+ }
+
+ if (mShowAsIncoming) {
+ mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming));
+ } else {
+ mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing));
+ }
+ mProgressBar.setVisualStyle(mShowAsIncoming);
+ mPlayPauseButton.setVisualStyle(mShowAsIncoming);
+ updatePlayPauseButtonState();
+ }
+
+ private void initializeViewsForMode() {
+ switch (mMode) {
+ case LAYOUT_MODE_NORMAL:
+ setOrientation(HORIZONTAL);
+ mProgressBar.setVisibility(VISIBLE);
+ break;
+
+ case LAYOUT_MODE_COMPACT:
+ setOrientation(VERTICAL);
+ mProgressBar.setVisibility(GONE);
+ ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0);
+ ((MarginLayoutParams) mChronometer.getLayoutParams()).setMargins(0, 0, 0, 0);
+ break;
+
+ case LAYOUT_MODE_SUB_COMPACT:
+ setOrientation(VERTICAL);
+ mProgressBar.setVisibility(GONE);
+ mChronometer.setVisibility(GONE);
+ ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0);
+ final ImageView playButton = (ImageView) findViewById(R.id.play_button);
+ playButton.setImageDrawable(
+ getResources().getDrawable(R.drawable.ic_preview_play));
+ final ImageView pauseButton = (ImageView) findViewById(R.id.pause_button);
+ pauseButton.setImageDrawable(
+ getResources().getDrawable(R.drawable.ic_preview_pause));
+ break;
+
+ default:
+ Assert.fail("Unsupported mode for AudioAttachmentView!");
+ break;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java b/src/com/android/messaging/ui/AudioPlaybackProgressBar.java
new file mode 100644
index 0000000..a5b3a57
--- /dev/null
+++ b/src/com/android/messaging/ui/AudioPlaybackProgressBar.java
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+import android.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.content.Context;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.ProgressBar;
+
+/**
+ * Shows a styled progress bar that is synchronized with the playback state of an audio attachment.
+ */
+public class AudioPlaybackProgressBar extends ProgressBar implements PlaybackStateView {
+ private long mDurationInMillis;
+ private final TimeAnimator mUpdateAnimator;
+ private long mCumulativeTime = 0;
+ private long mCurrentPlayStartTime = 0;
+ private boolean mIncoming = false;
+
+ public AudioPlaybackProgressBar(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ mUpdateAnimator = new TimeAnimator();
+ mUpdateAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+ mUpdateAnimator.setTimeListener(new TimeListener() {
+ @Override
+ public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
+ final long deltaTime) {
+ int progress = 0;
+ if (mDurationInMillis > 0) {
+ progress = (int) (((mCumulativeTime + SystemClock.elapsedRealtime() -
+ mCurrentPlayStartTime) * 1.0f / mDurationInMillis) * 100);
+ progress = Math.max(Math.min(progress, 100), 0);
+ }
+ setProgress(progress);
+ }
+ });
+ updateAppearance();
+ }
+
+ /**
+ * Sets the duration of the audio that's being played, in milliseconds.
+ */
+ public void setDuration(final long durationInMillis) {
+ mDurationInMillis = durationInMillis;
+ }
+
+ @Override
+ public void restart() {
+ reset();
+ resume();
+ }
+
+ @Override
+ public void reset() {
+ stopUpdateTicks();
+ setProgress(0);
+ mCumulativeTime = 0;
+ mCurrentPlayStartTime = 0;
+ }
+
+ @Override
+ public void resume() {
+ mCurrentPlayStartTime = SystemClock.elapsedRealtime();
+ startUpdateTicks();
+ }
+
+ @Override
+ public void pause() {
+ mCumulativeTime += SystemClock.elapsedRealtime() - mCurrentPlayStartTime;
+ stopUpdateTicks();
+ }
+
+ private void startUpdateTicks() {
+ if (!mUpdateAnimator.isStarted()) {
+ mUpdateAnimator.start();
+ }
+ }
+
+ private void stopUpdateTicks() {
+ if (mUpdateAnimator.isStarted()) {
+ mUpdateAnimator.end();
+ }
+ }
+
+ private void updateAppearance() {
+ final Drawable drawable =
+ ConversationDrawables.get().getAudioProgressDrawable(mIncoming);
+ final ClipDrawable clipDrawable = new ClipDrawable(drawable, Gravity.START,
+ ClipDrawable.HORIZONTAL);
+ setProgressDrawable(clipDrawable);
+ setBackground(ConversationDrawables.get()
+ .getAudioProgressBackgroundDrawable(mIncoming));
+ }
+
+ public void setVisualStyle(final boolean incoming) {
+ if (mIncoming != incoming) {
+ mIncoming = incoming;
+ updateAppearance();
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/BaseBugleActivity.java b/src/com/android/messaging/ui/BaseBugleActivity.java
new file mode 100644
index 0000000..1236282
--- /dev/null
+++ b/src/com/android/messaging/ui/BaseBugleActivity.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.messaging.util.BugleActivityUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Base class for app activities that would normally derive from Activity. Responsible for
+ * ensuring app requirements are met during onResume()
+ */
+public class BaseBugleActivity extends Activity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
+ return;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
+ BugleActivityUtil.onActivityResume(this, BaseBugleActivity.this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
+ }
+}
diff --git a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java b/src/com/android/messaging/ui/BaseBugleFragmentActivity.java
new file mode 100644
index 0000000..947970f
--- /dev/null
+++ b/src/com/android/messaging/ui/BaseBugleFragmentActivity.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import android.app.Activity;
+
+import com.android.messaging.util.BugleActivityUtil;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Base class for app activities that would normally derive from FragmentActivity. Responsible for
+ * ensuring app requirements are met during onResume()
+ */
+public class BaseBugleFragmentActivity extends Activity {
+ @Override
+ protected void onResume() {
+ super.onResume();
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
+ // Ensure we have a sufficient version of Google Play Services, prompting for upgrade and
+ // disabling the data updates if we don't have the correct version.
+ BugleActivityUtil.onActivityResume(this, BaseBugleFragmentActivity.this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
+ }
+}
diff --git a/src/com/android/messaging/ui/BasePagerViewHolder.java b/src/com/android/messaging/ui/BasePagerViewHolder.java
new file mode 100644
index 0000000..a82ecce
--- /dev/null
+++ b/src/com/android/messaging/ui/BasePagerViewHolder.java
@@ -0,0 +1,106 @@
+/*
+ * 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;
+
+import android.os.Parcelable;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * The base pager view holder implementation that handles basic view creation/destruction logic,
+ * as well as logic to save/restore instance state that's persisted not only for activity
+ * reconstruction (e.g. during a configuration change), but also cases where the individual
+ * page view gets destroyed and recreated.
+ *
+ * To opt into saving/restoring instance state behavior for a particular page view, let the
+ * PageView implement PersistentInstanceState to save and restore instance states to/from a
+ * Parcelable.
+ */
+public abstract class BasePagerViewHolder implements PagerViewHolder {
+ protected View mView;
+ protected Parcelable mSavedState;
+
+ /**
+ * This is called when the entire view pager is being torn down (due to configuration change
+ * for example) that will be restored later.
+ */
+ @Override
+ public Parcelable saveState() {
+ savePendingState();
+ return mSavedState;
+ }
+
+ /**
+ * This is called when the view pager is being restored.
+ */
+ @Override
+ public void restoreState(final Parcelable restoredState) {
+ if (restoredState != null) {
+ mSavedState = restoredState;
+ // If the view is already there, let it restore the state. Otherwise, it will be
+ // restored the next time the view gets created.
+ restorePendingState();
+ }
+ }
+
+ @Override
+ public void resetState() {
+ mSavedState = null;
+ if (mView != null && (mView instanceof PersistentInstanceState)) {
+ ((PersistentInstanceState) mView).resetState();
+ }
+ }
+
+ /**
+ * This is called when the view itself is being torn down. This may happen when the user
+ * has flipped to another page in the view pager, so we want to save the current state if
+ * possible.
+ */
+ @Override
+ public View destroyView() {
+ savePendingState();
+ final View retView = mView;
+ mView = null;
+ return retView;
+ }
+
+ @Override
+ public View getView(ViewGroup container) {
+ if (mView == null) {
+ mView = createView(container);
+ // When initially created, check if the view has any saved state. If so restore it.
+ restorePendingState();
+ }
+ return mView;
+ }
+
+ private void savePendingState() {
+ if (mView != null && (mView instanceof PersistentInstanceState)) {
+ mSavedState = ((PersistentInstanceState) mView).saveState();
+ }
+ }
+
+ private void restorePendingState() {
+ if (mView != null && (mView instanceof PersistentInstanceState) && (mSavedState != null)) {
+ ((PersistentInstanceState) mView).restoreState(mSavedState);
+ }
+ }
+
+ /**
+ * Create and initialize a new page view.
+ */
+ protected abstract View createView(ViewGroup container);
+}
diff --git a/src/com/android/messaging/ui/BlockedParticipantListItemView.java b/src/com/android/messaging/ui/BlockedParticipantListItemView.java
new file mode 100644
index 0000000..2ccdebf
--- /dev/null
+++ b/src/com/android/messaging/ui/BlockedParticipantListItemView.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.support.v4.text.BidiFormatter;
+import android.support.v4.text.TextDirectionHeuristicsCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantListItemData;
+
+/**
+ * View for individual participant in blocked participants list.
+ *
+ * Unblocks participant when clicked.
+ */
+public class BlockedParticipantListItemView extends LinearLayout {
+ private TextView mNameTextView;
+ private ContactIconView mContactIconView;
+ private ParticipantListItemData mData;
+
+ public BlockedParticipantListItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mNameTextView = (TextView) findViewById(R.id.name);
+ mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mData.unblock(getContext());
+ }
+ });
+ }
+
+ public void bind(final ParticipantListItemData data) {
+ mData = data;
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ mNameTextView.setText(bidiFormatter.unicodeWrap(
+ data.getDisplayName(), TextDirectionHeuristicsCompat.LTR));
+ mContactIconView.setImageResourceUri(data.getAvatarUri(), data.getContactId(),
+ data.getLookupKey(), data.getNormalizedDestination());
+ mNameTextView.setText(data.getDisplayName());
+ }
+}
diff --git a/src/com/android/messaging/ui/BlockedParticipantsActivity.java b/src/com/android/messaging/ui/BlockedParticipantsActivity.java
new file mode 100644
index 0000000..b740264
--- /dev/null
+++ b/src/com/android/messaging/ui/BlockedParticipantsActivity.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+
+/**
+ * Show a list of currently blocked participants.
+ */
+public class BlockedParticipantsActivity extends BugleActionBarActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.blocked_participants_activity);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ Assert.isTrue(fragment instanceof BlockedParticipantsFragment);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // Treat the home press as back press so that when we go back to
+ // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
+ onBackPressed();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/BlockedParticipantsFragment.java b/src/com/android/messaging/ui/BlockedParticipantsFragment.java
new file mode 100644
index 0000000..3ab54ab
--- /dev/null
+++ b/src/com/android/messaging/ui/BlockedParticipantsFragment.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.BlockedParticipantsData;
+import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.util.Assert;
+
+/**
+ * Show a list of currently blocked participants.
+ */
+public class BlockedParticipantsFragment extends Fragment
+ implements BlockedParticipantsDataListener {
+ private ListView mListView;
+ private BlockedParticipantListAdapter mAdapter;
+ private final Binding<BlockedParticipantsData> mBinding =
+ BindingBase.createBinding(this);
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View view =
+ inflater.inflate(R.layout.blocked_participants_fragment, container, false);
+ mListView = (ListView) view.findViewById(android.R.id.list);
+ mAdapter = new BlockedParticipantListAdapter(getActivity(), null);
+ mListView.setAdapter(mAdapter);
+ mBinding.bind(DataModel.get().createBlockedParticipantsData(getActivity(), this));
+ mBinding.getData().init(getLoaderManager(), mBinding);
+ return view;
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mBinding.unbind();
+ }
+
+ /**
+ * An adapter to display ParticipantListItemView based on ParticipantData.
+ */
+ private class BlockedParticipantListAdapter extends CursorAdapter {
+ public BlockedParticipantListAdapter(final Context context, final Cursor cursor) {
+ super(context, cursor, 0);
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.blocked_participant_list_item_view, parent, false);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ Assert.isTrue(view instanceof BlockedParticipantListItemView);
+ ((BlockedParticipantListItemView) view).bind(
+ mBinding.getData().createParticipantListItemData(cursor));
+ }
+ }
+
+ @Override
+ public void onBlockedParticipantsCursorUpdated(Cursor cursor) {
+ mAdapter.swapCursor(cursor);
+ }
+}
diff --git a/src/com/android/messaging/ui/BugleActionBarActivity.java b/src/com/android/messaging/ui/BugleActionBarActivity.java
new file mode 100644
index 0000000..80dabb8
--- /dev/null
+++ b/src/com/android/messaging/ui/BugleActionBarActivity.java
@@ -0,0 +1,356 @@
+/*
+ * 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;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.ActionMode;
+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.util.BugleActivityUtil;
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Base class for app activities that use an action bar. Responsible for logging/telemetry/other
+ * needs that will be common for all activities. We can break out the common code if/when we need
+ * a version that doesn't use an actionbar.
+ */
+public class BugleActionBarActivity extends ActionBarActivity implements ImeUtil.ImeStateHost {
+ // Tracks the list of observers opting in for IME state change.
+ private final Set<ImeUtil.ImeStateObserver> mImeStateObservers = new HashSet<>();
+
+ // Tracks the soft keyboard display state
+ private boolean mImeOpen;
+
+ // The ActionMode that represents the modal contextual action bar, using our own implementation
+ // rather than the built in contextual action bar to reduce jank
+ private CustomActionMode mActionMode;
+
+ // The menu for the action bar
+ private Menu mActionBarMenu;
+
+ // Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the
+ // device
+ private int mLastScreenHeight;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
+ return;
+ }
+
+ mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart");
+ }
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onStop();
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart");
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
+ }
+ BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop");
+ }
+ }
+
+ private boolean mDestroyed;
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDestroyed = true;
+ }
+
+ public boolean getIsDestroyed() {
+ return mDestroyed;
+ }
+
+ @Override
+ public void onDisplayHeightChanged(final int heightSpecification) {
+ int screenHeight = getResources().getDisplayMetrics().heightPixels;
+
+ if (screenHeight != mLastScreenHeight) {
+ // Appears to be an orientation change, don't fire ime updates
+ mLastScreenHeight = screenHeight;
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
+ " screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight +
+ " Skipped, appears to be orientation change.");
+ return;
+ }
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null && actionBar.isShowing()) {
+ screenHeight -= actionBar.getHeight();
+ }
+ final int height = View.MeasureSpec.getSize(heightSpecification);
+
+ final boolean imeWasOpen = mImeOpen;
+ mImeOpen = screenHeight - height > 100;
+
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
+ "imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " +
+ screenHeight + " height: " + height);
+ }
+
+ if (imeWasOpen != mImeOpen) {
+ for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) {
+ observer.onImeStateChanged(mImeOpen);
+ }
+ }
+ }
+
+ @Override
+ public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) {
+ mImeStateObservers.add(observer);
+ }
+
+ @Override
+ public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) {
+ mImeStateObservers.remove(observer);
+ }
+
+ @Override
+ public boolean isImeOpen() {
+ return mImeOpen;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ mActionBarMenu = menu;
+ if (mActionMode != null &&
+ mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(final Menu menu) {
+ mActionBarMenu = menu;
+ if (mActionMode != null &&
+ mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) {
+ return true;
+ }
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ if (mActionMode != null &&
+ mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) {
+ return true;
+ }
+
+ switch (menuItem.getItemId()) {
+ case android.R.id.home:
+ if (mActionMode != null) {
+ dismissActionMode();
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(menuItem);
+ }
+
+ @Override
+ public ActionMode startActionMode(final ActionMode.Callback callback) {
+ mActionMode = new CustomActionMode(callback);
+ supportInvalidateOptionsMenu();
+ invalidateActionBar();
+ return mActionMode;
+ }
+
+ public void dismissActionMode() {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ mActionMode = null;
+ invalidateActionBar();
+ }
+ }
+
+ public ActionMode getActionMode() {
+ return mActionMode;
+ }
+
+ protected ActionMode.Callback getActionModeCallback() {
+ if (mActionMode == null) {
+ return null;
+ }
+
+ return mActionMode.getCallback();
+ }
+
+ /**
+ * Receives and handles action bar invalidation request from sub-components of this activity.
+ *
+ * <p>Normally actions have sole control over the action bar, but in order to support seamless
+ * transitions for components such as the full screen media picker, we have to let it take over
+ * the action bar and then restore its state afterwards</p>
+ *
+ * <p>If a fragment does anything that may change the action bar, it should call this method
+ * and then it is this method's responsibility to figure out which component "controls" the
+ * action bar and delegate the updating of the action bar to that component</p>
+ */
+ public final void invalidateActionBar() {
+ if (mActionMode != null) {
+ mActionMode.updateActionBar(getSupportActionBar());
+ } else {
+ updateActionBar(getSupportActionBar());
+ }
+ }
+
+ protected void updateActionBar(final ActionBar actionBar) {
+ actionBar.setHomeAsUpIndicator(null);
+ }
+
+ /**
+ * Custom ActionMode implementation which allows us to just replace the contents of the main
+ * action bar rather than overlay over it
+ */
+ private class CustomActionMode extends ActionMode {
+ private CharSequence mTitle;
+ private CharSequence mSubtitle;
+ private View mCustomView;
+ private final Callback mCallback;
+
+ public CustomActionMode(final Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void setTitle(final CharSequence title) {
+ mTitle = title;
+ }
+
+ @Override
+ public void setTitle(final int resId) {
+ mTitle = getResources().getString(resId);
+ }
+
+ @Override
+ public void setSubtitle(final CharSequence subtitle) {
+ mSubtitle = subtitle;
+ }
+
+ @Override
+ public void setSubtitle(final int resId) {
+ mSubtitle = getResources().getString(resId);
+ }
+
+ @Override
+ public void setCustomView(final View view) {
+ mCustomView = view;
+ }
+
+ @Override
+ public void invalidate() {
+ invalidateActionBar();
+ }
+
+ @Override
+ public void finish() {
+ mActionMode = null;
+ mCallback.onDestroyActionMode(this);
+ supportInvalidateOptionsMenu();
+ invalidateActionBar();
+ }
+
+ @Override
+ public Menu getMenu() {
+ return mActionBarMenu;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return mSubtitle;
+ }
+
+ @Override
+ public View getCustomView() {
+ return mCustomView;
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return BugleActionBarActivity.this.getMenuInflater();
+ }
+
+ public Callback getCallback() {
+ return mCallback;
+ }
+
+ public void updateActionBar(final ActionBar actionBar) {
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP);
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayShowCustomEnabled(false);
+ mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu);
+ actionBar.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(R.color.contextual_action_bar_background_color)));
+ actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark);
+ actionBar.show();
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/BugleAnimationTags.java b/src/com/android/messaging/ui/BugleAnimationTags.java
new file mode 100644
index 0000000..b141f5b
--- /dev/null
+++ b/src/com/android/messaging/ui/BugleAnimationTags.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+
+/**
+ * Defines a list of animation tags used as android:transitionName properties used for L's
+ * hero transitions.
+ */
+public class BugleAnimationTags {
+ /**
+ * The tag for the FAB in the conversation list activity.
+ */
+ public static final String TAG_FABICON = "bugle:fabicon";
+
+ /**
+ * The tag for the content view of a conversation list item view.
+ */
+ public static final String TAG_CLIVCONTENT = "bugle:clivcontent";
+
+ /**
+ * The tag for the action bar.
+ */
+ public static final String TAG_ACTIONBAR = "bugle:actionbar";
+}
diff --git a/src/com/android/messaging/ui/ClassZeroActivity.java b/src/com/android/messaging/ui/ClassZeroActivity.java
new file mode 100644
index 0000000..129ec19
--- /dev/null
+++ b/src/com/android/messaging/ui/ClassZeroActivity.java
@@ -0,0 +1,205 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Telephony.Sms;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Window;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
+import com.android.messaging.util.Assert;
+
+import java.util.ArrayList;
+
+/**
+ * Display a class-zero SMS message to the user. Wait for the user to dismiss
+ * it.
+ */
+public class ClassZeroActivity extends Activity {
+ private static final boolean VERBOSE = false;
+ private static final String TAG = "display_00";
+ private static final int ON_AUTO_SAVE = 1;
+
+ /** Default timer to dismiss the dialog. */
+ private static final long DEFAULT_TIMER = 5 * 60 * 1000;
+
+ /** To remember the exact time when the timer should fire. */
+ private static final String TIMER_FIRE = "timer_fire";
+
+ private ContentValues mMessageValues = null;
+
+ /** Is the message read. */
+ private boolean mRead = false;
+
+ /** The timer to dismiss the dialog automatically. */
+ private long mTimerSet = 0;
+ private AlertDialog mDialog = null;
+
+ private ArrayList<ContentValues> mMessageQueue = null;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(final Message msg) {
+ // Do not handle an invalid message.
+ if (msg.what == ON_AUTO_SAVE) {
+ mRead = false;
+ mDialog.dismiss();
+ saveMessage();
+ processNextMessage();
+ }
+ }
+ };
+
+ private boolean queueMsgFromIntent(final Intent msgIntent) {
+ final ContentValues messageValues =
+ msgIntent.getParcelableExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_VALUES);
+ // that takes the format argument is a hidden API right now.
+ final String message = messageValues.getAsString(Sms.BODY);
+ if (TextUtils.isEmpty(message)) {
+ if (mMessageQueue.size() == 0) {
+ finish();
+ }
+ return false;
+ }
+ mMessageQueue.add(messageValues);
+ return true;
+ }
+
+ private void processNextMessage() {
+ if (mMessageQueue.size() > 0) {
+ mMessageQueue.remove(0);
+ }
+ if (mMessageQueue.size() == 0) {
+ finish();
+ } else {
+ displayZeroMessage(mMessageQueue.get(0));
+ }
+ }
+
+ private void saveMessage() {
+ mMessageValues.put(Sms.Inbox.READ, mRead ? Integer.valueOf(1) : Integer.valueOf(0));
+ final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(mMessageValues);
+ action.start();
+ }
+
+ @Override
+ protected void onCreate(final Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ if (mMessageQueue == null) {
+ mMessageQueue = new ArrayList<ContentValues>();
+ }
+ if (!queueMsgFromIntent(getIntent())) {
+ return;
+ }
+ Assert.isTrue(mMessageQueue.size() == 1);
+ if (mMessageQueue.size() == 1) {
+ displayZeroMessage(mMessageQueue.get(0));
+ }
+
+ if (icicle != null) {
+ mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet);
+ }
+ }
+
+ private void displayZeroMessage(final ContentValues messageValues) {
+ /* This'll be used by the save action */
+ mMessageValues = messageValues;
+ final String message = messageValues.getAsString(Sms.BODY);;
+
+ mDialog = new AlertDialog.Builder(this).setMessage(message)
+ .setPositiveButton(R.string.save, mSaveListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener)
+ .setTitle(R.string.class_0_message_activity)
+ .setCancelable(false).show();
+ final long now = SystemClock.uptimeMillis();
+ mTimerSet = now + DEFAULT_TIMER;
+ }
+
+ @Override
+ protected void onNewIntent(final Intent msgIntent) {
+ // Running with another visible message, queue this one
+ queueMsgFromIntent(msgIntent);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ final long now = SystemClock.uptimeMillis();
+ if (mTimerSet <= now) {
+ // Save the message if the timer already expired.
+ mHandler.sendEmptyMessage(ON_AUTO_SAVE);
+ } else {
+ mHandler.sendEmptyMessageAtTime(ON_AUTO_SAVE, mTimerSet);
+ if (VERBOSE) {
+ Log.d(TAG, "onRestart time = " + Long.toString(mTimerSet) + " "
+ + this.toString());
+ }
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putLong(TIMER_FIRE, mTimerSet);
+ if (VERBOSE) {
+ Log.d(TAG, "onSaveInstanceState time = " + Long.toString(mTimerSet)
+ + " " + this.toString());
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mHandler.removeMessages(ON_AUTO_SAVE);
+ if (VERBOSE) {
+ Log.d(TAG, "onStop time = " + Long.toString(mTimerSet)
+ + " " + this.toString());
+ }
+ }
+
+ private final OnClickListener mCancelListener = new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int whichButton) {
+ dialog.dismiss();
+ processNextMessage();
+ }
+ };
+
+ private final OnClickListener mSaveListener = new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int whichButton) {
+ mRead = true;
+ saveMessage();
+ dialog.dismiss();
+ processNextMessage();
+ }
+ };
+}
diff --git a/src/com/android/messaging/ui/CompositeAdapter.java b/src/com/android/messaging/ui/CompositeAdapter.java
new file mode 100644
index 0000000..620e511
--- /dev/null
+++ b/src/com/android/messaging/ui/CompositeAdapter.java
@@ -0,0 +1,288 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+/**
+ * A general purpose adapter that composes one or more other adapters. It
+ * appends them in the order they are added.
+ */
+public class CompositeAdapter extends BaseAdapter {
+
+ private static final int INITIAL_CAPACITY = 2;
+
+ public static class Partition {
+ boolean mShowIfEmpty;
+ boolean mHasHeader;
+ BaseAdapter mAdapter;
+
+ public Partition(final boolean showIfEmpty, final boolean hasHeader,
+ final BaseAdapter adapter) {
+ this.mShowIfEmpty = showIfEmpty;
+ this.mHasHeader = hasHeader;
+ this.mAdapter = adapter;
+ }
+
+ /**
+ * True if the directory should be shown even if no contacts are found.
+ */
+ public boolean showIfEmpty() {
+ return mShowIfEmpty;
+ }
+
+ public boolean hasHeader() {
+ return mHasHeader;
+ }
+
+ public int getCount() {
+ int count = mAdapter.getCount();
+ if (mHasHeader && (count != 0 || mShowIfEmpty)) {
+ count++;
+ }
+ return count;
+ }
+
+ public BaseAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ public View getHeaderView(final View convertView, final ViewGroup parentView) {
+ return null;
+ }
+
+ public void close() {
+ // do nothing in base class.
+ }
+ }
+
+ private class Observer extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ CompositeAdapter.this.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ CompositeAdapter.this.notifyDataSetInvalidated();
+ }
+ };
+
+ protected final Context mContext;
+ private Partition[] mPartitions;
+ private int mSize = 0;
+ private int mCount = 0;
+ private boolean mCacheValid = true;
+ private final Observer mObserver;
+
+ public CompositeAdapter(final Context context) {
+ mContext = context;
+ mObserver = new Observer();
+ mPartitions = new Partition[INITIAL_CAPACITY];
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public void addPartition(final Partition partition) {
+ if (mSize >= mPartitions.length) {
+ final int newCapacity = mSize + 2;
+ final Partition[] newAdapters = new Partition[newCapacity];
+ System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
+ mPartitions = newAdapters;
+ }
+ mPartitions[mSize++] = partition;
+ partition.getAdapter().registerDataSetObserver(mObserver);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void removePartition(final int index) {
+ final Partition partition = mPartitions[index];
+ partition.close();
+ System.arraycopy(mPartitions, index + 1, mPartitions, index,
+ mSize - index - 1);
+ mSize--;
+ partition.getAdapter().unregisterDataSetObserver(mObserver);
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public void clearPartitions() {
+ for (int i = 0; i < mSize; i++) {
+ final Partition partition = mPartitions[i];
+ partition.close();
+ partition.getAdapter().unregisterDataSetObserver(mObserver);
+ }
+ invalidate();
+ notifyDataSetChanged();
+ }
+
+ public Partition getPartition(final int index) {
+ return mPartitions[index];
+ }
+
+ public int getPartitionAtPosition(final int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mSize; i++) {
+ final int end = start + mPartitions[i].getCount();
+ if (position >= start && position < end) {
+ int offset = position - start;
+ if (mPartitions[i].hasHeader() &&
+ (mPartitions[i].getCount() > 0 || mPartitions[i].showIfEmpty())) {
+ offset--;
+ }
+ if (offset == -1) {
+ return -1;
+ }
+ return i;
+ }
+ start = end;
+ }
+ return mSize - 1;
+ }
+
+ public int getPartitionCount() {
+ return mSize;
+ }
+
+ public void invalidate() {
+ mCacheValid = false;
+ }
+
+ private void ensureCacheValid() {
+ if (mCacheValid) {
+ return;
+ }
+ mCount = 0;
+ for (int i = 0; i < mSize; i++) {
+ mCount += mPartitions[i].getCount();
+ }
+ }
+
+ @Override
+ public int getCount() {
+ ensureCacheValid();
+ return mCount;
+ }
+
+ public int getCount(final int index) {
+ ensureCacheValid();
+ return mPartitions[index].getCount();
+ }
+
+ @Override
+ public Object getItem(final int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mSize; i++) {
+ final int end = start + mPartitions[i].getCount();
+ if (position >= start && position < end) {
+ final int offset = position - start;
+ final Partition partition = mPartitions[i];
+ if (partition.hasHeader() && offset == 0 &&
+ (partition.getCount() > 0 || partition.showIfEmpty())) {
+ // This is the header
+ return null;
+ }
+ return mPartitions[i].getAdapter().getItem(offset);
+ }
+ start = end;
+ }
+
+ return null;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mSize; i++) {
+ final int end = start + mPartitions[i].getCount();
+ if (position >= start && position < end) {
+ final int offset = position - start;
+ final Partition partition = mPartitions[i];
+ if (partition.hasHeader() && offset == 0 &&
+ (partition.getCount() > 0 || partition.showIfEmpty())) {
+ // Header
+ return 0;
+ }
+ return mPartitions[i].getAdapter().getItemId(offset);
+ }
+ start = end;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mSize; i++) {
+ final int end = start + mPartitions[i].getCount();
+ if (position >= start && position < end) {
+ final int offset = position - start;
+ final Partition partition = mPartitions[i];
+ if (partition.hasHeader() && offset == 0 &&
+ (partition.getCount() > 0 || partition.showIfEmpty())) {
+ // This is the header
+ return false;
+ }
+ return true;
+ }
+ start = end;
+ }
+ return true;
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parentView) {
+ ensureCacheValid();
+ int start = 0;
+ for (int i = 0; i < mSize; i++) {
+ final Partition partition = mPartitions[i];
+ final int end = start + partition.getCount();
+ if (position >= start && position < end) {
+ int offset = position - start;
+ View view;
+ if (partition.hasHeader() &&
+ (partition.getCount() > 0 || partition.showIfEmpty())) {
+ offset = offset - 1;
+ }
+ if (offset == -1) {
+ view = partition.getHeaderView(convertView, parentView);
+ } else {
+ view = partition.getAdapter().getView(offset, convertView, parentView);
+ }
+ if (view == null) {
+ throw new NullPointerException("View should not be null, partition: " + i
+ + " position: " + offset);
+ }
+ return view;
+ }
+ start = end;
+ }
+
+ throw new ArrayIndexOutOfBoundsException(position);
+ }
+}
diff --git a/src/com/android/messaging/ui/ContactIconView.java b/src/com/android/messaging/ui/ContactIconView.java
new file mode 100644
index 0000000..44983ab
--- /dev/null
+++ b/src/com/android/messaging/ui/ContactIconView.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
+import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ContactUtil;
+
+/**
+ * A view used to render contact icons. This class derives from AsyncImageView, so it loads contact
+ * icons from MediaResourceManager, and it handles more rendering logic than an AsyncImageView
+ * (draws a circular bitmap).
+ */
+public class ContactIconView extends AsyncImageView {
+ private static final int NORMAL_ICON_SIZE_ID = 0;
+ private static final int LARGE_ICON_SIZE_ID = 1;
+ private static final int SMALL_ICON_SIZE_ID = 2;
+
+ protected final int mIconSize;
+ private final int mColorPressedId;
+
+ private long mContactId;
+ private String mContactLookupKey;
+ private String mNormalizedDestination;
+ private Uri mAvatarUri;
+ private boolean mDisableClickHandler;
+
+ public ContactIconView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ final Resources resources = context.getResources();
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContactIconView);
+
+ final int iconSizeId = a.getInt(R.styleable.ContactIconView_iconSize, 0);
+ switch (iconSizeId) {
+ case NORMAL_ICON_SIZE_ID:
+ mIconSize = (int) resources.getDimension(
+ R.dimen.contact_icon_view_normal_size);
+ break;
+ case LARGE_ICON_SIZE_ID:
+ mIconSize = (int) resources.getDimension(
+ R.dimen.contact_icon_view_large_size);
+ break;
+ case SMALL_ICON_SIZE_ID:
+ mIconSize = (int) resources.getDimension(
+ R.dimen.contact_icon_view_small_size);
+ break;
+ default:
+ // For the compiler, something has to be set even with the assert.
+ mIconSize = 0;
+ Assert.fail("Unsupported ContactIconView icon size attribute");
+ }
+ mColorPressedId = resources.getColor(R.color.contact_avatar_pressed_color);
+
+ setImage(null);
+ a.recycle();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ setColorFilter(mColorPressedId);
+ } else {
+ clearColorFilter();
+ }
+ return super.onTouchEvent(event);
+ }
+
+ /**
+ * Method which allows the automatic hookup of a click handler when the Uri is changed
+ */
+ public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) {
+ mDisableClickHandler = isHandlerDisabled;
+ setOnClickListener(null);
+ setClickable(false);
+ }
+
+ /**
+ * A convenience method that sets the URI of the contact icon by creating a new image request.
+ */
+ public void setImageResourceUri(final Uri uri) {
+ setImageResourceUri(uri, 0, null, null);
+ }
+
+ public void setImageResourceUri(final Uri uri, final long contactId,
+ final String contactLookupKey, final String normalizedDestination) {
+ if (uri == null) {
+ setImageResourceId(null);
+ } else {
+ final String avatarType = AvatarUriUtil.getAvatarType(uri);
+ if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) {
+ setImageResourceId(new AvatarGroupRequestDescriptor(uri, mIconSize, mIconSize));
+ } else {
+ setImageResourceId(new AvatarRequestDescriptor(uri, mIconSize, mIconSize));
+ }
+ }
+
+ mContactId = contactId;
+ mContactLookupKey = contactLookupKey;
+ mNormalizedDestination = normalizedDestination;
+ mAvatarUri = uri;
+
+ maybeInitializeOnClickListener();
+ }
+
+ protected void maybeInitializeOnClickListener() {
+ if ((mContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
+ && !TextUtils.isEmpty(mContactLookupKey)) ||
+ !TextUtils.isEmpty(mNormalizedDestination)) {
+ if (!mDisableClickHandler) {
+ setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ ContactUtil.showOrAddContact(view, mContactId, mContactLookupKey,
+ mAvatarUri, mNormalizedDestination);
+ }
+ });
+ }
+ } else {
+ // This should happen when the phone number is not in the user's contacts or it is a
+ // group conversation, group conversations don't have contact phone numbers. If this
+ // is the case then absorb the click to prevent propagation.
+ setOnClickListener(null);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/ConversationDrawables.java b/src/com/android/messaging/ui/ConversationDrawables.java
new file mode 100644
index 0000000..cf858e2
--- /dev/null
+++ b/src/com/android/messaging/ui/ConversationDrawables.java
@@ -0,0 +1,177 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.util.ImageUtils;
+
+/**
+ * A singleton cache that holds tinted drawable resources for displaying messages, such as
+ * message bubbles, audio attachments etc.
+ */
+public class ConversationDrawables {
+ private static ConversationDrawables sInstance;
+
+ // Cache the color filtered bubble drawables so that we don't need to create a
+ // new one for each ConversationMessageView.
+ private Drawable mIncomingBubbleDrawable;
+ private Drawable mOutgoingBubbleDrawable;
+ private Drawable mIncomingErrorBubbleDrawable;
+ private Drawable mIncomingBubbleNoArrowDrawable;
+ private Drawable mOutgoingBubbleNoArrowDrawable;
+ private Drawable mAudioPlayButtonDrawable;
+ private Drawable mAudioPauseButtonDrawable;
+ private Drawable mIncomingAudioProgressBackgroundDrawable;
+ private Drawable mOutgoingAudioProgressBackgroundDrawable;
+ private Drawable mAudioProgressForegroundDrawable;
+ private Drawable mFastScrollThumbDrawable;
+ private Drawable mFastScrollThumbPressedDrawable;
+ private Drawable mFastScrollPreviewDrawableLeft;
+ private Drawable mFastScrollPreviewDrawableRight;
+ private final Context mContext;
+ private int mOutgoingBubbleColor;
+ private int mIncomingErrorBubbleColor;
+ private int mIncomingAudioButtonColor;
+ private int mSelectedBubbleColor;
+ private int mThemeColor;
+
+ public static ConversationDrawables get() {
+ if (sInstance == null) {
+ sInstance = new ConversationDrawables(Factory.get().getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ private ConversationDrawables(final Context context) {
+ mContext = context;
+ // Pre-create all the drawables.
+ updateDrawables();
+ }
+
+ public int getConversationThemeColor() {
+ return mThemeColor;
+ }
+
+ public void updateDrawables() {
+ final Resources resources = mContext.getResources();
+
+ mIncomingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_incoming);
+ mIncomingBubbleNoArrowDrawable =
+ resources.getDrawable(R.drawable.message_bubble_incoming_no_arrow);
+ mIncomingErrorBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_error);
+ mOutgoingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_outgoing);
+ mOutgoingBubbleNoArrowDrawable =
+ resources.getDrawable(R.drawable.message_bubble_outgoing_no_arrow);
+ mAudioPlayButtonDrawable = resources.getDrawable(R.drawable.ic_audio_play);
+ mAudioPauseButtonDrawable = resources.getDrawable(R.drawable.ic_audio_pause);
+ mIncomingAudioProgressBackgroundDrawable =
+ resources.getDrawable(R.drawable.audio_progress_bar_background_incoming);
+ mOutgoingAudioProgressBackgroundDrawable =
+ resources.getDrawable(R.drawable.audio_progress_bar_background_outgoing);
+ mAudioProgressForegroundDrawable =
+ resources.getDrawable(R.drawable.audio_progress_bar_progress);
+ mFastScrollThumbDrawable = resources.getDrawable(R.drawable.fastscroll_thumb);
+ mFastScrollThumbPressedDrawable =
+ resources.getDrawable(R.drawable.fastscroll_thumb_pressed);
+ mFastScrollPreviewDrawableLeft =
+ resources.getDrawable(R.drawable.fastscroll_preview_left);
+ mFastScrollPreviewDrawableRight =
+ resources.getDrawable(R.drawable.fastscroll_preview_right);
+ mOutgoingBubbleColor = resources.getColor(R.color.message_bubble_color_outgoing);
+ mIncomingErrorBubbleColor =
+ resources.getColor(R.color.message_error_bubble_color_incoming);
+ mIncomingAudioButtonColor =
+ resources.getColor(R.color.message_audio_button_color_incoming);
+ mSelectedBubbleColor = resources.getColor(R.color.message_bubble_color_selected);
+ mThemeColor = resources.getColor(R.color.primary_color);
+ }
+
+ public Drawable getBubbleDrawable(final boolean selected, final boolean incoming,
+ final boolean needArrow, final boolean isError) {
+ final Drawable protoDrawable;
+ if (needArrow) {
+ if (incoming) {
+ protoDrawable = isError && !selected ?
+ mIncomingErrorBubbleDrawable : mIncomingBubbleDrawable;
+ } else {
+ protoDrawable = mOutgoingBubbleDrawable;
+ }
+ } else if (incoming) {
+ protoDrawable = mIncomingBubbleNoArrowDrawable;
+ } else {
+ protoDrawable = mOutgoingBubbleNoArrowDrawable;
+ }
+
+ int color;
+ if (selected) {
+ color = mSelectedBubbleColor;
+ } else if (incoming) {
+ if (isError) {
+ color = mIncomingErrorBubbleColor;
+ } else {
+ color = mThemeColor;
+ }
+ } else {
+ color = mOutgoingBubbleColor;
+ }
+
+ return ImageUtils.getTintedDrawable(mContext, protoDrawable, color);
+ }
+
+ private int getAudioButtonColor(final boolean incoming) {
+ return incoming ? mIncomingAudioButtonColor : mThemeColor;
+ }
+
+ public Drawable getPlayButtonDrawable(final boolean incoming) {
+ return ImageUtils.getTintedDrawable(
+ mContext, mAudioPlayButtonDrawable, getAudioButtonColor(incoming));
+ }
+
+ public Drawable getPauseButtonDrawable(final boolean incoming) {
+ return ImageUtils.getTintedDrawable(
+ mContext, mAudioPauseButtonDrawable, getAudioButtonColor(incoming));
+ }
+
+ public Drawable getAudioProgressDrawable(final boolean incoming) {
+ return ImageUtils.getTintedDrawable(
+ mContext, mAudioProgressForegroundDrawable, getAudioButtonColor(incoming));
+ }
+
+ public Drawable getAudioProgressBackgroundDrawable(final boolean incoming) {
+ return incoming ? mIncomingAudioProgressBackgroundDrawable :
+ mOutgoingAudioProgressBackgroundDrawable;
+ }
+
+ public Drawable getFastScrollThumbDrawable(final boolean pressed) {
+ if (pressed) {
+ return ImageUtils.getTintedDrawable(mContext, mFastScrollThumbPressedDrawable,
+ mThemeColor);
+ } else {
+ return mFastScrollThumbDrawable;
+ }
+ }
+
+ public Drawable getFastScrollPreviewDrawable(boolean positionRight) {
+ Drawable protoDrawable = positionRight ? mFastScrollPreviewDrawableRight :
+ mFastScrollPreviewDrawableLeft;
+ return ImageUtils.getTintedDrawable(mContext, protoDrawable, mThemeColor);
+ }
+}
diff --git a/src/com/android/messaging/ui/CursorRecyclerAdapter.java b/src/com/android/messaging/ui/CursorRecyclerAdapter.java
new file mode 100644
index 0000000..f1a7b7d
--- /dev/null
+++ b/src/com/android/messaging/ui/CursorRecyclerAdapter.java
@@ -0,0 +1,333 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.FilterQueryProvider;
+
+/**
+ * Copy of CursorAdapter suited for RecyclerView.
+ *
+ * TODO: BUG 16327984. Replace this with a framework supported CursorAdapter for
+ * RecyclerView when one is available.
+ */
+public abstract class CursorRecyclerAdapter<VH extends RecyclerView.ViewHolder>
+ extends RecyclerView.Adapter<VH> {
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected boolean mDataValid;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected boolean mAutoRequery;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected Cursor mCursor;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected Context mContext;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected int mRowIDColumn;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected ChangeObserver mChangeObserver;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected DataSetObserver mDataSetObserver;
+ /**
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected FilterQueryProvider mFilterQueryProvider;
+
+ /**
+ * If set the adapter will call requery() on the cursor whenever a content change
+ * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
+ *
+ * @deprecated This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+ */
+ @Deprecated
+ public static final int FLAG_AUTO_REQUERY = 0x01;
+
+ /**
+ * If set the adapter will register a content observer on the cursor and will call
+ * {@link #onContentChanged()} when a notification comes in. Be careful when
+ * using this flag: you will need to unset the current Cursor from the adapter
+ * to avoid leaks due to its registered observers. This flag is not needed
+ * when using a CursorAdapter with a
+ * {@link android.content.CursorLoader}.
+ */
+ public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
+
+ /**
+ * Recommended constructor.
+ *
+ * @param c The cursor from which to get the data.
+ * @param context The context
+ * @param flags Flags used to determine the behavior of the adapter; may
+ * be any combination of {@link #FLAG_AUTO_REQUERY} and
+ * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
+ */
+ public CursorRecyclerAdapter(final Context context, final Cursor c, final int flags) {
+ init(context, c, flags);
+ }
+
+ void init(final Context context, final Cursor c, int flags) {
+ if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
+ flags |= FLAG_REGISTER_CONTENT_OBSERVER;
+ mAutoRequery = true;
+ } else {
+ mAutoRequery = false;
+ }
+ final boolean cursorPresent = c != null;
+ mCursor = c;
+ mDataValid = cursorPresent;
+ mContext = context;
+ mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
+ if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
+ mChangeObserver = new ChangeObserver();
+ mDataSetObserver = new MyDataSetObserver();
+ } else {
+ mChangeObserver = null;
+ mDataSetObserver = null;
+ }
+
+ if (cursorPresent) {
+ if (mChangeObserver != null) {
+ c.registerContentObserver(mChangeObserver);
+ }
+ if (mDataSetObserver != null) {
+ c.registerDataSetObserver(mDataSetObserver);
+ }
+ }
+ }
+
+ /**
+ * Returns the cursor.
+ * @return the cursor.
+ */
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ @Override
+ public int getItemCount() {
+ if (mDataValid && mCursor != null) {
+ return mCursor.getCount();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * @see android.support.v7.widget.RecyclerView.Adapter#getItem(int)
+ */
+ public Object getItem(final int position) {
+ if (mDataValid && mCursor != null) {
+ mCursor.moveToPosition(position);
+ return mCursor;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int)
+ */
+ @Override
+ public long getItemId(final int position) {
+ if (mDataValid && mCursor != null) {
+ if (mCursor.moveToPosition(position)) {
+ return mCursor.getLong(mRowIDColumn);
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public VH onCreateViewHolder(final ViewGroup parent, final int viewType) {
+ return createViewHolder(mContext, parent, viewType);
+ }
+
+ @Override
+ public void onBindViewHolder(final VH holder, final int position) {
+ if (!mDataValid) {
+ throw new IllegalStateException("this should only be called when the cursor is valid");
+ }
+ if (!mCursor.moveToPosition(position)) {
+ throw new IllegalStateException("couldn't move cursor to position " + position);
+ }
+ bindViewHolder(holder, mContext, mCursor);
+ }
+ /**
+ * Bind an existing view to the data pointed to by cursor
+ * @param view Existing view, returned earlier by newView
+ * @param context Interface to application's global information
+ * @param cursor The cursor from which to get the data. The cursor is already
+ * moved to the correct position.
+ */
+ public abstract void bindViewHolder(VH holder, Context context, Cursor cursor);
+
+ /**
+ * @see android.support.v7.widget.RecyclerView.Adapter#createViewHolder(Context, ViewGroup, int)
+ */
+ public abstract VH createViewHolder(Context context, ViewGroup parent, int viewType);
+
+ /**
+ * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
+ * closed.
+ *
+ * @param cursor The new cursor to be used
+ */
+ public void changeCursor(final Cursor cursor) {
+ final Cursor old = swapCursor(cursor);
+ if (old != null) {
+ old.close();
+ }
+ }
+
+ /**
+ * Swap in a new Cursor, returning the old Cursor. Unlike
+ * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
+ * closed.
+ *
+ * @param newCursor The new cursor to be used.
+ * @return Returns the previously set Cursor, or null if there wasa not one.
+ * If the given new Cursor is the same instance is the previously set
+ * Cursor, null is also returned.
+ */
+ public Cursor swapCursor(final Cursor newCursor) {
+ if (newCursor == mCursor) {
+ return null;
+ }
+ final Cursor oldCursor = mCursor;
+ if (oldCursor != null) {
+ if (mChangeObserver != null) {
+ oldCursor.unregisterContentObserver(mChangeObserver);
+ }
+ if (mDataSetObserver != null) {
+ oldCursor.unregisterDataSetObserver(mDataSetObserver);
+ }
+ }
+ mCursor = newCursor;
+ if (newCursor != null) {
+ if (mChangeObserver != null) {
+ newCursor.registerContentObserver(mChangeObserver);
+ }
+ if (mDataSetObserver != null) {
+ newCursor.registerDataSetObserver(mDataSetObserver);
+ }
+ mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
+ mDataValid = true;
+ // notify the observers about the new cursor
+ notifyDataSetChanged();
+ } else {
+ mRowIDColumn = -1;
+ mDataValid = false;
+ // notify the observers about the lack of a data set
+ notifyDataSetChanged();
+ }
+ return oldCursor;
+ }
+
+ /**
+ * <p>Converts the cursor into a CharSequence. Subclasses should override this
+ * method to convert their results. The default implementation returns an
+ * empty String for null values or the default String representation of
+ * the value.</p>
+ *
+ * @param cursor the cursor to convert to a CharSequence
+ * @return a CharSequence representing the value
+ */
+ public CharSequence convertToString(final Cursor cursor) {
+ return cursor == null ? "" : cursor.toString();
+ }
+
+ /**
+ * Called when the {@link ContentObserver} on the cursor receives a change notification.
+ * The default implementation provides the auto-requery logic, but may be overridden by
+ * sub classes.
+ *
+ * @see ContentObserver#onChange(boolean)
+ */
+ protected void onContentChanged() {
+ if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
+ if (false) {
+ Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+ }
+ mDataValid = mCursor.requery();
+ }
+ }
+
+ private class ChangeObserver extends ContentObserver {
+ public ChangeObserver() {
+ super(new Handler());
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(final boolean selfChange) {
+ onContentChanged();
+ }
+ }
+
+ private class MyDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ mDataValid = true;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ mDataValid = false;
+ notifyDataSetChanged();
+ }
+ }
+
+}
diff --git a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java
new file mode 100644
index 0000000..1268c53
--- /dev/null
+++ b/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java
@@ -0,0 +1,136 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+
+import com.android.messaging.util.ImeUtil;
+
+/**
+ * Produces and holds a list view and its tab header to be displayed in a ViewPager.
+ */
+public abstract class CustomHeaderPagerListViewHolder extends BasePagerViewHolder
+ implements CustomHeaderPagerViewHolder {
+ private final Context mContext;
+ private final CursorAdapter mListAdapter;
+ private boolean mListCursorInitialized;
+ private ListView mListView;
+
+ public CustomHeaderPagerListViewHolder(final Context context,
+ final CursorAdapter adapter) {
+ mContext = context;
+ mListAdapter = adapter;
+ }
+
+ @Override
+ protected View createView(ViewGroup container) {
+ final LayoutInflater inflater = (LayoutInflater)
+ mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final View view = inflater.inflate(
+ getLayoutResId(),
+ null /* root */,
+ false /* attachToRoot */);
+ final ListView listView = (ListView) view.findViewById(getListViewResId());
+ listView.setAdapter(mListAdapter);
+ listView.setOnScrollListener(new OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(final AbsListView view, final int scrollState) {
+ if (scrollState != SCROLL_STATE_IDLE) {
+ ImeUtil.get().hideImeKeyboard(mContext, view);
+ }
+ }
+
+ @Override
+ public void onScroll(final AbsListView view, final int firstVisibleItem,
+ final int visibleItemCount, final int totalItemCount) {
+ }
+ });
+ mListView = listView;
+ maybeSetEmptyView();
+ return view;
+ }
+
+ public void onContactsCursorUpdated(final Cursor data) {
+ mListAdapter.swapCursor(data);
+ if (!mListCursorInitialized) {
+ // We set the emptyView here instead of in create so that the initial load won't show
+ // the empty UI - the system handles this and doesn't do what we would like.
+ mListCursorInitialized = true;
+ maybeSetEmptyView();
+ }
+ }
+
+ /**
+ * We don't want to show the empty view hint until BOTH conditions are met:
+ * 1. The view has been created.
+ * 2. Cursor data has been loaded once.
+ * Due to timing when data is loaded, the view may not be ready (and vice versa). So we
+ * are calling this method from both onContactsCursorUpdated & createView.
+ */
+ private void maybeSetEmptyView() {
+ if (mView != null && mListCursorInitialized) {
+ final ListEmptyView emptyView = (ListEmptyView) mView.findViewById(getEmptyViewResId());
+ if (emptyView != null) {
+ emptyView.setTextHint(getEmptyViewTitleResId());
+ emptyView.setImageHint(getEmptyViewImageResId());
+ final ListView listView = (ListView) mView.findViewById(getListViewResId());
+ listView.setEmptyView(emptyView);
+ }
+ }
+ }
+
+ public void invalidateList() {
+ mListAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * In order for scene transition to work, we toggle the visibility for each individual list
+ * view items so that they can be properly tracked by the scene transition manager.
+ * @param show whether the pending transition is to show or hide the list.
+ */
+ public void toggleVisibilityForPendingTransition(final boolean show, final View epicenterView) {
+ if (mListView == null) {
+ return;
+ }
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View childView = mListView.getChildAt(i);
+ if (childView != epicenterView) {
+ childView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+ }
+
+ @Override
+ public CharSequence getPageTitle(Context context) {
+ return context.getString(getPageTitleResId());
+ }
+
+ protected abstract int getLayoutResId();
+ protected abstract int getPageTitleResId();
+ protected abstract int getEmptyViewResId();
+ protected abstract int getEmptyViewTitleResId();
+ protected abstract int getEmptyViewImageResId();
+ protected abstract int getListViewResId();
+}
diff --git a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java
new file mode 100644
index 0000000..43802cd
--- /dev/null
+++ b/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+/**
+ * An extension on the standard PagerViewHolder to return a custom header view to be used by
+ * CustomHeaderViewPager
+ */
+public interface CustomHeaderPagerViewHolder extends PagerViewHolder {
+ CharSequence getPageTitle(Context context);
+}
diff --git a/src/com/android/messaging/ui/CustomHeaderViewPager.java b/src/com/android/messaging/ui/CustomHeaderViewPager.java
new file mode 100644
index 0000000..2e8df42
--- /dev/null
+++ b/src/com/android/messaging/ui/CustomHeaderViewPager.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+
+/**
+ * A view that contains both a view pager and a tab strip wrapped in a linear layout.
+ */
+public class CustomHeaderViewPager extends LinearLayout {
+ public final static int DEFAULT_TAB_STRIP_SIZE = -1;
+ private final int mDefaultTabStripSize;
+ private ViewPager mViewPager;
+ private ViewPagerTabs mTabstrip;
+
+ public CustomHeaderViewPager(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.custom_header_view_pager, this, true);
+ setOrientation(LinearLayout.VERTICAL);
+
+ mTabstrip = (ViewPagerTabs) findViewById(R.id.tab_strip);
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+
+ TypedValue tv = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
+ mDefaultTabStripSize = context.getResources().getDimensionPixelSize(tv.resourceId);
+ }
+
+ public void setCurrentItem(final int position) {
+ mViewPager.setCurrentItem(position);
+ }
+
+ public void setViewPagerTabHeight(final int tabHeight) {
+ mTabstrip.getLayoutParams().height = tabHeight == DEFAULT_TAB_STRIP_SIZE ?
+ mDefaultTabStripSize : tabHeight;
+ }
+
+ public void setViewHolders(final CustomHeaderPagerViewHolder[] viewHolders) {
+ Assert.notNull(mViewPager);
+ final PagerAdapter adapter = new CustomHeaderViewPagerAdapter(viewHolders);
+ mViewPager.setAdapter(adapter);
+ mTabstrip.setViewPager(mViewPager);
+ mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mTabstrip.onPageScrollStateChanged(state);
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
+ mTabstrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ mTabstrip.onPageSelected(position);
+ }
+ });
+ }
+
+ public int getSelectedItemPosition() {
+ return mTabstrip.getSelectedItemPosition();
+ }
+}
diff --git a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java b/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java
new file mode 100644
index 0000000..1a5cf6a
--- /dev/null
+++ b/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import com.android.messaging.Factory;
+
+public class CustomHeaderViewPagerAdapter extends
+ FixedViewPagerAdapter<CustomHeaderPagerViewHolder> {
+
+ public CustomHeaderViewPagerAdapter(final CustomHeaderPagerViewHolder[] viewHolders) {
+ super(viewHolders);
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ // The tab strip will handle RTL internally so we should use raw position.
+ return getViewHolder(position, false /* rtlAware */)
+ .getPageTitle(Factory.get().getApplicationContext());
+ }
+}
diff --git a/src/com/android/messaging/ui/FixedViewPagerAdapter.java b/src/com/android/messaging/ui/FixedViewPagerAdapter.java
new file mode 100644
index 0000000..bd73b71
--- /dev/null
+++ b/src/com/android/messaging/ui/FixedViewPagerAdapter.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v4.view.PagerAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * A PagerAdapter that provides a fixed number of paged Views provided by a fixed set of
+ * {@link PagerViewHolder}'s. This allows us to put a fixed number of Views, instead of fragments,
+ * into a given ViewPager.
+ */
+public class FixedViewPagerAdapter<T extends PagerViewHolder> extends PagerAdapter {
+ private final T[] mViewHolders;
+
+ public FixedViewPagerAdapter(final T[] viewHolders) {
+ Assert.notNull(viewHolders);
+ mViewHolders = viewHolders;
+ }
+
+ @Override
+ public Object instantiateItem(final ViewGroup container, final int position) {
+ final PagerViewHolder viewHolder = getViewHolder(position);
+ final View view = viewHolder.getView(container);
+ if (view == null) {
+ return null;
+ }
+ view.setTag(viewHolder);
+ container.addView(view);
+ return viewHolder;
+ }
+
+ @Override
+ public void destroyItem(final ViewGroup container, final int position, final Object object) {
+ final PagerViewHolder viewHolder = getViewHolder(position);
+ final View destroyedView = viewHolder.destroyView();
+ if (destroyedView != null) {
+ container.removeView(destroyedView);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mViewHolders.length;
+ }
+
+ @Override
+ public boolean isViewFromObject(final View view, final Object object) {
+ return view.getTag() == object;
+ }
+
+ public T getViewHolder(final int i) {
+ return getViewHolder(i, true /* rtlAware */);
+ }
+
+ @VisibleForTesting
+ public T getViewHolder(final int i, final boolean rtlAware) {
+ return mViewHolders[rtlAware ? getRtlPosition(i) : i];
+ }
+
+ @Override
+ public Parcelable saveState() {
+ // The paged views in the view pager gets created and destroyed as the user scrolls through
+ // them. By default, only the pages to the immediate left and right of the current visible
+ // page are realized. Moreover, if the activity gets destroyed and recreated, the pages are
+ // automatically destroyed. Therefore, in order to preserve transient page UI states that
+ // are not persisted in the DB we'd like to store them in a Bundle when views get
+ // destroyed. When the views get recreated, we rehydrate them by passing them the saved
+ // data. When the activity gets destroyed, it invokes saveState() on this adapter to
+ // add this saved Bundle to the overall saved instance state.
+ final Bundle savedViewHolderState = new Bundle(Factory.get().getApplicationContext()
+ .getClassLoader());
+ for (int i = 0; i < mViewHolders.length; i++) {
+ final Parcelable pageState = getViewHolder(i).saveState();
+ savedViewHolderState.putParcelable(getInstanceStateKeyForPage(i), pageState);
+ }
+ return savedViewHolderState;
+ }
+
+ @Override
+ public void restoreState(final Parcelable state, final ClassLoader loader) {
+ if (state instanceof Bundle) {
+ final Bundle restoredViewHolderState = (Bundle) state;
+ ((Bundle) state).setClassLoader(Factory.get().getApplicationContext().getClassLoader());
+ for (int i = 0; i < mViewHolders.length; i++) {
+ final Parcelable pageState = restoredViewHolderState
+ .getParcelable(getInstanceStateKeyForPage(i));
+ getViewHolder(i).restoreState(pageState);
+ }
+ } else {
+ super.restoreState(state, loader);
+ }
+ }
+
+ public void resetState() {
+ for (int i = 0; i < mViewHolders.length; i++) {
+ getViewHolder(i).resetState();
+ }
+ }
+
+ private String getInstanceStateKeyForPage(final int i) {
+ return getViewHolder(i).getClass().getCanonicalName() + "_savedstate_" + i;
+ }
+
+ protected int getRtlPosition(final int position) {
+ if (UiUtils.isRtlMode()) {
+ return mViewHolders.length - 1 - position;
+ }
+ return position;
+ }
+}
diff --git a/src/com/android/messaging/ui/ImeDetectFrameLayout.java b/src/com/android/messaging/ui/ImeDetectFrameLayout.java
new file mode 100644
index 0000000..32564ea
--- /dev/null
+++ b/src/com/android/messaging/ui/ImeDetectFrameLayout.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.LogUtil;
+
+public class ImeDetectFrameLayout extends FrameLayout {
+ public ImeDetectFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int measuredHeight = getMeasuredHeight();
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, "ImeDetectFrameLayout " +
+ "measuredHeight: " + measuredHeight + " getMeasuredHeight(): " +
+ getMeasuredHeight());
+ }
+
+ if (measuredHeight != getMeasuredHeight() && getContext() instanceof ImeUtil.ImeStateHost) {
+ ((ImeUtil.ImeStateHost) getContext()).onDisplayHeightChanged(heightMeasureSpec);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/LicenseActivity.java b/src/com/android/messaging/ui/LicenseActivity.java
new file mode 100644
index 0000000..a28da81
--- /dev/null
+++ b/src/com/android/messaging/ui/LicenseActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.webkit.WebView;
+
+import com.android.messaging.R;
+
+public class LicenseActivity extends Activity {
+ private final String LICENSE_URL = "file:///android_asset/licenses.html";
+
+ @Override
+ public void onCreate(final Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.license_activity);
+ final WebView webView = (WebView) findViewById(R.id.content);
+ webView.loadUrl(LICENSE_URL);
+ }
+}
diff --git a/src/com/android/messaging/ui/LineWrapLayout.java b/src/com/android/messaging/ui/LineWrapLayout.java
new file mode 100644
index 0000000..5219811
--- /dev/null
+++ b/src/com/android/messaging/ui/LineWrapLayout.java
@@ -0,0 +1,232 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+
+/**
+* A line-wrapping flow layout. Arranges children in horizontal flow, packing as many
+* child views as possible on each line. When the current line does not
+* have enough horizontal space, the layout continues on the next line.
+*/
+public class LineWrapLayout extends ViewGroup {
+ public LineWrapLayout(Context context) {
+ this(context, null);
+ }
+
+ public LineWrapLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int startPadding = UiUtils.getPaddingStart(this);
+ final int endPadding = UiUtils.getPaddingEnd(this);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec) - startPadding - endPadding;
+ final boolean isFixedSize = (widthMode == MeasureSpec.EXACTLY);
+
+ int height = 0;
+
+ int childCount = getChildCount();
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
+ int x = startPadding;
+ int currLineWidth = 0;
+ int currLineHeight = 0;
+ int maxLineWidth = 0;
+
+ for (int i = 0; i < childCount; i++) {
+ View currChild = getChildAt(i);
+ if (currChild.getVisibility() == GONE) {
+ continue;
+ }
+ LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
+ int startMargin = layoutParams.getStartMargin();
+ int endMargin = layoutParams.getEndMargin();
+ currChild.measure(childWidthSpec, MeasureSpec.UNSPECIFIED);
+ int childMeasuredWidth = currChild.getMeasuredWidth() + startMargin + endMargin;
+ int childMeasuredHeight = currChild.getMeasuredHeight() + layoutParams.topMargin +
+ layoutParams.bottomMargin;
+
+ if ((x + childMeasuredWidth) > widthSize) {
+ // New line. Update the overall height and reset trackers.
+ height += currLineHeight;
+ currLineHeight = 0;
+ x = startPadding;
+ currLineWidth = 0;
+ startMargin = 0;
+ }
+
+ x += childMeasuredWidth;
+ currLineWidth += childMeasuredWidth;
+ currLineHeight = Math.max(currLineHeight, childMeasuredHeight);
+ maxLineWidth = Math.max(currLineWidth, maxLineWidth);
+ }
+ // And account for the height of the last line.
+ height += currLineHeight;
+
+ int width = isFixedSize ? widthSize : maxLineWidth;
+ setMeasuredDimension(width + startPadding + endPadding,
+ height + getPaddingTop() + getPaddingBottom());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int startPadding = UiUtils.getPaddingStart(this);
+ final int endPadding = UiUtils.getPaddingEnd(this);
+ int width = getWidth() - startPadding - endPadding;
+ int y = getPaddingTop();
+ int x = startPadding;
+ int childCount = getChildCount();
+
+ int currLineHeight = 0;
+
+ // Do a dry-run first to get the line heights.
+ final ArrayList<Integer> lineHeights = new ArrayList<Integer>();
+ for (int i = 0; i < childCount; i++) {
+ View currChild = getChildAt(i);
+ if (currChild.getVisibility() == GONE) {
+ continue;
+ }
+ LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
+ int childWidth = currChild.getMeasuredWidth();
+ int childHeight = currChild.getMeasuredHeight();
+ int startMargin = layoutParams.getStartMargin();
+ int endMargin = layoutParams.getEndMargin();
+
+ if ((x + childWidth + startMargin + endMargin) > width) {
+ // new line
+ lineHeights.add(currLineHeight);
+ currLineHeight = 0;
+ x = startPadding;
+ startMargin = 0;
+ }
+ currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
+ layoutParams.bottomMargin);
+ x += childWidth + startMargin + endMargin;
+ }
+ // Add the last line height.
+ lineHeights.add(currLineHeight);
+
+ // Now perform the actual layout.
+ x = startPadding;
+ currLineHeight = 0;
+ int lineIndex = 0;
+ for (int i = 0; i < childCount; i++) {
+ View currChild = getChildAt(i);
+ if (currChild.getVisibility() == GONE) {
+ continue;
+ }
+ LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
+ int childWidth = currChild.getMeasuredWidth();
+ int childHeight = currChild.getMeasuredHeight();
+ int startMargin = layoutParams.getStartMargin();
+ int endMargin = layoutParams.getEndMargin();
+
+ if ((x + childWidth + startMargin + endMargin) > width) {
+ // new line
+ y += currLineHeight;
+ currLineHeight = 0;
+ x = startPadding;
+ startMargin = 0;
+ lineIndex++;
+ }
+ final int startPositionX = x + startMargin;
+ int startPositionY = y + layoutParams.topMargin; // default to top gravity
+ final int majorGravity = layoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if (majorGravity != Gravity.TOP && lineHeights.size() > lineIndex) {
+ final int lineHeight = lineHeights.get(lineIndex);
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ startPositionY = y + lineHeight - childHeight - layoutParams.bottomMargin;
+ break;
+
+ case Gravity.CENTER_VERTICAL:
+ startPositionY = y + (lineHeight - childHeight) / 2;
+ break;
+ }
+ }
+
+ if (OsUtil.isAtLeastJB_MR2() && getResources().getConfiguration()
+ .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ currChild.layout(width - startPositionX - childWidth, startPositionY,
+ width - startPositionX, startPositionY + childHeight);
+ } else {
+ currChild.layout(startPositionX, startPositionY, startPositionX + childWidth,
+ startPositionY + childHeight);
+ }
+ currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
+ layoutParams.bottomMargin);
+ x += childWidth + startMargin + endMargin;
+ }
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ public static final class LayoutParams extends FrameLayout.LayoutParams {
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public int getStartMargin() {
+ if (OsUtil.isAtLeastJB_MR2()) {
+ return getMarginStart();
+ } else {
+ return leftMargin;
+ }
+ }
+
+ public int getEndMargin() {
+ if (OsUtil.isAtLeastJB_MR2()) {
+ return getMarginEnd();
+ } else {
+ return rightMargin;
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/ListEmptyView.java b/src/com/android/messaging/ui/ListEmptyView.java
new file mode 100644
index 0000000..8cf3049
--- /dev/null
+++ b/src/com/android/messaging/ui/ListEmptyView.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+
+/**
+ * A common reusable view that shows a hint image and text for an empty list view.
+ */
+public class ListEmptyView extends LinearLayout {
+ private ImageView mEmptyImageHint;
+ private TextView mEmptyTextHint;
+
+ public ListEmptyView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mEmptyImageHint = (ImageView) findViewById(R.id.empty_image_hint);
+ mEmptyTextHint = (TextView) findViewById(R.id.empty_text_hint);
+ }
+
+ public void setImageHint(final int resId) {
+ mEmptyImageHint.setImageResource(resId);
+ }
+
+ public void setTextHint(final int resId) {
+ mEmptyTextHint.setText(getResources().getText(resId));
+ }
+
+ public void setTextHint(final CharSequence hintText) {
+ mEmptyTextHint.setText(hintText);
+ }
+
+ public void setIsImageVisible(final boolean isImageVisible) {
+ mEmptyImageHint.setVisibility(isImageVisible ? VISIBLE : GONE);
+ }
+
+ public void setIsVerticallyCentered(final boolean isVerticallyCentered) {
+ int gravity =
+ isVerticallyCentered ? Gravity.CENTER : Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ ((LinearLayout.LayoutParams) mEmptyImageHint.getLayoutParams()).gravity = gravity;
+ ((LinearLayout.LayoutParams) mEmptyTextHint.getLayoutParams()).gravity = gravity;
+ getLayoutParams().height =
+ isVerticallyCentered ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
+ requestLayout();
+ }
+}
diff --git a/src/com/android/messaging/ui/MaxHeightScrollView.java b/src/com/android/messaging/ui/MaxHeightScrollView.java
new file mode 100644
index 0000000..a000cd1
--- /dev/null
+++ b/src/com/android/messaging/ui/MaxHeightScrollView.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+import com.android.messaging.R;
+
+/**
+ * A ScrollView that limits the maximum height that it can take. This is to work around android
+ * layout's limitation of not having android:maxHeight.
+ */
+public class MaxHeightScrollView extends ScrollView {
+ private static final int NO_MAX_HEIGHT = -1;
+
+ private final int mMaxHeight;
+
+ public MaxHeightScrollView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ final TypedArray attr = context.obtainStyledAttributes(attrs,
+ R.styleable.MaxHeightScrollView, 0, 0);
+ mMaxHeight = attr.getDimensionPixelSize(R.styleable.MaxHeightScrollView_android_maxHeight,
+ NO_MAX_HEIGHT);
+ attr.recycle();
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mMaxHeight != NO_MAX_HEIGHT) {
+ setMeasuredDimension(getMeasuredWidth(), Math.min(getMeasuredHeight(), mMaxHeight));
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/MultiAttachmentLayout.java b/src/com/android/messaging/ui/MultiAttachmentLayout.java
new file mode 100644
index 0000000..f620245
--- /dev/null
+++ b/src/com/android/messaging/ui/MultiAttachmentLayout.java
@@ -0,0 +1,424 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AnimationSet;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.datamodel.media.ImageRequestDescriptor;
+import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Holds and displays multiple attachments in a 4x2 grid. Each preview image "tile" can take
+ * one of three sizes - small (1x1), wide (2x1) and large (2x2). We have a number of predefined
+ * layout settings designed for holding 2, 3, 4+ attachments (these layout settings are
+ * tweakable by design request to allow for max flexibility). For a visual example, consider the
+ * following attachment layout:
+ *
+ * +---------------+----------------+
+ * | | |
+ * | | B |
+ * | | |
+ * | A |-------+--------|
+ * | | | |
+ * | | C | D |
+ * | | | |
+ * +---------------+-------+--------+
+ *
+ * In the above example, the layout consists of four tiles, A-D. A is a large tile, B is a
+ * wide tile and C & D are both small tiles. A starts at (0,0) and ends at (1,1), B starts at
+ * (2,0) and ends at (3,0), and so on. In our layout class we'd have these tiles in the order
+ * of A-D, so that we make sure the last tile is always the one where we can put the overflow
+ * indicator (e.g. "+2").
+ */
+public class MultiAttachmentLayout extends FrameLayout {
+
+ public interface OnAttachmentClickListener {
+ boolean onAttachmentClick(MessagePartData attachment, Rect viewBoundsOnScreen,
+ boolean longPress);
+ }
+
+ private static final int GRID_WIDTH = 4; // in # of cells
+ private static final int GRID_HEIGHT = 2; // in # of cells
+
+ /**
+ * Represents a preview image tile in the layout
+ */
+ private static class Tile {
+ public final int startX;
+ public final int startY;
+ public final int endX;
+ public final int endY;
+
+ private Tile(final int startX, final int startY, final int endX, final int endY) {
+ this.startX = startX;
+ this.startY = startY;
+ this.endX = endX;
+ this.endY = endY;
+ }
+
+ public int getWidthMeasureSpec(final int cellWidth, final int padding) {
+ return MeasureSpec.makeMeasureSpec((endX - startX + 1) * cellWidth - padding * 2,
+ MeasureSpec.EXACTLY);
+ }
+
+ public int getHeightMeasureSpec(final int cellHeight, final int padding) {
+ return MeasureSpec.makeMeasureSpec((endY - startY + 1) * cellHeight - padding * 2,
+ MeasureSpec.EXACTLY);
+ }
+
+ public static Tile large(final int startX, final int startY) {
+ return new Tile(startX, startY, startX + 1, startY + 1);
+ }
+
+ public static Tile wide(final int startX, final int startY) {
+ return new Tile(startX, startY, startX + 1, startY);
+ }
+
+ public static Tile small(final int startX, final int startY) {
+ return new Tile(startX, startY, startX, startY);
+ }
+ }
+
+ /**
+ * A layout simply contains a list of tiles, in the order of top-left -> bottom-right.
+ */
+ private static class Layout {
+ public final List<Tile> tiles;
+ public Layout(final Tile[] tilesArray) {
+ tiles = Arrays.asList(tilesArray);
+ }
+ }
+
+ /**
+ * List of predefined layout configurations w.r.t no. of attachments.
+ */
+ private static final Layout[] ATTACHMENT_LAYOUTS_BY_COUNT = {
+ null, // Doesn't support zero attachments.
+ null, // Doesn't support one attachment. Single attachment preview is used instead.
+ new Layout(new Tile[] { Tile.large(0, 0), Tile.large(2, 0) }), // 2 items
+ new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.wide(2, 1) }), // 3 items
+ new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.small(2, 1), // 4+ items
+ Tile.small(3, 1) }),
+ };
+
+ /**
+ * List of predefined RTL layout configurations w.r.t no. of attachments.
+ */
+ private static final Layout[] ATTACHMENT_RTL_LAYOUTS_BY_COUNT = {
+ null, // Doesn't support zero attachments.
+ null, // Doesn't support one attachment. Single attachment preview is used instead.
+ new Layout(new Tile[] { Tile.large(2, 0), Tile.large(0, 0)}), // 2 items
+ new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.wide(0, 1) }), // 3 items
+ new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.small(1, 1), // 4+ items
+ Tile.small(0, 1) }),
+ };
+
+ private Layout mCurrentLayout;
+ private ArrayList<ViewWrapper> mPreviewViews;
+ private int mPlusNumber;
+ private TextView mPlusTextView;
+ private OnAttachmentClickListener mAttachmentClickListener;
+ private AsyncImageViewDelayLoader mImageViewDelayLoader;
+
+ public MultiAttachmentLayout(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mPreviewViews = new ArrayList<ViewWrapper>();
+ }
+
+ public void bindAttachments(final Iterable<MessagePartData> attachments,
+ final Rect transitionRect, final int count) {
+ final ArrayList<ViewWrapper> previousViews = mPreviewViews;
+ mPreviewViews = new ArrayList<ViewWrapper>();
+ removeView(mPlusTextView);
+ mPlusTextView = null;
+
+ determineLayout(attachments, count);
+ buildViews(attachments, previousViews, transitionRect);
+
+ // Remove all previous views that couldn't be recycled.
+ for (final ViewWrapper viewWrapper : previousViews) {
+ removeView(viewWrapper.view);
+ }
+ requestLayout();
+ }
+
+ public OnAttachmentClickListener getOnAttachmentClickListener() {
+ return mAttachmentClickListener;
+ }
+
+ public void setOnAttachmentClickListener(final OnAttachmentClickListener listener) {
+ mAttachmentClickListener = listener;
+ }
+
+ public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
+ mImageViewDelayLoader = delayLoader;
+ }
+
+ public void setColorFilter(int color) {
+ for (ViewWrapper viewWrapper : mPreviewViews) {
+ if (viewWrapper.view instanceof AsyncImageView) {
+ ((AsyncImageView) viewWrapper.view).setColorFilter(color);
+ }
+ }
+ }
+
+ public void clearColorFilter() {
+ for (ViewWrapper viewWrapper : mPreviewViews) {
+ if (viewWrapper.view instanceof AsyncImageView) {
+ ((AsyncImageView) viewWrapper.view).clearColorFilter();
+ }
+ }
+ }
+
+ private void determineLayout(final Iterable<MessagePartData> attachments, final int count) {
+ Assert.isTrue(attachments != null);
+ final boolean isRtl = AccessibilityUtil.isLayoutRtl(getRootView());
+ if (isRtl) {
+ mCurrentLayout = ATTACHMENT_RTL_LAYOUTS_BY_COUNT[Math.min(count,
+ ATTACHMENT_RTL_LAYOUTS_BY_COUNT.length - 1)];
+ } else {
+ mCurrentLayout = ATTACHMENT_LAYOUTS_BY_COUNT[Math.min(count,
+ ATTACHMENT_LAYOUTS_BY_COUNT.length - 1)];
+ }
+
+ // We must have a valid layout for the current configuration.
+ Assert.notNull(mCurrentLayout);
+
+ mPlusNumber = count - mCurrentLayout.tiles.size();
+ Assert.isTrue(mPlusNumber >= 0);
+ }
+
+ private void buildViews(final Iterable<MessagePartData> attachments,
+ final ArrayList<ViewWrapper> previousViews, final Rect transitionRect) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ final int count = mCurrentLayout.tiles.size();
+ int i = 0;
+ final Iterator<MessagePartData> iterator = attachments.iterator();
+ while (iterator.hasNext() && i < count) {
+ final MessagePartData attachment = iterator.next();
+ ViewWrapper attachmentWrapper = null;
+ // Try to recycle a previous view first
+ for (int j = 0; j < previousViews.size(); j++) {
+ final ViewWrapper previousView = previousViews.get(j);
+ if (previousView.attachment.equals(attachment) &&
+ !(previousView.attachment instanceof PendingAttachmentData)) {
+ attachmentWrapper = previousView;
+ previousViews.remove(j);
+ break;
+ }
+ }
+
+ if (attachmentWrapper == null) {
+ final View view = AttachmentPreviewFactory.createAttachmentPreview(layoutInflater,
+ attachment, this, AttachmentPreviewFactory.TYPE_MULTIPLE,
+ false /* startImageRequest */, mAttachmentClickListener);
+
+ if (view == null) {
+ // createAttachmentPreview can return null if something goes wrong (e.g.
+ // attachment has unsupported contentType)
+ continue;
+ }
+ if (view instanceof AsyncImageView && mImageViewDelayLoader != null) {
+ AsyncImageView asyncImageView = (AsyncImageView) view;
+ asyncImageView.setDelayLoader(mImageViewDelayLoader);
+ }
+ addView(view);
+ attachmentWrapper = new ViewWrapper(view, attachment);
+ // Help animate from single to multi by copying over the prev location
+ if (count == 2 && i == 1 && transitionRect != null) {
+ attachmentWrapper.prevLeft = transitionRect.left;
+ attachmentWrapper.prevTop = transitionRect.top;
+ attachmentWrapper.prevWidth = transitionRect.width();
+ attachmentWrapper.prevHeight = transitionRect.height();
+ }
+ }
+ i++;
+ Assert.notNull(attachmentWrapper);
+ mPreviewViews.add(attachmentWrapper);
+
+ // The first view will animate in using PopupTransitionAnimation, but the remaining
+ // views will slide from their previous position to their new position within the
+ // layout
+ if (i == 0) {
+ AttachmentPreview.tryAnimateViewIn(attachment, attachmentWrapper.view);
+ }
+ attachmentWrapper.needsSlideAnimation = i > 0;
+ }
+
+ // Build the plus text view (e.g. "+2") for when there are more attachments than what
+ // this layout can display.
+ if (mPlusNumber > 0) {
+ mPlusTextView = (TextView) layoutInflater.inflate(R.layout.attachment_more_text_view,
+ null /* parent */);
+ mPlusTextView.setText(getResources().getString(R.string.attachment_more_items,
+ mPlusNumber));
+ addView(mPlusTextView);
+ }
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ final int maxWidth = getResources().getDimensionPixelSize(
+ R.dimen.multiple_attachment_preview_width);
+ final int maxHeight = getResources().getDimensionPixelSize(
+ R.dimen.multiple_attachment_preview_height);
+ final int width = Math.min(MeasureSpec.getSize(widthMeasureSpec), maxWidth);
+ final int height = maxHeight;
+ final int cellWidth = width / GRID_WIDTH;
+ final int cellHeight = height / GRID_HEIGHT;
+ final int count = mPreviewViews.size();
+ final int padding = getResources().getDimensionPixelOffset(
+ R.dimen.multiple_attachment_preview_padding);
+ for (int i = 0; i < count; i++) {
+ final View view = mPreviewViews.get(i).view;
+ final Tile imageTile = mCurrentLayout.tiles.get(i);
+ view.measure(imageTile.getWidthMeasureSpec(cellWidth, padding),
+ imageTile.getHeightMeasureSpec(cellHeight, padding));
+
+ // Now that we know the size, we can request an appropriately-sized image.
+ if (view instanceof AsyncImageView) {
+ final ImageRequestDescriptor imageRequest =
+ AttachmentPreviewFactory.getImageRequestDescriptorForAttachment(
+ mPreviewViews.get(i).attachment,
+ view.getMeasuredWidth(),
+ view.getMeasuredHeight());
+ ((AsyncImageView) view).setImageResourceId(imageRequest);
+ }
+
+ if (i == count - 1 && mPlusTextView != null) {
+ // The plus text view always covers the last attachment.
+ mPlusTextView.measure(imageTile.getWidthMeasureSpec(cellWidth, padding),
+ imageTile.getHeightMeasureSpec(cellHeight, padding));
+ }
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onLayout(final boolean changed, final int left, final int top, final int right,
+ final int bottom) {
+ final int cellWidth = getMeasuredWidth() / GRID_WIDTH;
+ final int cellHeight = getMeasuredHeight() / GRID_HEIGHT;
+ final int padding = getResources().getDimensionPixelOffset(
+ R.dimen.multiple_attachment_preview_padding);
+ final int count = mPreviewViews.size();
+ for (int i = 0; i < count; i++) {
+ final ViewWrapper viewWrapper = mPreviewViews.get(i);
+ final View view = viewWrapper.view;
+ final Tile imageTile = mCurrentLayout.tiles.get(i);
+ final int tileLeft = imageTile.startX * cellWidth;
+ final int tileTop = imageTile.startY * cellHeight;
+ view.layout(tileLeft + padding, tileTop + padding,
+ tileLeft + view.getMeasuredWidth(),
+ tileTop + view.getMeasuredHeight());
+ if (viewWrapper.needsSlideAnimation) {
+ trySlideAttachmentView(viewWrapper);
+ viewWrapper.needsSlideAnimation = false;
+ } else {
+ viewWrapper.prevLeft = view.getLeft();
+ viewWrapper.prevTop = view.getTop();
+ viewWrapper.prevWidth = view.getWidth();
+ viewWrapper.prevHeight = view.getHeight();
+ }
+
+ if (i == count - 1 && mPlusTextView != null) {
+ // The plus text view always covers the last attachment.
+ mPlusTextView.layout(tileLeft + padding, tileTop + padding,
+ tileLeft + mPlusTextView.getMeasuredWidth(),
+ tileTop + mPlusTextView.getMeasuredHeight());
+ }
+ }
+ }
+
+ private void trySlideAttachmentView(final ViewWrapper viewWrapper) {
+ if (!(viewWrapper.attachment instanceof MediaPickerMessagePartData)) {
+ return;
+ }
+ final View view = viewWrapper.view;
+
+
+ final int xOffset = viewWrapper.prevLeft - view.getLeft();
+ final int yOffset = viewWrapper.prevTop - view.getTop();
+ final float scaleX = viewWrapper.prevWidth / (float) view.getWidth();
+ final float scaleY = viewWrapper.prevHeight / (float) view.getHeight();
+
+ if (xOffset == 0 && yOffset == 0 && scaleX == 1 && scaleY == 1) {
+ // Layout hasn't changed
+ return;
+ }
+
+ final AnimationSet animationSet = new AnimationSet(
+ true /* shareInterpolator */);
+ animationSet.addAnimation(new TranslateAnimation(xOffset, 0, yOffset, 0));
+ animationSet.addAnimation(new ScaleAnimation(scaleX, 1, scaleY, 1));
+ animationSet.setDuration(
+ UiUtils.MEDIAPICKER_TRANSITION_DURATION);
+ animationSet.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
+ view.startAnimation(animationSet);
+ view.invalidate();
+ viewWrapper.prevLeft = view.getLeft();
+ viewWrapper.prevTop = view.getTop();
+ viewWrapper.prevWidth = view.getWidth();
+ viewWrapper.prevHeight = view.getHeight();
+ }
+
+ public View findViewForAttachment(final MessagePartData attachment) {
+ for (ViewWrapper wrapper : mPreviewViews) {
+ if (wrapper.attachment.equals(attachment) &&
+ !(wrapper.attachment instanceof PendingAttachmentData)) {
+ return wrapper.view;
+ }
+ }
+ return null;
+ }
+
+ private static class ViewWrapper {
+ final View view;
+ final MessagePartData attachment;
+ boolean needsSlideAnimation;
+ int prevLeft;
+ int prevTop;
+ int prevWidth;
+ int prevHeight;
+
+ ViewWrapper(final View view, final MessagePartData attachment) {
+ this.view = view;
+ this.attachment = attachment;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/OrientedBitmapDrawable.java b/src/com/android/messaging/ui/OrientedBitmapDrawable.java
new file mode 100644
index 0000000..9242668
--- /dev/null
+++ b/src/com/android/messaging/ui/OrientedBitmapDrawable.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.Gravity;
+
+import com.android.messaging.util.exif.ExifInterface;
+
+/**
+ * A drawable that draws a bitmap in a flipped or rotated orientation without having to adjust the
+ * bitmap
+ */
+public class OrientedBitmapDrawable extends BitmapDrawable {
+ private final ExifInterface.OrientationParams mOrientationParams;
+ private final Rect mDstRect;
+ private int mCenterX;
+ private int mCenterY;
+ private boolean mApplyGravity;
+
+ public static BitmapDrawable create(final int orientation, Resources res, Bitmap bitmap) {
+ if (orientation <= ExifInterface.Orientation.TOP_LEFT) {
+ // No need to adjust the bitmap, so just use a regular BitmapDrawable
+ return new BitmapDrawable(res, bitmap);
+ } else {
+ // Create an oriented bitmap drawable
+ return new OrientedBitmapDrawable(orientation, res, bitmap);
+ }
+ }
+
+ private OrientedBitmapDrawable(final int orientation, Resources res, Bitmap bitmap) {
+ super(res, bitmap);
+ mOrientationParams = ExifInterface.getOrientationParams(orientation);
+ mApplyGravity = true;
+ mDstRect = new Rect();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ if (mOrientationParams.invertDimensions) {
+ return super.getIntrinsicHeight();
+ }
+ return super.getIntrinsicWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ if (mOrientationParams.invertDimensions) {
+ return super.getIntrinsicWidth();
+ }
+ return super.getIntrinsicHeight();
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mApplyGravity = true;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mApplyGravity) {
+ Gravity.apply(getGravity(), getIntrinsicWidth(), getIntrinsicHeight(), getBounds(),
+ mDstRect);
+ mCenterX = mDstRect.centerX();
+ mCenterY = mDstRect.centerY();
+ if (mOrientationParams.invertDimensions) {
+ final Matrix matrix = new Matrix();
+ matrix.setRotate(mOrientationParams.rotation, mCenterX, mCenterY);
+ final RectF rotatedRect = new RectF(mDstRect);
+ matrix.mapRect(rotatedRect);
+ mDstRect.set((int) rotatedRect.left, (int) rotatedRect.top, (int) rotatedRect.right,
+ (int) rotatedRect.bottom);
+ }
+
+ mApplyGravity = false;
+ }
+ canvas.save();
+ canvas.scale(mOrientationParams.scaleX, mOrientationParams.scaleY, mCenterX, mCenterY);
+ canvas.rotate(mOrientationParams.rotation, mCenterX, mCenterY);
+ canvas.drawBitmap(getBitmap(), (Rect) null, mDstRect, getPaint());
+ canvas.restore();
+ }
+}
diff --git a/src/com/android/messaging/ui/PagerViewHolder.java b/src/com/android/messaging/ui/PagerViewHolder.java
new file mode 100644
index 0000000..2f33a0f
--- /dev/null
+++ b/src/com/android/messaging/ui/PagerViewHolder.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Holds reusable View(s) for a {@link FixedViewPagerAdapter} to display a page. By using
+ * reusable Views inside ViewPagers this allows us to get rid of nested fragments and the messy
+ * activity lifecycle problems they entail.
+ */
+public interface PagerViewHolder extends PersistentInstanceState {
+ /** Instructs the pager to clean up any view related resources
+ * @return the destroyed View so that the adapter may remove it from the container, or
+ * null if no View has been created. */
+ View destroyView();
+
+ /** @return The view that presents the page view to the user */
+ View getView(ViewGroup container);
+}
diff --git a/src/com/android/messaging/ui/PagingAwareViewPager.java b/src/com/android/messaging/ui/PagingAwareViewPager.java
new file mode 100644
index 0000000..cc7b2cd
--- /dev/null
+++ b/src/com/android/messaging/ui/PagingAwareViewPager.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.messaging.util.UiUtils;
+
+/**
+ * A simple extension on the standard ViewPager which lets you turn paging on/off.
+ */
+public class PagingAwareViewPager extends ViewPager {
+ private boolean mPagingEnabled = true;
+
+ public PagingAwareViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setCurrentItem(int item, boolean smoothScroll) {
+ super.setCurrentItem(getRtlPosition(item), smoothScroll);
+ }
+
+ @Override
+ public void setCurrentItem(int item) {
+ super.setCurrentItem(getRtlPosition(item));
+ }
+
+ @Override
+ public int getCurrentItem() {
+ int position = super.getCurrentItem();
+ return getRtlPosition(position);
+ }
+
+ /**
+ * Switches position in pager to be adjusted for if we are in RtL mode
+ *
+ * @param position
+ * @return position adjusted if in rtl mode
+ */
+ protected int getRtlPosition(final int position) {
+ final PagerAdapter adapter = getAdapter();
+ if (adapter != null && UiUtils.isRtlMode()) {
+ return adapter.getCount() - 1 - position;
+ }
+ return position;
+ }
+
+ @Override
+ public boolean onTouchEvent(final MotionEvent event) {
+ if (!mPagingEnabled) {
+ return false;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(final MotionEvent event) {
+ if (!mPagingEnabled) {
+ return false;
+ }
+ return super.onInterceptTouchEvent(event);
+ }
+
+ public void setPagingEnabled(final boolean enabled) {
+ this.mPagingEnabled = enabled;
+ }
+
+ /** This prevents touch-less scrolling eg. while doing accessibility navigation. */
+ @Override
+ public boolean canScrollHorizontally(int direction) {
+ if (mPagingEnabled) {
+ return super.canScrollHorizontally(direction);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/PermissionCheckActivity.java b/src/com/android/messaging/ui/PermissionCheckActivity.java
new file mode 100644
index 0000000..e992a10
--- /dev/null
+++ b/src/com/android/messaging/ui/PermissionCheckActivity.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Activity to check if the user has required permissions. If not, it will try to prompt the user
+ * to grant permissions. However, the OS may not actually prompt the user if the user had
+ * previously checked the "Never ask again" checkbox while denying the required permissions.
+ */
+public class PermissionCheckActivity extends Activity {
+ private static final int REQUIRED_PERMISSIONS_REQUEST_CODE = 1;
+ private static final long AUTOMATED_RESULT_THRESHOLD_MILLLIS = 250;
+ private static final String PACKAGE_URI_PREFIX = "package:";
+ private long mRequestTimeMillis;
+ private TextView mNextView;
+ private TextView mSettingsView;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (redirectIfNeeded()) {
+ return;
+ }
+
+ setContentView(R.layout.permission_check_activity);
+ UiUtils.setStatusBarColor(this, getColor(R.color.permission_check_activity_background));
+
+ findViewById(R.id.exit).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ finish();
+ }
+ });
+
+ mNextView = (TextView) findViewById(R.id.next);
+ mNextView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ tryRequestPermission();
+ }
+ });
+
+ mSettingsView = (TextView) findViewById(R.id.settings);
+ mSettingsView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.parse(PACKAGE_URI_PREFIX + getPackageName()));
+ startActivity(intent);
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (redirectIfNeeded()) {
+ return;
+ }
+ }
+
+ private void tryRequestPermission() {
+ final String[] missingPermissions = OsUtil.getMissingRequiredPermissions();
+ if (missingPermissions.length == 0) {
+ redirect();
+ return;
+ }
+
+ mRequestTimeMillis = SystemClock.elapsedRealtime();
+ requestPermissions(missingPermissions, REQUIRED_PERMISSIONS_REQUEST_CODE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ final int requestCode, final String permissions[], final int[] grantResults) {
+ if (requestCode == REQUIRED_PERMISSIONS_REQUEST_CODE) {
+ // We do not use grantResults as some of the granted permissions might have been
+ // revoked while the permissions dialog box was being shown for the missing permissions.
+ if (OsUtil.hasRequiredPermissions()) {
+ Factory.get().onRequiredPermissionsAcquired();
+ redirect();
+ } else {
+ final long currentTimeMillis = SystemClock.elapsedRealtime();
+ // If the permission request completes very quickly, it must be because the system
+ // automatically denied. This can happen if the user had previously denied it
+ // and checked the "Never ask again" check box.
+ if ((currentTimeMillis - mRequestTimeMillis) < AUTOMATED_RESULT_THRESHOLD_MILLLIS) {
+ mNextView.setVisibility(View.GONE);
+
+ mSettingsView.setVisibility(View.VISIBLE);
+ findViewById(R.id.enable_permission_procedure).setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ }
+
+ /** Returns true if the redirecting was performed */
+ private boolean redirectIfNeeded() {
+ if (!OsUtil.hasRequiredPermissions()) {
+ return false;
+ }
+
+ redirect();
+ return true;
+ }
+
+ private void redirect() {
+ UIIntents.get().launchConversationListActivity(this);
+ finish();
+ }
+}
diff --git a/src/com/android/messaging/ui/PersistentInstanceState.java b/src/com/android/messaging/ui/PersistentInstanceState.java
new file mode 100644
index 0000000..3cc1856
--- /dev/null
+++ b/src/com/android/messaging/ui/PersistentInstanceState.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import android.os.Parcelable;
+
+/**
+ * Wraps around functionality to save, restore and reset a particular UI component's state.
+ */
+public interface PersistentInstanceState {
+ /**
+ * Saves necessary information about the current state of the instance to later restore
+ * the instance to its original state.
+ */
+ Parcelable saveState();
+
+ /**
+ * Given a previously saved instance state, attempt to restore to its original view state.
+ */
+ void restoreState(Parcelable restoredState);
+
+ /**
+ * Called when any current/preserved state should be reset to zero state.
+ */
+ void resetState();
+}
diff --git a/src/com/android/messaging/ui/PersonItemView.java b/src/com/android/messaging/ui/PersonItemView.java
new file mode 100644
index 0000000..afd1a99
--- /dev/null
+++ b/src/com/android/messaging/ui/PersonItemView.java
@@ -0,0 +1,242 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.text.BidiFormatter;
+import android.support.v4.text.TextDirectionHeuristicsCompat;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.binding.DetachableBinding;
+import com.android.messaging.datamodel.data.PersonItemData;
+import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Shows a view for a "person" - could be a contact or a participant. This always shows a
+ * contact icon on the left, and the person's display name on the right.
+ *
+ * This view is always bound to an abstract PersonItemData class, so to use it for a specific
+ * scenario, all you need to do is to create a concrete PersonItemData subclass that bridges
+ * between the underlying data (e.g. ParticipantData) and what the UI wants (e.g. display name).
+ */
+public class PersonItemView extends LinearLayout implements PersonItemDataListener,
+ OnLayoutChangeListener {
+ public interface PersonItemViewListener {
+ void onPersonClicked(PersonItemData data);
+ boolean onPersonLongClicked(PersonItemData data);
+ }
+
+ protected final DetachableBinding<PersonItemData> mBinding;
+ private TextView mNameTextView;
+ private TextView mDetailsTextView;
+ private ContactIconView mContactIconView;
+ private View mDetailsContainer;
+ private PersonItemViewListener mListener;
+ private boolean mAvatarOnly;
+
+ public PersonItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mBinding = BindingBase.createDetachableBinding(this);
+ LayoutInflater.from(getContext()).inflate(R.layout.person_item_view, this, true);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mNameTextView = (TextView) findViewById(R.id.name);
+ mDetailsTextView = (TextView) findViewById(R.id.details);
+ mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
+ mDetailsContainer = findViewById(R.id.details_container);
+ mNameTextView.addOnLayoutChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mBinding.isBound()) {
+ mBinding.detach();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mBinding.reAttachIfPossible();
+ }
+
+ /**
+ * Binds to a person item data which will provide us info to be displayed.
+ * @param personData the PersonItemData to be bound to.
+ */
+ public void bind(final PersonItemData personData) {
+ if (mBinding.isBound()) {
+ if (mBinding.getData().equals(personData)) {
+ // Don't rebind if we are requesting the same data.
+ return;
+ }
+ mBinding.unbind();
+ }
+
+ if (personData != null) {
+ mBinding.bind(personData);
+ mBinding.getData().setListener(this);
+
+ // Accessibility reason : in case phone numbers are mixed in the display name,
+ // we need to vocalize it for talkback.
+ final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
+ getResources(), getDisplayName());
+ mNameTextView.setContentDescription(vocalizedDisplayName);
+ }
+ updateViewAppearance();
+ }
+
+ /**
+ * @return Display name, possibly comma-ellipsized.
+ */
+ private String getDisplayName() {
+ final int width = mNameTextView.getMeasuredWidth();
+ final String displayName = mBinding.getData().getDisplayName();
+ if (width == 0 || TextUtils.isEmpty(displayName) || !displayName.contains(",")) {
+ return displayName;
+ }
+ final String plusOneString = getContext().getString(R.string.plus_one);
+ final String plusNString = getContext().getString(R.string.plus_n);
+ return BidiFormatter.getInstance().unicodeWrap(
+ UiUtils.commaEllipsize(
+ displayName,
+ mNameTextView.getPaint(),
+ width,
+ plusOneString,
+ plusNString).toString(),
+ TextDirectionHeuristicsCompat.LTR);
+ }
+
+ @Override
+ public void onLayoutChange(final View v, final int left, final int top, final int right,
+ final int bottom, final int oldLeft, final int oldTop, final int oldRight,
+ final int oldBottom) {
+ if (mBinding.isBound() && v == mNameTextView) {
+ setNameTextView();
+ }
+ }
+
+ /**
+ * When set to true, we display only the avatar of the person and hide everything else.
+ */
+ public void setAvatarOnly(final boolean avatarOnly) {
+ mAvatarOnly = avatarOnly;
+ mDetailsContainer.setVisibility(avatarOnly ? GONE : VISIBLE);
+ }
+
+ public boolean isAvatarOnly() {
+ return mAvatarOnly;
+ }
+
+ public void setNameTextColor(final int color) {
+ mNameTextView.setTextColor(color);
+ }
+
+ public void setDetailsTextColor(final int color) {
+ mDetailsTextView.setTextColor(color);
+ }
+
+ public void setListener(final PersonItemViewListener listener) {
+ mListener = listener;
+ if (mListener == null) {
+ return;
+ }
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ if (mListener != null && mBinding.isBound()) {
+ mListener.onPersonClicked(mBinding.getData());
+ }
+ }
+ });
+ final OnLongClickListener onLongClickListener = new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (mListener != null && mBinding.isBound()) {
+ return mListener.onPersonLongClicked(mBinding.getData());
+ }
+ return false;
+ }
+ };
+ setOnLongClickListener(onLongClickListener);
+ mContactIconView.setOnLongClickListener(onLongClickListener);
+ }
+
+ public void performClickOnAvatar() {
+ mContactIconView.performClick();
+ }
+
+ protected void updateViewAppearance() {
+ if (mBinding.isBound()) {
+ setNameTextView();
+
+ final String details = mBinding.getData().getDetails();
+ if (TextUtils.isEmpty(details)) {
+ mDetailsTextView.setVisibility(GONE);
+ } else {
+ mDetailsTextView.setVisibility(VISIBLE);
+ mDetailsTextView.setText(details);
+ }
+
+ mContactIconView.setImageResourceUri(mBinding.getData().getAvatarUri(),
+ mBinding.getData().getContactId(), mBinding.getData().getLookupKey(),
+ mBinding.getData().getNormalizedDestination());
+ } else {
+ mNameTextView.setText("");
+ mContactIconView.setImageResourceUri(null);
+ }
+ }
+
+ private void setNameTextView() {
+ final String displayName = getDisplayName();
+ if (TextUtils.isEmpty(displayName)) {
+ mNameTextView.setVisibility(GONE);
+ } else {
+ mNameTextView.setVisibility(VISIBLE);
+ mNameTextView.setText(displayName);
+ }
+ }
+
+ @Override
+ public void onPersonDataUpdated(final PersonItemData data) {
+ mBinding.ensureBound(data);
+ updateViewAppearance();
+ }
+
+ @Override
+ public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
+ mBinding.ensureBound(data);
+ updateViewAppearance();
+ }
+
+ public Intent getClickIntent() {
+ return mBinding.getData().getClickIntent();
+ }
+}
diff --git a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java b/src/com/android/messaging/ui/PlaceholderInsetDrawable.java
new file mode 100644
index 0000000..dda2e7b
--- /dev/null
+++ b/src/com/android/messaging/ui/PlaceholderInsetDrawable.java
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+
+/**
+ * A "placeholder" drawable that has the same sizing properties as the real UI element it
+ * replaces.
+ *
+ * This is an InsetDrawable that takes a placeholder drawable (an animation list, or simply
+ * a color drawable) and place it in the center of the inset drawable that's sized to the
+ * requested source width and height of the image that it replaces. Unlike the base
+ * InsetDrawable, this implementation returns the true width and height of the real image
+ * that it's placeholding, instead of the intrinsic size of the contained drawable, so that
+ * when used in an ImageView, it may be positioned/scaled/cropped the same way the real
+ * image is.
+ */
+public class PlaceholderInsetDrawable extends InsetDrawable {
+ // The dimensions of the real image that this drawable is replacing.
+ private final int mSourceWidth;
+ private final int mSourceHeight;
+
+ /**
+ * Given a source drawable, wraps it around in this placeholder drawable by placing the
+ * drawable at the center of the container if possible (or fill the container if the
+ * drawable doesn't have intrinsic size such as color drawable).
+ */
+ public static PlaceholderInsetDrawable fromDrawable(final Drawable drawable,
+ final int sourceWidth, final int sourceHeight) {
+ final int drawableWidth = drawable.getIntrinsicWidth();
+ final int drawableHeight = drawable.getIntrinsicHeight();
+ final int insetHorizontal = drawableWidth < 0 || drawableWidth > sourceWidth ?
+ 0 : (sourceWidth - drawableWidth) / 2;
+ final int insetVertical = drawableHeight < 0 || drawableHeight > sourceHeight ?
+ 0 : (sourceHeight - drawableHeight) / 2;
+ return new PlaceholderInsetDrawable(drawable, insetHorizontal, insetVertical,
+ insetHorizontal, insetVertical, sourceWidth, sourceHeight);
+ }
+
+ private PlaceholderInsetDrawable(final Drawable drawable, final int insetLeft,
+ final int insetTop, final int insetRight, final int insetBottom,
+ final int sourceWidth, final int sourceHeight) {
+ super(drawable, insetLeft, insetTop, insetRight, insetBottom);
+ mSourceWidth = sourceWidth;
+ mSourceHeight = sourceHeight;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mSourceWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mSourceHeight;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/PlainTextEditText.java b/src/com/android/messaging/ui/PlainTextEditText.java
new file mode 100644
index 0000000..8d6e784
--- /dev/null
+++ b/src/com/android/messaging/ui/PlainTextEditText.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+/**
+ * We want the EditText used in Conversations to convert text to plain text on paste. This
+ * conversion would happen anyway on send, so without this class it could appear to the user
+ * that we would send e.g. bold or italic formatting, but in the sent message it would just be
+ * plain text.
+ */
+public class PlainTextEditText extends EditText {
+ private static final char OBJECT_UNICODE = '\uFFFC';
+
+ public PlainTextEditText(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ // Intercept and modify the paste event. Let everything else through unchanged.
+ @Override
+ public boolean onTextContextMenuItem(final int id) {
+ if (id == android.R.id.paste) {
+ // We can use this to know where the text position was originally before we pasted
+ final int selectionStartPrePaste = getSelectionStart();
+
+ // Let the EditText's normal paste routine fire, then modify the content after.
+ // This is simpler than re-implementing the paste logic, which we'd have to do
+ // if we want to get the text from the clipboard ourselves and then modify it.
+
+ final boolean result = super.onTextContextMenuItem(id);
+ CharSequence text = getText();
+ int selectionStart = getSelectionStart();
+ int selectionEnd = getSelectionEnd();
+
+ // There is an option in the Chrome mobile app to copy image; however, instead of the
+ // image in the form of the uri, Chrome gives us the html source for the image, which
+ // the platform paste code turns into the unicode object character. The below section
+ // of code looks for that edge case and replaces it with the url for the image.
+ final int startIndex = selectionStart - 1;
+ final int pasteStringLength = selectionStart - selectionStartPrePaste;
+ // Only going to handle the case where the pasted object is the image
+ if (pasteStringLength == 1 && text.charAt(startIndex) == OBJECT_UNICODE) {
+ final ClipboardManager clipboard =
+ (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ final ClipData clip = clipboard.getPrimaryClip();
+ if (clip != null) {
+ ClipData.Item item = clip.getItemAt(0);
+ StringBuilder sb = new StringBuilder(text);
+ final String url = item.getText().toString();
+ sb.replace(selectionStartPrePaste, selectionStart, url);
+ text = sb.toString();
+ selectionStart = selectionStartPrePaste + url.length();
+ selectionEnd = selectionStart;
+ }
+ }
+
+ // This removes the formatting due to the conversion to string.
+ setText(text.toString(), BufferType.EDITABLE);
+
+ // Restore the cursor selection state.
+ setSelection(selectionStart, selectionEnd);
+ return result;
+ } else {
+ return super.onTextContextMenuItem(id);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/PlaybackStateView.java b/src/com/android/messaging/ui/PlaybackStateView.java
new file mode 100644
index 0000000..8d9aac7
--- /dev/null
+++ b/src/com/android/messaging/ui/PlaybackStateView.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * An interface for a UI element ("View") that reflects the playback state of a piece of media
+ * content. It needs to support the ability to take common playback commands (play, pause, stop,
+ * restart) and reflect the state in UI (through timer or progress bar etc.)
+ */
+public interface PlaybackStateView {
+ /**
+ * Restart the playback.
+ */
+ void restart();
+
+ /**
+ * Reset ("stop") the playback to the starting position.
+ */
+ void reset();
+
+ /**
+ * Resume the playback, or start it if it hasn't been started yet.
+ */
+ void resume();
+
+ /**
+ * Pause the playback.
+ */
+ void pause();
+}
diff --git a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java b/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java
new file mode 100644
index 0000000..c731164
--- /dev/null
+++ b/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+
+import com.android.messaging.datamodel.NoConfirmationSmsSendService;
+import com.android.messaging.util.LogUtil;
+
+public class RemoteInputEntrypointActivity extends BaseBugleActivity {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (intent == null) {
+ LogUtil.w(TAG, "No intent attached");
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ // Perform some action depending on the intent
+ String action = intent.getAction();
+ if (Intent.ACTION_SENDTO.equals(action)) {
+ // Build and send the intent
+ final Intent sendIntent = new Intent(this, NoConfirmationSmsSendService.class);
+ sendIntent.setAction(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE);
+ sendIntent.putExtras(intent);
+ // Wear apparently passes all of its extras via the clip data. Must pass it along.
+ sendIntent.setClipData(intent.getClipData());
+ startService(sendIntent);
+ setResult(RESULT_OK);
+ } else {
+ LogUtil.w(TAG, "Unrecognized intent action: " + action);
+ setResult(RESULT_CANCELED);
+ }
+ // This activity should never stick around after processing the intent
+ finish();
+ }
+}
diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java b/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java
new file mode 100644
index 0000000..6b3e84b
--- /dev/null
+++ b/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+
+/**
+ * Activity to contain the dialog of warning sms storage low.
+ */
+public class SmsStorageLowWarningActivity extends BaseBugleFragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ SmsStorageLowWarningFragment fragment =
+ SmsStorageLowWarningFragment.newInstance();
+ ft.add(fragment, null/*tag*/);
+ ft.commit();
+ }
+}
diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java b/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java
new file mode 100644
index 0000000..3ebfdcf
--- /dev/null
+++ b/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java
@@ -0,0 +1,267 @@
+/*
+ * 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;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.action.HandleLowStorageAction;
+import com.android.messaging.sms.SmsReleaseStorage;
+import com.android.messaging.sms.SmsReleaseStorage.Duration;
+import com.android.messaging.sms.SmsStorageStatusManager;
+import com.android.messaging.util.Assert;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Dialog to show the sms storage low warning
+ */
+public class SmsStorageLowWarningFragment extends Fragment {
+ private SmsStorageLowWarningFragment() {
+ }
+
+ public static SmsStorageLowWarningFragment newInstance() {
+ return new SmsStorageLowWarningFragment();
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ final ChooseActionDialogFragment dialog = ChooseActionDialogFragment.newInstance();
+ dialog.setTargetFragment(this, 0/*requestCode*/);
+ dialog.show(ft, null/*tag*/);
+ }
+
+ /**
+ * Perform confirm action for a specific action
+ *
+ * @param actionIndex
+ */
+ private void confirm(final int actionIndex) {
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ final ConfirmationDialog dialog = ConfirmationDialog.newInstance(actionIndex);
+ dialog.setTargetFragment(this, 0/*requestCode*/);
+ dialog.show(ft, null/*tag*/);
+ }
+
+ /**
+ * The dialog is cancelled at any step
+ */
+ private void cancel() {
+ getActivity().finish();
+ }
+
+ /**
+ * The dialog to show for user to choose what delete actions to take when storage is low
+ */
+ private static class ChooseActionDialogFragment extends DialogFragment {
+ public static ChooseActionDialogFragment newInstance() {
+ return new ChooseActionDialogFragment();
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+ final LayoutInflater inflater = getActivity().getLayoutInflater();
+ final View dialogLayout = inflater.inflate(
+ R.layout.sms_storage_low_warning_dialog, null);
+ final ListView actionListView = (ListView) dialogLayout.findViewById(
+ R.id.free_storage_action_list);
+ final List<String> actions = loadFreeStorageActions(getActivity().getResources());
+ final ActionListAdapter listAdapter = new ActionListAdapter(getActivity(), actions);
+ actionListView.setAdapter(listAdapter);
+
+ builder.setTitle(R.string.sms_storage_low_title)
+ .setView(dialogLayout)
+ .setNegativeButton(R.string.ignore, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ final Dialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+
+ @Override
+ public void onCancel(final DialogInterface dialog) {
+ ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
+ }
+
+ private class ActionListAdapter extends ArrayAdapter<String> {
+ public ActionListAdapter(final Context context, final List<String> actions) {
+ super(context, R.layout.sms_free_storage_action_item_view, actions);
+ }
+
+ @Override
+ public View getView(final int position, final View view, final ViewGroup parent) {
+ TextView actionItemView;
+ if (view == null || !(view instanceof TextView)) {
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ actionItemView = (TextView) inflater.inflate(
+ R.layout.sms_free_storage_action_item_view, parent, false);
+ } else {
+ actionItemView = (TextView) view;
+ }
+
+ final String action = getItem(position);
+ actionItemView.setText(action);
+ actionItemView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ dismiss();
+ ((SmsStorageLowWarningFragment) getTargetFragment()).confirm(position);
+ }
+ });
+ return actionItemView;
+ }
+ }
+ }
+
+ private static final String KEY_ACTION_INDEX = "action_index";
+
+ /**
+ * The dialog to confirm user's delete action
+ */
+ private static class ConfirmationDialog extends DialogFragment {
+ private Duration mDuration;
+ private String mDurationString;
+
+ public static ConfirmationDialog newInstance(final int actionIndex) {
+ final ConfirmationDialog dialog = new ConfirmationDialog();
+ final Bundle args = new Bundle();
+ args.putInt(KEY_ACTION_INDEX, actionIndex);
+ dialog.setArguments(args);
+ return dialog;
+ }
+
+ @Override
+ public void onCancel(final DialogInterface dialog) {
+ ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ mDuration = SmsReleaseStorage.parseMessageRetainingDuration();
+ mDurationString = SmsReleaseStorage.getMessageRetainingDurationString(mDuration);
+
+ final int actionIndex = getArguments().getInt(KEY_ACTION_INDEX);
+ if (actionIndex < 0 || actionIndex > 1) {
+ return null;
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.sms_storage_low_title)
+ .setMessage(getConfirmDialogMessage(actionIndex))
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ dismiss();
+ ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
+ }
+ })
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ dismiss();
+ handleAction(actionIndex);
+ getActivity().finish();
+ SmsStorageStatusManager.cancelStorageLowNotification();
+ }
+ });
+ return builder.create();
+ }
+
+ private void handleAction(final int actionIndex) {
+ final long durationInMillis =
+ SmsReleaseStorage.durationToTimeInMillis(mDuration);
+ switch (actionIndex) {
+ case 0:
+ HandleLowStorageAction.handleDeleteMediaMessages(durationInMillis);
+ break;
+
+ case 1:
+ HandleLowStorageAction.handleDeleteOldMessages(durationInMillis);
+ break;
+
+ default:
+ Assert.fail("Unsupported action");
+ break;
+ }
+ }
+
+ /**
+ * Get the confirm dialog text for a specific delete action
+ * @param index The action index
+ * @return
+ */
+ private String getConfirmDialogMessage(final int index) {
+ switch (index) {
+ case 0:
+ return getString(R.string.delete_all_media_confirmation, mDurationString);
+ case 1:
+ return getString(R.string.delete_oldest_messages_confirmation, mDurationString);
+ case 2:
+ return getString(R.string.auto_delete_oldest_messages_confirmation,
+ mDurationString);
+ }
+ throw new IllegalArgumentException(
+ "SmsStorageLowWarningFragment: invalid action index " + index);
+ }
+ }
+
+ /**
+ * Load the text of delete message actions
+ *
+ * @param resources
+ * @return
+ */
+ private static List<String> loadFreeStorageActions(final Resources resources) {
+ final Duration duration = SmsReleaseStorage.parseMessageRetainingDuration();
+ final String durationString = SmsReleaseStorage.getMessageRetainingDurationString(duration);
+ final List<String> actions = Lists.newArrayList();
+ actions.add(resources.getString(R.string.delete_all_media));
+ actions.add(resources.getString(R.string.delete_oldest_messages, durationString));
+
+ // TODO: Auto-purging is disabled for Bugle V1.
+ // actions.add(resources.getString(R.string.auto_delete_oldest_messages, durationString));
+ return actions;
+ }
+}
diff --git a/src/com/android/messaging/ui/SnackBar.java b/src/com/android/messaging/ui/SnackBar.java
new file mode 100644
index 0000000..a278040
--- /dev/null
+++ b/src/com/android/messaging/ui/SnackBar.java
@@ -0,0 +1,314 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SnackBar {
+ public static final int LONG_DURATION_IN_MS = 5000;
+ public static final int SHORT_DURATION_IN_MS = 1000;
+ public static final int MAX_DURATION_IN_MS = 10000;
+
+ public interface SnackBarListener {
+ void onActionClick();
+ }
+
+ /**
+ * Defines an action to be performed when the user clicks on the action button on the snack bar
+ */
+ public static class Action {
+ private final Runnable mActionRunnable;
+ private final String mActionLabel;
+
+ public final static int SNACK_BAR_UNDO = 0;
+ public final static int SNACK_BAR_RETRY = 1;
+
+ private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) {
+ mActionRunnable = actionRunnable;
+ mActionLabel = actionLabel;
+ }
+
+ Runnable getActionRunnable() {
+ return mActionRunnable;
+ }
+
+ String getActionLabel() {
+ return mActionLabel;
+ }
+
+ public static Action createUndoAction(final Runnable undoRunnable) {
+ return createCustomAction(undoRunnable, Factory.get().getApplicationContext()
+ .getString(R.string.snack_bar_undo));
+ }
+
+ public static Action createRetryAction(final Runnable retryRunnable) {
+ return createCustomAction(retryRunnable, Factory.get().getApplicationContext()
+ .getString(R.string.snack_bar_retry));
+ }
+
+
+ public static Action createCustomAction(final Runnable runnable, final String actionLabel) {
+ return new Action(runnable, actionLabel);
+ }
+ }
+
+ /**
+ * Defines the placement of the snack bar (e.g. anchored view, anchor gravity).
+ */
+ public static class Placement {
+ private final View mAnchorView;
+ private final boolean mAnchorAbove;
+
+ private Placement(@NonNull final View anchorView, final boolean anchorAbove) {
+ Assert.notNull(anchorView);
+ mAnchorView = anchorView;
+ mAnchorAbove = anchorAbove;
+ }
+
+ public View getAnchorView() {
+ return mAnchorView;
+ }
+
+ public boolean getAnchorAbove() {
+ return mAnchorAbove;
+ }
+
+ /**
+ * Anchor the snack bar above the given {@code anchorView}.
+ */
+ public static Placement above(final View anchorView) {
+ return new Placement(anchorView, true);
+ }
+
+ /**
+ * Anchor the snack bar below the given {@code anchorView}.
+ */
+ public static Placement below(final View anchorView) {
+ return new Placement(anchorView, false);
+ }
+ }
+
+ public static class Builder {
+ private static final List<SnackBarInteraction> NO_INTERACTIONS =
+ new ArrayList<SnackBarInteraction>();
+
+ private final Context mContext;
+ private final SnackBarManager mSnackBarManager;
+
+ private String mSnackBarMessage;
+ private int mDuration = LONG_DURATION_IN_MS;
+ private List<SnackBarInteraction> mInteractions = NO_INTERACTIONS;
+ private Action mAction;
+ private Placement mPlacement;
+ // The parent view is only used to get a window token and doesn't affect the layout
+ private View mParentView;
+
+ public Builder(final SnackBarManager snackBarManager, final View parentView) {
+ Assert.notNull(snackBarManager);
+ Assert.notNull(parentView);
+ mSnackBarManager = snackBarManager;
+ mContext = parentView.getContext();
+ mParentView = parentView;
+ }
+
+ public Builder setText(final String snackBarMessage) {
+ Assert.isTrue(!TextUtils.isEmpty(snackBarMessage));
+ mSnackBarMessage = snackBarMessage;
+ return this;
+ }
+
+ public Builder setAction(final Action action) {
+ mAction = action;
+ return this;
+ }
+
+ /**
+ * Sets the duration to show this toast for in milliseconds.
+ */
+ public Builder setDuration(final int duration) {
+ Assert.isTrue(0 < duration && duration < MAX_DURATION_IN_MS);
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * Sets the components that this toast's animation will interact with. These components may
+ * be animated to make room for the toast.
+ */
+ public Builder withInteractions(final List<SnackBarInteraction> interactions) {
+ mInteractions = interactions;
+ return this;
+ }
+
+ /**
+ * Place the snack bar with the given placement requirement.
+ */
+ public Builder withPlacement(final Placement placement) {
+ Assert.isNull(mPlacement);
+ mPlacement = placement;
+ return this;
+ }
+
+ public SnackBar build() {
+ return new SnackBar(this);
+ }
+
+ public void show() {
+ mSnackBarManager.show(build());
+ }
+ }
+
+ private final View mRootView;
+ private final Context mContext;
+ private final View mSnackBarView;
+ private final String mText;
+ private final int mDuration;
+ private final List<SnackBarInteraction> mInteractions;
+ private final Action mAction;
+ private final Placement mPlacement;
+ private final TextView mActionTextView;
+ private final TextView mMessageView;
+ private final FrameLayout mMessageWrapper;
+ private final View mParentView;
+
+ private SnackBarListener mListener;
+
+ private SnackBar(final Builder builder) {
+ mContext = builder.mContext;
+ mRootView = LayoutInflater.from(mContext).inflate(R.layout.snack_bar,
+ null /* WindowManager will show this in show() below */);
+ mSnackBarView = mRootView.findViewById(R.id.snack_bar);
+ mText = builder.mSnackBarMessage;
+ mDuration = builder.mDuration;
+ mAction = builder.mAction;
+ mPlacement = builder.mPlacement;
+ mParentView = builder.mParentView;
+ if (builder.mInteractions == null) {
+ mInteractions = new ArrayList<SnackBarInteraction>();
+ } else {
+ mInteractions = builder.mInteractions;
+ }
+
+ mActionTextView = (TextView) mRootView.findViewById(R.id.snack_bar_action);
+ mMessageView = (TextView) mRootView.findViewById(R.id.snack_bar_message);
+ mMessageWrapper = (FrameLayout) mRootView.findViewById(R.id.snack_bar_message_wrapper);
+
+ setUpButton();
+ setUpTextLines();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public View getRootView() {
+ return mRootView;
+ }
+
+ public View getParentView() {
+ return mParentView;
+ }
+
+ public View getSnackBarView() {
+ return mSnackBarView;
+ }
+
+ public String getMessageText() {
+ return mText;
+ }
+
+ public String getActionLabel() {
+ if (mAction == null) {
+ return null;
+ }
+ return mAction.getActionLabel();
+ }
+
+ public int getDuration() {
+ return mDuration;
+ }
+
+ public Placement getPlacement() {
+ return mPlacement;
+ }
+
+ public List<SnackBarInteraction> getInteractions() {
+ return mInteractions;
+ }
+
+ public void setEnabled(final boolean enabled) {
+ mActionTextView.setClickable(enabled);
+ }
+
+ public void setListener(final SnackBarListener snackBarListener) {
+ mListener = snackBarListener;
+ }
+
+ private void setUpButton() {
+ if (mAction == null || mAction.getActionRunnable() == null) {
+ mActionTextView.setVisibility(View.GONE);
+ // In the XML layout we add left/right padding to the button to add space between
+ // the message text and the button and on the right side we add padding to put space
+ // between the button and the edge of the snack bar. This is so the button can use the
+ // padding area as part of it's click target. Since we have no button, we need to put
+ // some margin on the right side. While the left margin is already set on the wrapper,
+ // we're setting it again to not have to make a special case for RTL.
+ final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams();
+ final int leftRightMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.snack_bar_left_right_margin);
+ lp.leftMargin = leftRightMargin;
+ lp.rightMargin = leftRightMargin;
+ mMessageWrapper.setLayoutParams(lp);
+ } else {
+ mActionTextView.setVisibility(View.VISIBLE);
+ mActionTextView.setText(mAction.getActionLabel());
+ mActionTextView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mAction.getActionRunnable().run();
+ if (mListener != null) {
+ mListener.onActionClick();
+ }
+ }
+ });
+ }
+ }
+
+ private void setUpTextLines() {
+ if (mText == null) {
+ mMessageView.setVisibility(View.GONE);
+ } else {
+ mMessageView.setVisibility(View.VISIBLE);
+ mMessageView.setText(mText);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/SnackBarInteraction.java b/src/com/android/messaging/ui/SnackBarInteraction.java
new file mode 100644
index 0000000..f723caa
--- /dev/null
+++ b/src/com/android/messaging/ui/SnackBarInteraction.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * An interface that defines how a component can be animated with an {@link SnackBar}.
+ */
+public interface SnackBarInteraction {
+ /**
+ * Returns the animator that will be run in reaction to the given SnackBar being shown.
+ *
+ * Implementations may return null here if it determines that the given SnackBar does not need
+ * to animate this component.
+ */
+ ViewPropertyAnimator animateOnSnackBarShow(SnackBar snackBar);
+
+ /**
+ * Returns the animator that will be run in reaction to the given SnackBar being dismissed.
+ *
+ * Implementations may return null here if it determines that the given SnackBar does not need
+ * to animate this component.
+ */
+ ViewPropertyAnimator animateOnSnackBarDismiss(SnackBar snackBar);
+
+ /**
+ * A basic implementation of {@link SnackBarInteraction} that assumes that the
+ * {@link SnackBar} is always shown with {@link Gravity#BOTTOM} and that the provided View will
+ * always need to be translated up to make room for the SnackBar.
+ */
+ public static class BasicSnackBarInteraction implements SnackBarInteraction {
+ private final View mView;
+
+ public BasicSnackBarInteraction(final View view) {
+ mView = Preconditions.checkNotNull(view);
+ }
+
+ @Override
+ public ViewPropertyAnimator animateOnSnackBarShow(final SnackBar snackBar) {
+ final View rootView = snackBar.getRootView();
+ return mView.animate().translationY(-rootView.getMeasuredHeight());
+ }
+
+ @Override
+ public ViewPropertyAnimator animateOnSnackBarDismiss(final SnackBar snackBar) {
+ return mView.animate().translationY(0);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/SnackBarManager.java b/src/com/android/messaging/ui/SnackBarManager.java
new file mode 100644
index 0000000..e107999
--- /dev/null
+++ b/src/com/android/messaging/ui/SnackBarManager.java
@@ -0,0 +1,365 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.WindowManager;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.ui.SnackBar.Placement;
+import com.android.messaging.ui.SnackBar.SnackBarListener;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.TextUtil;
+import com.android.messaging.util.UiUtils;
+import com.google.common.base.Joiner;
+
+import java.util.List;
+
+public class SnackBarManager {
+
+ private static SnackBarManager sInstance;
+
+ public static SnackBarManager get() {
+ if (sInstance == null) {
+ synchronized (SnackBarManager.class) {
+ if (sInstance == null) {
+ sInstance = new SnackBarManager();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ private final Runnable mDismissRunnable = new Runnable() {
+ @Override
+ public void run() {
+ dismiss();
+ }
+ };
+
+ private final OnTouchListener mDismissOnTouchListener = new OnTouchListener() {
+ @Override
+ public boolean onTouch(final View view, final MotionEvent event) {
+ // Dismiss the {@link SnackBar} but don't consume the event.
+ dismiss();
+ return false;
+ }
+ };
+
+ private final SnackBarListener mDismissOnUserTapListener = new SnackBarListener() {
+ @Override
+ public void onActionClick() {
+ dismiss();
+ }
+ };
+
+ private final int mTranslationDurationMs;
+ private final Handler mHideHandler;
+
+ private SnackBar mCurrentSnackBar;
+ private SnackBar mLatestSnackBar;
+ private SnackBar mNextSnackBar;
+ private boolean mIsCurrentlyDismissing;
+ private PopupWindow mPopupWindow;
+
+ private SnackBarManager() {
+ mTranslationDurationMs = Factory.get().getApplicationContext().getResources().getInteger(
+ R.integer.snackbar_translation_duration_ms);
+ mHideHandler = new Handler();
+ }
+
+ public SnackBar getLatestSnackBar() {
+ return mLatestSnackBar;
+ }
+
+ public SnackBar.Builder newBuilder(final View parentView) {
+ return new SnackBar.Builder(this, parentView);
+ }
+
+ /**
+ * The given snackBar is not guaranteed to be shown. If the previous snackBar is animating away,
+ * and another snackBar is requested to show after this one, this snackBar will be skipped.
+ */
+ public void show(final SnackBar snackBar) {
+ Assert.notNull(snackBar);
+
+ if (mCurrentSnackBar != null) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar, but currentSnackBar was not null.");
+
+ // Dismiss the current snack bar. That will cause the next snack bar to be shown on
+ // completion.
+ mNextSnackBar = snackBar;
+ mLatestSnackBar = snackBar;
+ dismiss();
+ return;
+ }
+
+ mCurrentSnackBar = snackBar;
+ mLatestSnackBar = snackBar;
+
+ // We want to know when either button was tapped so we can dismiss.
+ snackBar.setListener(mDismissOnUserTapListener);
+
+ // Cancel previous dismisses & set dismiss for the delay time.
+ mHideHandler.removeCallbacks(mDismissRunnable);
+ mHideHandler.postDelayed(mDismissRunnable, snackBar.getDuration());
+
+ snackBar.setEnabled(false);
+
+ // For some reason, the addView function does not respect layoutParams.
+ // We need to explicitly set it first here.
+ final View rootView = snackBar.getRootView();
+
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar: " + snackBar);
+ }
+ // Measure the snack bar root view so we know how much to translate by.
+ measureSnackBar(snackBar);
+ mPopupWindow = new PopupWindow(snackBar.getContext());
+ mPopupWindow.setWidth(LayoutParams.MATCH_PARENT);
+ mPopupWindow.setHeight(LayoutParams.WRAP_CONTENT);
+ mPopupWindow.setBackgroundDrawable(null);
+ mPopupWindow.setContentView(rootView);
+ final Placement placement = snackBar.getPlacement();
+ if (placement == null) {
+ mPopupWindow.showAtLocation(
+ snackBar.getParentView(), Gravity.BOTTOM | Gravity.START,
+ 0, getScreenBottomOffset(snackBar));
+ } else {
+ final View anchorView = placement.getAnchorView();
+
+ // You'd expect PopupWindow.showAsDropDown to ensure the popup moves with the anchor
+ // view, which it does for scrolling, but not layout changes, so we have to manually
+ // update while the snackbar is showing
+ final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mPopupWindow.update(anchorView, 0, getRelativeOffset(snackBar),
+ anchorView.getWidth(), LayoutParams.WRAP_CONTENT);
+ }
+ };
+ anchorView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
+ mPopupWindow.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ anchorView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
+ }
+ });
+ mPopupWindow.showAsDropDown(anchorView, 0, getRelativeOffset(snackBar));
+ }
+
+
+ // Animate the toast bar into view.
+ placeSnackBarOffScreen(snackBar);
+ animateSnackBarOnScreen(snackBar).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mCurrentSnackBar.setEnabled(true);
+ makeCurrentSnackBarDismissibleOnTouch();
+ // Fire an accessibility event as needed
+ String snackBarText = snackBar.getMessageText();
+ if (!TextUtils.isEmpty(snackBarText) &&
+ TextUtils.getTrimmedLength(snackBarText) > 0) {
+ snackBarText = snackBarText.trim();
+ final String snackBarActionText = snackBar.getActionLabel();
+ if (!TextUtil.isAllWhitespace(snackBarActionText)) {
+ snackBarText = Joiner.on(", ").join(snackBarText, snackBarActionText);
+ }
+ AccessibilityUtil.announceForAccessibilityCompat(snackBar.getSnackBarView(),
+ null /*accessibilityManager*/, snackBarText);
+ }
+ }
+ });
+
+ // Animate any interaction views out of the way.
+ animateInteractionsOnShow(snackBar);
+ }
+
+ /**
+ * Dismisses the current toast that is showing. If there is a toast waiting to be shown, that
+ * toast will be shown when the current one has been dismissed.
+ */
+ public void dismiss() {
+ mHideHandler.removeCallbacks(mDismissRunnable);
+
+ if (mCurrentSnackBar == null || mIsCurrentlyDismissing) {
+ return;
+ }
+
+ final SnackBar snackBar = mCurrentSnackBar;
+
+ LogUtil.d(LogUtil.BUGLE_TAG, "Dismissing snack bar.");
+ mIsCurrentlyDismissing = true;
+
+ snackBar.setEnabled(false);
+
+ // Animate the toast bar down.
+ final View rootView = snackBar.getRootView();
+ animateSnackBarOffScreen(snackBar).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ rootView.setVisibility(View.GONE);
+ try {
+ mPopupWindow.dismiss();
+ } catch (IllegalArgumentException e) {
+ // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity
+ // has already ended while we were animating
+ }
+
+ mCurrentSnackBar = null;
+ mIsCurrentlyDismissing = false;
+
+ // Show the next toast if one is waiting.
+ if (mNextSnackBar != null) {
+ final SnackBar localNextSnackBar = mNextSnackBar;
+ mNextSnackBar = null;
+ show(localNextSnackBar);
+ }
+ }
+ });
+
+ // Animate any interaction views back.
+ animateInteractionsOnDismiss(snackBar);
+ }
+
+ private void makeCurrentSnackBarDismissibleOnTouch() {
+ // Set touching on the entire view, the {@link SnackBar} itself, as
+ // well as the button's dismiss the toast.
+ mCurrentSnackBar.getRootView().setOnTouchListener(mDismissOnTouchListener);
+ mCurrentSnackBar.getSnackBarView().setOnTouchListener(mDismissOnTouchListener);
+ }
+
+ private void measureSnackBar(final SnackBar snackBar) {
+ final View rootView = snackBar.getRootView();
+ final Point displaySize = new Point();
+ getWindowManager(snackBar.getContext()).getDefaultDisplay().getSize(displaySize);
+ final int widthSpec = ViewGroup.getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(displaySize.x, MeasureSpec.EXACTLY),
+ 0, LayoutParams.MATCH_PARENT);
+ final int heightSpec = ViewGroup.getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(displaySize.y, MeasureSpec.EXACTLY),
+ 0, LayoutParams.WRAP_CONTENT);
+ rootView.measure(widthSpec, heightSpec);
+ }
+
+ private void placeSnackBarOffScreen(final SnackBar snackBar) {
+ final View rootView = snackBar.getRootView();
+ final View snackBarView = snackBar.getSnackBarView();
+ snackBarView.setTranslationY(rootView.getMeasuredHeight());
+ }
+
+ private ViewPropertyAnimator animateSnackBarOnScreen(final SnackBar snackBar) {
+ final View snackBarView = snackBar.getSnackBarView();
+ return normalizeAnimator(snackBarView.animate()).translationX(0).translationY(0);
+ }
+
+ private ViewPropertyAnimator animateSnackBarOffScreen(final SnackBar snackBar) {
+ final View rootView = snackBar.getRootView();
+ final View snackBarView = snackBar.getSnackBarView();
+ return normalizeAnimator(snackBarView.animate()).translationY(rootView.getHeight());
+ }
+
+ private void animateInteractionsOnShow(final SnackBar snackBar) {
+ final List<SnackBarInteraction> interactions = snackBar.getInteractions();
+ for (final SnackBarInteraction interaction : interactions) {
+ if (interaction != null) {
+ final ViewPropertyAnimator animator = interaction.animateOnSnackBarShow(snackBar);
+ if (animator != null) {
+ normalizeAnimator(animator);
+ }
+ }
+ }
+ }
+
+ private void animateInteractionsOnDismiss(final SnackBar snackBar) {
+ final List<SnackBarInteraction> interactions = snackBar.getInteractions();
+ for (final SnackBarInteraction interaction : interactions) {
+ if (interaction != null) {
+ final ViewPropertyAnimator animator =
+ interaction.animateOnSnackBarDismiss(snackBar);
+ if (animator != null) {
+ normalizeAnimator(animator);
+ }
+ }
+ }
+ }
+
+ private ViewPropertyAnimator normalizeAnimator(final ViewPropertyAnimator animator) {
+ return animator
+ .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
+ .setDuration(mTranslationDurationMs);
+ }
+
+ private WindowManager getWindowManager(final Context context) {
+ return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ /**
+ * Get the offset from the bottom of the screen where the snack bar should be placed.
+ */
+ private int getScreenBottomOffset(final SnackBar snackBar) {
+ final WindowManager windowManager = getWindowManager(snackBar.getContext());
+ final DisplayMetrics displayMetrics = new DisplayMetrics();
+ if (OsUtil.isAtLeastL()) {
+ windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
+ } else {
+ windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+ }
+ final int screenHeight = displayMetrics.heightPixels;
+
+ if (OsUtil.isAtLeastL()) {
+ // In L, the navigation bar is included in the space for the popup window, so we have to
+ // offset by the size of the navigation bar
+ final Rect displayRect = new Rect();
+ snackBar.getParentView().getRootView().getWindowVisibleDisplayFrame(displayRect);
+ return screenHeight - displayRect.bottom;
+ }
+
+ return 0;
+ }
+
+ private int getRelativeOffset(final SnackBar snackBar) {
+ final Placement placement = snackBar.getPlacement();
+ Assert.notNull(placement);
+ final View anchorView = placement.getAnchorView();
+ if (placement.getAnchorAbove()) {
+ return -snackBar.getRootView().getMeasuredHeight() - anchorView.getHeight();
+ } else {
+ // Use the default dropdown positioning
+ return 0;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/TestActivity.java b/src/com/android/messaging/ui/TestActivity.java
new file mode 100644
index 0000000..1693660
--- /dev/null
+++ b/src/com/android/messaging/ui/TestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.util.LogUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * An empty activity that can be used to host a fragment or view for unit testing purposes. Lives in
+ * app code vs test code due to requirement of ActivityInstrumentationTestCase2.
+ */
+public class TestActivity extends FragmentActivity {
+ private FragmentEventListener mFragmentEventListener;
+
+ public interface FragmentEventListener {
+ public void onAttachFragment(Fragment fragment);
+ }
+
+ @Override
+ protected void onCreate(final Bundle bundle) {
+ super.onCreate(bundle);
+
+ if (bundle != null) {
+ // The test case may have configured the fragment, and recreating the activity will
+ // lose that configuration. The real activity is the only activity that would know
+ // how to reapply that configuration.
+ throw new IllegalStateException("TestActivity cannot get recreated");
+ }
+
+ // There is a race condition, but this often makes it possible for tests to run with the
+ // key guard up
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+ setContentView(R.layout.test_activity);
+ }
+
+ @VisibleForTesting
+ public void setFragment(final Fragment fragment) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setFragment");
+ getFragmentManager()
+ .beginTransaction()
+ .replace(R.id.test_content, fragment)
+ .commit();
+ getFragmentManager().executePendingTransactions();
+ }
+
+ @VisibleForTesting
+ public void setView(final View view) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setView");
+ ((FrameLayout) findViewById(R.id.test_content)).addView(view);
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ if (mFragmentEventListener != null) {
+ mFragmentEventListener.onAttachFragment(fragment);
+ }
+ }
+
+ public void setFragmentEventListener(final FragmentEventListener fragmentEventListener) {
+ mFragmentEventListener = fragmentEventListener;
+ }
+}
diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java
new file mode 100644
index 0000000..e5f8a52
--- /dev/null
+++ b/src/com/android/messaging/ui/UIIntents.java
@@ -0,0 +1,378 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.util.ConversationIdSet;
+
+/**
+ * A central repository of Intents used to start activities.
+ */
+public abstract class UIIntents {
+ public static UIIntents get() {
+ return Factory.get().getUIIntents();
+ }
+
+ // Intent extras
+ public static final String UI_INTENT_EXTRA_CONVERSATION_ID = "conversation_id";
+
+ // Sending draft data (from share intent / message forwarding) to the ConversationActivity.
+ public static final String UI_INTENT_EXTRA_DRAFT_DATA = "draft_data";
+
+ // The request code for picking image from the Document picker.
+ public static final int REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER = 1400;
+
+ // Indicates what type of notification this applies to (See BugleNotifications:
+ // UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, UPDATE_ALL)
+ public static final String UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE = "notifications_update";
+
+ // Pass a set of conversation id's.
+ public static final String UI_INTENT_EXTRA_CONVERSATION_ID_SET = "conversation_id_set";
+
+ // Sending class zero message to its activity
+ public static final String UI_INTENT_EXTRA_MESSAGE_VALUES = "message_values";
+
+ // For the widget to go to the ConversationList from the Conversation.
+ public static final String UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST = "goto_conv_list";
+
+ // Indicates whether a conversation is launched with custom transition.
+ public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition";
+
+ public static final String ACTION_RESET_NOTIFICATIONS =
+ "com.android.messaging.reset_notifications";
+
+ // Sending VCard uri to VCard detail activity
+ public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri";
+
+ public static final String CMAS_COMPONENT = "com.android.cellbroadcastreceiver";
+
+ // Intent action for local broadcast receiver for conversation self id change.
+ public static final String CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION =
+ "conversation_self_id_change";
+
+ // Conversation self id
+ public static final String UI_INTENT_EXTRA_CONVERSATION_SELF_ID = "conversation_self_id";
+
+ // For opening an APN editor on a particular row in the apn database.
+ public static final String UI_INTENT_EXTRA_APN_ROW_ID = "apn_row_id";
+
+ // Subscription id
+ public static final String UI_INTENT_EXTRA_SUB_ID = "sub_id";
+
+ // Per-Subscription setting activity title
+ public static final String UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE =
+ "per_sub_setting_title";
+
+ // Is application settings launched as the top level settings activity?
+ public static final String UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS = "top_level_settings";
+
+ // Sending attachment uri from widget
+ public static final String UI_INTENT_EXTRA_ATTACHMENT_URI = "attachment_uri";
+
+ // Sending attachment content type from widget
+ public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type";
+
+ public static final String ACTION_WIDGET_CONVERSATION =
+ "com.android.messaging.widget_conversation:";
+
+ public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms";
+
+ public static final String UI_INTENT_EXTRA_SELF_ID = "self_id";
+
+ // Message position to scroll to.
+ public static final String UI_INTENT_EXTRA_MESSAGE_POSITION = "message_position";
+
+ /**
+ * Launch the permission check activity
+ */
+ public abstract void launchPermissionCheckActivity(final Context context);
+
+ public abstract void launchConversationListActivity(final Context context);
+
+ /**
+ * Launch an activity to show a conversation. This method by default provides no additional
+ * activity options.
+ */
+ public void launchConversationActivity(final Context context,
+ final String conversationId, final MessageData draft) {
+ launchConversationActivity(context, conversationId, draft, null,
+ false /* withCustomTransition */);
+ }
+
+ /**
+ * Launch an activity to show a conversation.
+ */
+ public abstract void launchConversationActivity(final Context context,
+ final String conversationId, final MessageData draft, final Bundle activityOptions,
+ final boolean withCustomTransition);
+
+
+ /**
+ * Launch an activity to show conversation with conversation list in back stack.
+ */
+ public abstract void launchConversationActivityWithParentStack(Context context,
+ String conversationId, String smsBody);
+
+ /**
+ * Launch an activity to show a conversation as a new task.
+ */
+ public abstract void launchConversationActivityNewTask(final Context context,
+ final String conversationId);
+
+ /**
+ * Launch an activity to start a new conversation
+ */
+ public abstract void launchCreateNewConversationActivity(final Context context,
+ final MessageData draft);
+
+ /**
+ * Launch debug activity to set MMS config options.
+ */
+ public abstract void launchDebugMmsConfigActivity(final Context context);
+
+ /**
+ * Launch an activity to change settings.
+ */
+ public abstract void launchSettingsActivity(final Context context);
+
+ /**
+ * Launch an activity to add a contact with a given destination.
+ */
+ public abstract void launchAddContactActivity(final Context context, final String destination);
+
+ /**
+ * Launch an activity to show the document picker to pick an image.
+ * @param fragment the requesting fragment
+ */
+ public abstract void launchDocumentImagePicker(final Fragment fragment);
+
+ /**
+ * Launch an activity to show people & options for a given conversation.
+ */
+ public abstract void launchPeopleAndOptionsActivity(final Activity context,
+ final String conversationId);
+
+ /**
+ * Launch an external activity to handle a phone call
+ * @param phoneNumber the phone number to call
+ * @param clickPosition is the location tapped to start this launch for transition use
+ */
+ public abstract void launchPhoneCallActivity(final Context context, final String phoneNumber,
+ final Point clickPosition);
+
+ /**
+ * Launch an activity to show archived conversations.
+ */
+ public abstract void launchArchivedConversationsActivity(final Context context);
+
+ /**
+ * Launch an activity to show blocked participants.
+ */
+ public abstract void launchBlockedParticipantsActivity(final Context context);
+
+ /**
+ * Launch an activity to show a class zero message
+ */
+ public abstract void launchClassZeroActivity(Context context, ContentValues messageValues);
+
+ /**
+ * Launch an activity to let the user forward a message
+ */
+ public abstract void launchForwardMessageActivity(Context context, MessageData message);
+
+ /**
+ * Launch an activity to show details for a VCard
+ */
+ public abstract void launchVCardDetailActivity(Context context, Uri vcardUri);
+
+ /**
+ * Launch an external activity that handles the intent to add VCard to contacts
+ */
+ public abstract void launchSaveVCardToContactsActivity(Context context, Uri vcardUri);
+
+ /**
+ * Launch an activity to let the user select & unselect the list of attachments to send.
+ */
+ public abstract void launchAttachmentChooserActivity(final Activity activity,
+ final String conversationId, final int requestCode);
+
+ /**
+ * Launch full screen video viewer.
+ */
+ public abstract void launchFullScreenVideoViewer(Context context, Uri videoUri);
+
+ /**
+ * Launch full screen photo viewer.
+ */
+ public abstract void launchFullScreenPhotoViewer(Activity activity, Uri initialPhoto,
+ Rect initialPhotoBounds, Uri photosUri);
+
+ /**
+ * Launch an activity to show general app settings
+ * @param topLevel indicates whether the app settings is launched as the top-level settings
+ * activity (instead of SettingsActivity which shows a collapsed view of the app
+ * settings + one settings item per subscription). This is true when there's only one
+ * active SIM in the system so we can show this activity directly.
+ */
+ public abstract void launchApplicationSettingsActivity(Context context, boolean topLevel);
+
+ /**
+ * Launch an activity to show per-subscription settings
+ */
+ public abstract void launchPerSubscriptionSettingsActivity(Context context, int subId,
+ String settingTitle);
+
+ /**
+ * Get a ACTION_VIEW intent
+ * @param url display the data in the url to users
+ */
+ public abstract Intent getViewUrlIntent(final String url);
+
+ /**
+ * Get an intent to launch the ringtone picker
+ * @param title the title to show in the ringtone picker
+ * @param existingUri the currently set uri
+ * @param defaultUri the default uri if none is currently set
+ * @param toneType type of ringtone to pick, maybe any of RingtoneManager.TYPE_*
+ */
+ public abstract Intent getRingtonePickerIntent(final String title, final Uri existingUri,
+ final Uri defaultUri, final int toneType);
+
+ /**
+ * Get an intent to launch the wireless alert viewer.
+ */
+ public abstract Intent getWirelessAlertsIntent();
+
+ /**
+ * Get an intent to launch the dialog for changing the default SMS App.
+ */
+ public abstract Intent getChangeDefaultSmsAppIntent(final Activity activity);
+
+ /**
+ * Broadcast conversation self id change so it may be reflected in the message compose UI.
+ */
+ public abstract void broadcastConversationSelfIdChange(final Context context,
+ final String conversationId, final String conversationSelfId);
+
+ /**
+ * Get a PendingIntent for starting conversation list from notifications.
+ */
+ public abstract PendingIntent getPendingIntentForConversationListActivity(
+ final Context context);
+
+ /**
+ * Get a PendingIntent for starting conversation list from widget.
+ */
+ public abstract PendingIntent getWidgetPendingIntentForConversationListActivity(
+ final Context context);
+
+ /**
+ * Get a PendingIntent for showing a conversation from notifications.
+ */
+ public abstract PendingIntent getPendingIntentForConversationActivity(final Context context,
+ final String conversationId, final MessageData draft);
+
+ /**
+ * Get an Intent for showing a conversation from the widget.
+ */
+ public abstract Intent getIntentForConversationActivity(final Context context,
+ final String conversationId, final MessageData draft);
+
+ /**
+ * Get a PendingIntent for sending a message to a conversation, without opening the Bugle UI.
+ *
+ * <p>This is intended to be used by the Android Wear companion app when sending transcribed
+ * voice replies.
+ */
+ public abstract PendingIntent getPendingIntentForSendingMessageToConversation(
+ final Context context, final String conversationId, final String selfId,
+ final boolean requiresMms, final int requestCode);
+
+ /**
+ * Get a PendingIntent for clearing notifications.
+ *
+ * <p>This is intended to be used by notifications.
+ */
+ public abstract PendingIntent getPendingIntentForClearingNotifications(final Context context,
+ final int updateTargets, final ConversationIdSet conversationIdSet,
+ final int requestCode);
+
+ /**
+ * Get a PendingIntent for showing low storage notifications.
+ */
+ public abstract PendingIntent getPendingIntentForLowStorageNotifications(final Context context);
+
+ /**
+ * Get a PendingIntent for showing a new message to a secondary user.
+ */
+ public abstract PendingIntent getPendingIntentForSecondaryUserNewMessageNotification(
+ final Context context);
+
+ /**
+ * Get an intent for showing the APN editor.
+ */
+ public abstract Intent getApnEditorIntent(final Context context, final String rowId, int subId);
+
+ /**
+ * Get an intent for showing the APN settings.
+ */
+ public abstract Intent getApnSettingsIntent(final Context context, final int subId);
+
+ /**
+ * Get an intent for showing advanced settings.
+ */
+ public abstract Intent getAdvancedSettingsIntent(final Context context);
+
+ /**
+ * Get an intent for the LaunchConversationActivity.
+ */
+ public abstract Intent getLaunchConversationActivityIntent(final Context context);
+
+ /**
+ * Tell MediaScanner to re-scan the specified volume.
+ */
+ public abstract void kickMediaScanner(final Context context, final String volume);
+
+ /**
+ * Launch to browser for a url.
+ */
+ public abstract void launchBrowserForUrl(final Context context, final String url);
+
+ /**
+ * Get a PendingIntent for the widget conversation template.
+ */
+ public abstract PendingIntent getWidgetPendingIntentForConversationActivity(
+ final Context context, final String conversationId, final int requestCode);
+
+ /**
+ * Get a PendingIntent for the conversation widget configuration activity template.
+ */
+ public abstract PendingIntent getWidgetPendingIntentForConfigurationActivity(
+ final Context context, final int appWidgetId);
+
+}
diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java
new file mode 100644
index 0000000..b7db719
--- /dev/null
+++ b/src/com/android/messaging/ui/UIIntentsImpl.java
@@ -0,0 +1,577 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
+import android.provider.MediaStore;
+import android.provider.Telephony;
+import android.support.annotation.Nullable;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+
+import com.android.ex.photo.Intents.PhotoViewIntentBuilder;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.ConversationImagePartsView;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.receiver.NotificationReceiver;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.ui.appsettings.ApnEditorActivity;
+import com.android.messaging.ui.appsettings.ApnSettingsActivity;
+import com.android.messaging.ui.appsettings.ApplicationSettingsActivity;
+import com.android.messaging.ui.appsettings.PerSubscriptionSettingsActivity;
+import com.android.messaging.ui.appsettings.SettingsActivity;
+import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity;
+import com.android.messaging.ui.conversation.ConversationActivity;
+import com.android.messaging.ui.conversation.LaunchConversationActivity;
+import com.android.messaging.ui.conversationlist.ArchivedConversationListActivity;
+import com.android.messaging.ui.conversationlist.ConversationListActivity;
+import com.android.messaging.ui.conversationlist.ForwardMessageActivity;
+import com.android.messaging.ui.conversationsettings.PeopleAndOptionsActivity;
+import com.android.messaging.ui.debug.DebugMmsConfigActivity;
+import com.android.messaging.ui.photoviewer.BuglePhotoViewActivity;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ConversationIdSet;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.UriUtil;
+
+/**
+ * A central repository of Intents used to start activities.
+ */
+public class UIIntentsImpl extends UIIntents {
+ private static final String CELL_BROADCAST_LIST_ACTIVITY =
+ "com.android.cellbroadcastreceiver.CellBroadcastListActivity";
+ private static final String CALL_TARGET_CLICK_KEY = "touchPoint";
+ private static final String CALL_TARGET_CLICK_EXTRA_KEY =
+ "android.telecom.extra.OUTGOING_CALL_EXTRAS";
+ private static final String MEDIA_SCANNER_CLASS =
+ "com.android.providers.media.MediaScannerService";
+ private static final String MEDIA_SCANNER_PACKAGE = "com.android.providers.media";
+ private static final String MEDIA_SCANNER_SCAN_ACTION = "android.media.IMediaScannerService";
+
+ /**
+ * Get an intent which takes you to a conversation
+ */
+ private Intent getConversationActivityIntent(final Context context,
+ final String conversationId, final MessageData draft,
+ final boolean withCustomTransition) {
+ final Intent intent = new Intent(context, ConversationActivity.class);
+
+ // Always try to reuse the same ConversationActivity in the current task so that we don't
+ // have two conversation activities in the back stack.
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ // Otherwise we're starting a new conversation
+ if (conversationId != null) {
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ }
+ if (draft != null) {
+ intent.putExtra(UI_INTENT_EXTRA_DRAFT_DATA, draft);
+
+ // If draft attachments came from an external content provider via a share intent, we
+ // need to propagate the URI permissions through to ConversationActivity. This requires
+ // putting the URIs into the ClipData (setData also works, but accepts only one URI).
+ ClipData clipData = null;
+ for (final MessagePartData partData : draft.getParts()) {
+ if (partData.isAttachment()) {
+ final Uri uri = partData.getContentUri();
+ if (clipData == null) {
+ clipData = ClipData.newRawUri("Attachments", uri);
+ } else {
+ clipData.addItem(new ClipData.Item(uri));
+ }
+ }
+ }
+ if (clipData != null) {
+ intent.setClipData(clipData);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ }
+ if (withCustomTransition) {
+ intent.putExtra(UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION, true);
+ }
+
+ if (!(context instanceof Activity)) {
+ // If the caller supplies an application context, and not an activity context, we must
+ // include this flag
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ return intent;
+ }
+
+ @Override
+ public void launchPermissionCheckActivity(final Context context) {
+ final Intent intent = new Intent(context, PermissionCheckActivity.class);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Get an intent which takes you to the conversation list
+ */
+ private Intent getConversationListActivityIntent(final Context context) {
+ return new Intent(context, ConversationListActivity.class);
+ }
+
+ @Override
+ public void launchConversationListActivity(final Context context) {
+ final Intent intent = getConversationListActivityIntent(context);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Get an intent which shows the low storage warning activity.
+ */
+ private Intent getSmsStorageLowWarningActivityIntent(final Context context) {
+ return new Intent(context, SmsStorageLowWarningActivity.class);
+ }
+
+ @Override
+ public void launchConversationActivity(final Context context,
+ final String conversationId, final MessageData draft, final Bundle activityOptions,
+ final boolean withCustomTransition) {
+ Assert.isTrue(!withCustomTransition || activityOptions != null);
+ final Intent intent = getConversationActivityIntent(context, conversationId, draft,
+ withCustomTransition);
+ context.startActivity(intent, activityOptions);
+ }
+
+ @Override
+ public void launchConversationActivityNewTask(
+ final Context context, final String conversationId) {
+ final Intent intent = getConversationActivityIntent(context, conversationId, null,
+ false /* withCustomTransition */);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void launchConversationActivityWithParentStack(final Context context,
+ final String conversationId, final String smsBody) {
+ final MessageData messageData = TextUtils.isEmpty(smsBody)
+ ? null
+ : MessageData.createDraftSmsMessage(conversationId, null, smsBody);
+ TaskStackBuilder.create(context)
+ .addNextIntentWithParentStack(
+ getConversationActivityIntent(context, conversationId, messageData,
+ false /* withCustomTransition */))
+ .startActivities();
+ }
+
+ @Override
+ public void launchCreateNewConversationActivity(final Context context,
+ final MessageData draft) {
+ final Intent intent = getConversationActivityIntent(context, null, draft,
+ false /* withCustomTransition */);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void launchDebugMmsConfigActivity(final Context context) {
+ context.startActivity(new Intent(context, DebugMmsConfigActivity.class));
+ }
+
+ @Override
+ public void launchAddContactActivity(final Context context, final String destination) {
+ final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ final String destinationType = MmsSmsUtils.isEmailAddress(destination) ?
+ Intents.Insert.EMAIL : Intents.Insert.PHONE;
+ intent.setType(Contacts.CONTENT_ITEM_TYPE);
+ intent.putExtra(destinationType, destination);
+ startExternalActivity(context, intent);
+ }
+
+ @Override
+ public void launchSettingsActivity(final Context context) {
+ final Intent intent = new Intent(context, SettingsActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void launchArchivedConversationsActivity(final Context context) {
+ final Intent intent = new Intent(context, ArchivedConversationListActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void launchBlockedParticipantsActivity(final Context context) {
+ final Intent intent = new Intent(context, BlockedParticipantsActivity.class);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void launchDocumentImagePicker(final Fragment fragment) {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_IMAGE_TYPES);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(ContentType.IMAGE_UNSPECIFIED);
+
+ fragment.startActivityForResult(intent, REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER);
+ }
+
+ @Override
+ public void launchPeopleAndOptionsActivity(final Activity activity,
+ final String conversationId) {
+ final Intent intent = new Intent(activity, PeopleAndOptionsActivity.class);
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ activity.startActivityForResult(intent, 0);
+ }
+
+ @Override
+ public void launchPhoneCallActivity(final Context context, final String phoneNumber,
+ final Point clickPosition) {
+ final Intent intent = new Intent(Intent.ACTION_CALL,
+ Uri.parse(UriUtil.SCHEME_TEL + phoneNumber));
+ final Bundle extras = new Bundle();
+ extras.putParcelable(CALL_TARGET_CLICK_KEY, clickPosition);
+ intent.putExtra(CALL_TARGET_CLICK_EXTRA_KEY, extras);
+ startExternalActivity(context, intent);
+ }
+
+ @Override
+ public void launchClassZeroActivity(final Context context, final ContentValues messageValues) {
+ final Intent classZeroIntent = new Intent(context, ClassZeroActivity.class)
+ .putExtra(UI_INTENT_EXTRA_MESSAGE_VALUES, messageValues)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ context.startActivity(classZeroIntent);
+ }
+
+ @Override
+ public void launchForwardMessageActivity(final Context context, final MessageData message) {
+ final Intent forwardMessageIntent = new Intent(context, ForwardMessageActivity.class)
+ .putExtra(UI_INTENT_EXTRA_DRAFT_DATA, message);
+ context.startActivity(forwardMessageIntent);
+ }
+
+ @Override
+ public void launchVCardDetailActivity(final Context context, final Uri vcardUri) {
+ final Intent vcardDetailIntent = new Intent(context, VCardDetailActivity.class)
+ .putExtra(UI_INTENT_EXTRA_VCARD_URI, vcardUri);
+ context.startActivity(vcardDetailIntent);
+ }
+
+ @Override
+ public void launchSaveVCardToContactsActivity(final Context context, final Uri vcardUri) {
+ Assert.isTrue(MediaScratchFileProvider.isMediaScratchSpaceUri(vcardUri));
+ final Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setDataAndType(vcardUri, ContentType.TEXT_VCARD.toLowerCase());
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startExternalActivity(context, intent);
+ }
+
+ @Override
+ public void launchAttachmentChooserActivity(final Activity activity,
+ final String conversationId, final int requestCode) {
+ final Intent intent = new Intent(activity, AttachmentChooserActivity.class);
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ activity.startActivityForResult(intent, requestCode);
+ }
+
+ @Override
+ public void launchFullScreenVideoViewer(final Context context, final Uri videoUri) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // So we don't see "surrounding" images in Gallery
+ intent.putExtra("SingleItemOnly", true);
+ intent.setDataAndType(videoUri, ContentType.VIDEO_UNSPECIFIED);
+ startExternalActivity(context, intent);
+ }
+
+ @Override
+ public void launchFullScreenPhotoViewer(final Activity activity, final Uri initialPhoto,
+ final Rect initialPhotoBounds, final Uri photosUri) {
+ final PhotoViewIntentBuilder builder =
+ com.android.ex.photo.Intents.newPhotoViewIntentBuilder(
+ activity, BuglePhotoViewActivity.class);
+ builder.setPhotosUri(photosUri.toString());
+ builder.setInitialPhotoUri(initialPhoto.toString());
+ builder.setProjection(ConversationImagePartsView.PhotoViewQuery.PROJECTION);
+
+ // Set the location of the imageView so that the photoviewer can animate from that location
+ // to full screen.
+ builder.setScaleAnimation(initialPhotoBounds.left, initialPhotoBounds.top,
+ initialPhotoBounds.width(), initialPhotoBounds.height());
+
+ builder.setDisplayThumbsFullScreen(false);
+ builder.setMaxInitialScale(8);
+ activity.startActivity(builder.build());
+ activity.overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public void launchApplicationSettingsActivity(final Context context, final boolean topLevel) {
+ final Intent intent = new Intent(context, ApplicationSettingsActivity.class);
+ intent.putExtra(UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, topLevel);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void launchPerSubscriptionSettingsActivity(final Context context, final int subId,
+ final String settingTitle) {
+ final Intent intent = getPerSubscriptionSettingsIntent(context, subId, settingTitle);
+ context.startActivity(intent);
+ }
+
+ @Override
+ public Intent getViewUrlIntent(final String url) {
+ final Uri uri = Uri.parse(url);
+ return new Intent(Intent.ACTION_VIEW, uri);
+ }
+
+ @Override
+ public void broadcastConversationSelfIdChange(final Context context,
+ final String conversationId, final String conversationSelfId) {
+ final Intent intent = new Intent(CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION);
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_SELF_ID, conversationSelfId);
+ LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+ }
+
+ @Override
+ public PendingIntent getPendingIntentForConversationListActivity(final Context context) {
+ final Intent intent = getConversationListActivityIntent(context);
+ return getPendingIntentWithParentStack(context, intent, 0);
+ }
+
+ @Override
+ public PendingIntent getPendingIntentForConversationActivity(final Context context,
+ final String conversationId, final MessageData draft) {
+ final Intent intent = getConversationActivityIntent(context, conversationId, draft,
+ false /* withCustomTransition */);
+ // Ensure that the platform doesn't reuse PendingIntents across conversations
+ intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId));
+ return getPendingIntentWithParentStack(context, intent, 0);
+ }
+
+ @Override
+ public Intent getIntentForConversationActivity(final Context context,
+ final String conversationId, final MessageData draft) {
+ final Intent intent = getConversationActivityIntent(context, conversationId, draft,
+ false /* withCustomTransition */);
+ return intent;
+ }
+
+ @Override
+ public PendingIntent getPendingIntentForSendingMessageToConversation(final Context context,
+ final String conversationId, final String selfId, final boolean requiresMms,
+ final int requestCode) {
+ final Intent intent = new Intent(context, RemoteInputEntrypointActivity.class);
+ intent.setAction(Intent.ACTION_SENDTO);
+ // Ensure that the platform doesn't reuse PendingIntents across conversations
+ intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId));
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_SELF_ID, selfId);
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, requiresMms);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return getPendingIntentWithParentStack(context, intent, requestCode);
+ }
+
+ @Override
+ public PendingIntent getPendingIntentForClearingNotifications(final Context context,
+ final int updateTargets, final ConversationIdSet conversationIdSet,
+ final int requestCode) {
+ final Intent intent = new Intent(context, NotificationReceiver.class);
+ intent.setAction(ACTION_RESET_NOTIFICATIONS);
+ intent.putExtra(UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, updateTargets);
+ if (conversationIdSet != null) {
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID_SET,
+ conversationIdSet.getDelimitedString());
+ }
+ return PendingIntent.getBroadcast(context,
+ requestCode, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ /**
+ * Gets a PendingIntent associated with an Intent to start an Activity. All notifications
+ * that starts an Activity must use this method to get a PendingIntent, which achieves two
+ * goals:
+ * 1. The target activities will be created, with any existing ones destroyed. This ensures
+ * we don't end up with multiple instances of ConversationListActivity, for example.
+ * 2. The target activity, when launched, will have its backstack correctly constructed so
+ * back navigation will work correctly.
+ */
+ private static PendingIntent getPendingIntentWithParentStack(final Context context,
+ final Intent intent, final int requestCode) {
+ final TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
+ // Adds the back stack for the Intent (plus the Intent itself)
+ stackBuilder.addNextIntentWithParentStack(intent);
+ final PendingIntent resultPendingIntent =
+ stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT);
+ return resultPendingIntent;
+ }
+
+ @Override
+ public Intent getRingtonePickerIntent(final String title, final Uri existingUri,
+ final Uri defaultUri, final int toneType) {
+ return new Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
+ .putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, toneType)
+ .putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, title)
+ .putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingUri)
+ .putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultUri);
+ }
+
+ @Override
+ public PendingIntent getPendingIntentForLowStorageNotifications(final Context context) {
+ final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
+ final Intent conversationListIntent = getConversationListActivityIntent(context);
+ taskStackBuilder.addNextIntent(conversationListIntent);
+ taskStackBuilder.addNextIntentWithParentStack(
+ getSmsStorageLowWarningActivityIntent(context));
+
+ return taskStackBuilder.getPendingIntent(
+ 0, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ @Override
+ public PendingIntent getPendingIntentForSecondaryUserNewMessageNotification(
+ final Context context) {
+ return getPendingIntentForConversationListActivity(context);
+ }
+
+ @Override
+ public Intent getWirelessAlertsIntent() {
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(new ComponentName(CMAS_COMPONENT, CELL_BROADCAST_LIST_ACTIVITY));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ @Override
+ public Intent getApnEditorIntent(final Context context, final String rowId, final int subId) {
+ final Intent intent = new Intent(context, ApnEditorActivity.class);
+ intent.putExtra(UI_INTENT_EXTRA_APN_ROW_ID, rowId);
+ intent.putExtra(UI_INTENT_EXTRA_SUB_ID, subId);
+ return intent;
+ }
+
+ @Override
+ public Intent getApnSettingsIntent(final Context context, final int subId) {
+ final Intent intent = new Intent(context, ApnSettingsActivity.class)
+ .putExtra(UI_INTENT_EXTRA_SUB_ID, subId);
+ return intent;
+ }
+
+ @Override
+ public Intent getAdvancedSettingsIntent(final Context context) {
+ return getPerSubscriptionSettingsIntent(context, ParticipantData.DEFAULT_SELF_SUB_ID, null);
+ }
+
+ @Override
+ public Intent getChangeDefaultSmsAppIntent(final Activity activity) {
+ final Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
+ intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, activity.getPackageName());
+ return intent;
+ }
+
+ @Override
+ public void launchBrowserForUrl(final Context context, final String url) {
+ final Intent intent = getViewUrlIntent(url);
+ startExternalActivity(context, intent);
+ }
+
+ /**
+ * Provides a safe way to handle external activities which may not exist.
+ */
+ private void startExternalActivity(final Context context, final Intent intent) {
+ try {
+ context.startActivity(intent);
+ } catch (final ActivityNotFoundException ex) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex);
+ UiUtils.showToastAtBottom(R.string.activity_not_found_message);
+ }
+ }
+
+ private Intent getPerSubscriptionSettingsIntent(final Context context, final int subId,
+ @Nullable final String settingTitle) {
+ return new Intent(context, PerSubscriptionSettingsActivity.class)
+ .putExtra(UI_INTENT_EXTRA_SUB_ID, subId)
+ .putExtra(UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE, settingTitle);
+ }
+
+ @Override
+ public Intent getLaunchConversationActivityIntent(final Context context) {
+ final Intent intent = new Intent(context, LaunchConversationActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
+ return intent;
+ }
+
+ @Override
+ public void kickMediaScanner(final Context context, final String volume) {
+ final Intent intent = new Intent(MEDIA_SCANNER_SCAN_ACTION)
+ .putExtra(MediaStore.MEDIA_SCANNER_VOLUME, volume)
+ .setClassName(MEDIA_SCANNER_PACKAGE, MEDIA_SCANNER_CLASS);
+ context.startService(intent);
+ }
+
+ @Override
+ public PendingIntent getWidgetPendingIntentForConversationActivity(final Context context,
+ final String conversationId, final int requestCode) {
+ final Intent intent = getConversationActivityIntent(context, null, null,
+ false /* withCustomTransition */);
+ if (conversationId != null) {
+ intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+
+ // Set the action to something unique to this conversation so if someone calls this
+ // function again on a different conversation, they'll get a new PendingIntent instead
+ // of the old one.
+ intent.setAction(ACTION_WIDGET_CONVERSATION + conversationId);
+ }
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return getPendingIntentWithParentStack(context, intent, requestCode);
+ }
+
+ @Override
+ public PendingIntent getWidgetPendingIntentForConversationListActivity(
+ final Context context) {
+ final Intent intent = getConversationListActivityIntent(context);
+ return getPendingIntentWithParentStack(context, intent, 0);
+ }
+
+ @Override
+ public PendingIntent getWidgetPendingIntentForConfigurationActivity(final Context context,
+ final int appWidgetId) {
+ final Intent configureIntent = new Intent(context, WidgetPickConversationActivity.class);
+ configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ configureIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+ configureIntent.setData(Uri.parse(configureIntent.toUri(Intent.URI_INTENT_SCHEME)));
+ configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY);
+ return getPendingIntentWithParentStack(context, configureIntent, 0);
+ }
+}
diff --git a/src/com/android/messaging/ui/VCardDetailActivity.java b/src/com/android/messaging/ui/VCardDetailActivity.java
new file mode 100644
index 0000000..fecdc34
--- /dev/null
+++ b/src/com/android/messaging/ui/VCardDetailActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+
+/**
+ * An activity that hosts VCardDetailFragment that shows the content of a VCard that contains one
+ * or more contacts.
+ */
+public class VCardDetailActivity extends BugleActionBarActivity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.vcard_detail_activity);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ Assert.isTrue(fragment instanceof VCardDetailFragment);
+ final Uri vCardUri = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_VCARD_URI);
+ Assert.notNull(vCardUri);
+ final VCardDetailFragment vCardDetailFragment = (VCardDetailFragment) fragment;
+ vCardDetailFragment.setVCardUri(vCardUri);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // Treat the home press as back press so that when we go back to
+ // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
+ onBackPressed();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/VCardDetailAdapter.java b/src/com/android/messaging/ui/VCardDetailAdapter.java
new file mode 100644
index 0000000..cfdd836
--- /dev/null
+++ b/src/com/android/messaging/ui/VCardDetailAdapter.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.media.VCardResourceEntry;
+import com.android.messaging.datamodel.media.VCardResourceEntry.VCardResourceEntryDestinationItem;
+
+import java.util.List;
+
+/**
+ * Displays a list of expandable contact cards shown in the VCardDetailActivity.
+ */
+public class VCardDetailAdapter extends BaseExpandableListAdapter {
+ private final List<VCardResourceEntry> mVCards;
+ private final LayoutInflater mInflater;
+
+ public VCardDetailAdapter(final Context context, final List<VCardResourceEntry> vCards) {
+ mVCards = vCards;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public Object getChild(final int groupPosition, final int childPosition) {
+ return mVCards.get(groupPosition).getContactInfo().get(childPosition);
+ }
+
+ @Override
+ public long getChildId(final int groupPosition, final int childPosition) {
+ return childPosition;
+ }
+
+ @Override
+ public View getChildView(final int groupPosition, final int childPosition,
+ final boolean isLastChild, final View convertView, final ViewGroup parent) {
+ PersonItemView v;
+ if (convertView == null) {
+ v = instantiateView(parent);
+ } else {
+ v = (PersonItemView) convertView;
+ }
+
+ final VCardResourceEntryDestinationItem item = (VCardResourceEntryDestinationItem)
+ getChild(groupPosition, childPosition);
+
+ v.bind(item.getDisplayItem());
+ return v;
+ }
+
+ @Override
+ public int getChildrenCount(final int groupPosition) {
+ return mVCards.get(groupPosition).getContactInfo().size();
+ }
+
+ @Override
+ public Object getGroup(final int groupPosition) {
+ return mVCards.get(groupPosition);
+ }
+
+ @Override
+ public int getGroupCount() {
+ return mVCards.size();
+ }
+
+ @Override
+ public long getGroupId(final int groupPosition) {
+ return groupPosition;
+ }
+
+ @Override
+ public View getGroupView(final int groupPosition, final boolean isExpanded,
+ final View convertView, final ViewGroup parent) {
+ PersonItemView v;
+ if (convertView == null) {
+ v = instantiateView(parent);
+ } else {
+ v = (PersonItemView) convertView;
+ }
+
+ final VCardResourceEntry item = (VCardResourceEntry) getGroup(groupPosition);
+ v.bind(item.getDisplayItem());
+ return v;
+ }
+
+ @Override
+ public boolean isChildSelectable(final int groupPosition, final int childPosition) {
+ return true;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ private PersonItemView instantiateView(final ViewGroup parent) {
+ final PersonItemView v = (PersonItemView) mInflater.inflate(R.layout.people_list_item_view,
+ parent, false);
+ v.setClickable(false);
+ return v;
+ }
+}
diff --git a/src/com/android/messaging/ui/VCardDetailFragment.java b/src/com/android/messaging/ui/VCardDetailFragment.java
new file mode 100644
index 0000000..1b2b88d
--- /dev/null
+++ b/src/com/android/messaging/ui/VCardDetailFragment.java
@@ -0,0 +1,197 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.OnChildClickListener;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.PersonItemData;
+import com.android.messaging.datamodel.data.VCardContactItemData;
+import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.UriUtil;
+
+/**
+ * A fragment that shows the content of a VCard that contains one or more contacts.
+ */
+public class VCardDetailFragment extends Fragment implements PersonItemDataListener {
+ private final Binding<VCardContactItemData> mBinding =
+ BindingBase.createBinding(this);
+ private ExpandableListView mListView;
+ private VCardDetailAdapter mAdapter;
+ private Uri mVCardUri;
+
+ /**
+ * We need to persist the VCard in the scratch directory before letting the user view it.
+ * We save this Uri locally, so that if the user cancels the action and re-perform the add
+ * to contacts action we don't have to persist it again.
+ */
+ private Uri mScratchSpaceUri;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ Assert.notNull(mVCardUri);
+ final View view = inflater.inflate(R.layout.vcard_detail_fragment, container, false);
+ mListView = (ExpandableListView) view.findViewById(R.id.list);
+ mListView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(final View v, final int left, final int top, final int right,
+ final int bottom, final int oldLeft, final int oldTop, final int oldRight,
+ final int oldBottom) {
+ mListView.setIndicatorBounds(mListView.getWidth() - getResources()
+ .getDimensionPixelSize(R.dimen.vcard_detail_group_indicator_width),
+ mListView.getWidth());
+ }
+ });
+ mListView.setOnChildClickListener(new OnChildClickListener() {
+ @Override
+ public boolean onChildClick(ExpandableListView expandableListView, View clickedView,
+ int groupPosition, int childPosition, long childId) {
+ if (!(clickedView instanceof PersonItemView)) {
+ return false;
+ }
+ final Intent intent = ((PersonItemView) clickedView).getClickIntent();
+ if (intent != null) {
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ mBinding.bind(DataModel.get().createVCardContactItemData(getActivity(), mVCardUri));
+ mBinding.getData().setListener(this);
+ return view;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mBinding.isBound()) {
+ mBinding.unbind();
+ }
+ mListView.setAdapter((ExpandableListAdapter) null);
+ }
+
+ private boolean shouldShowAddToContactsItem() {
+ return mBinding.isBound() && mBinding.getData().hasValidVCard();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.vcard_detail_fragment_menu, menu);
+ final MenuItem addToContactsItem = menu.findItem(R.id.action_add_contact);
+ addToContactsItem.setVisible(shouldShowAddToContactsItem());
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_add_contact:
+ mBinding.ensureBound();
+ final Uri vCardUri = mBinding.getData().getVCardUri();
+
+ // We have to do things in the background in case we need to copy the vcard data.
+ new SafeAsyncTask<Void, Void, Uri>() {
+ @Override
+ protected Uri doInBackgroundTimed(final Void... params) {
+ // We can't delete the persisted vCard file because we don't know when to
+ // delete it, since the app that uses it (contacts, dialer) may start or
+ // shut down at any point. Therefore, we rely on the system to clean up
+ // the cache directory for us.
+ return mScratchSpaceUri != null ? mScratchSpaceUri :
+ UriUtil.persistContentToScratchSpace(vCardUri);
+ }
+
+ @Override
+ protected void onPostExecute(final Uri result) {
+ if (result != null) {
+ mScratchSpaceUri = result;
+ if (getActivity() != null) {
+ MediaScratchFileProvider.addUriToDisplayNameEntry(
+ result, mBinding.getData().getDisplayName());
+ UIIntents.get().launchSaveVCardToContactsActivity(getActivity(),
+ result);
+ }
+ }
+ }
+ }.executeOnThreadPool();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ public void setVCardUri(final Uri vCardUri) {
+ Assert.isTrue(!mBinding.isBound());
+ mVCardUri = vCardUri;
+ }
+
+ @Override
+ public void onPersonDataUpdated(final PersonItemData data) {
+ Assert.isTrue(data instanceof VCardContactItemData);
+ mBinding.ensureBound();
+ final VCardContactItemData vCardData = (VCardContactItemData) data;
+ Assert.isTrue(vCardData.hasValidVCard());
+ mAdapter = new VCardDetailAdapter(getActivity(), vCardData.getVCardResource().getVCards());
+ mListView.setAdapter(mAdapter);
+
+ // Expand the contact card if there's only one contact.
+ if (mAdapter.getGroupCount() == 1) {
+ mListView.expandGroup(0);
+ }
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
+ mBinding.ensureBound();
+ UiUtils.showToastAtBottom(R.string.failed_loading_vcard);
+ getActivity().finish();
+ }
+}
diff --git a/src/com/android/messaging/ui/VideoThumbnailView.java b/src/com/android/messaging/ui/VideoThumbnailView.java
new file mode 100644
index 0000000..966336e
--- /dev/null
+++ b/src/com/android/messaging/ui/VideoThumbnailView.java
@@ -0,0 +1,343 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView.ScaleType;
+import android.widget.VideoView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
+import com.android.messaging.datamodel.media.VideoThumbnailRequest;
+import com.android.messaging.util.Assert;
+
+/**
+ * View that encapsulates a video preview (either as a thumbnail image, or video player), and the
+ * a play button to overlay it. Ensures that the video preview maintains the aspect ratio of the
+ * original video while trying to respect minimum width/height and constraining to the available
+ * bounds
+ */
+public class VideoThumbnailView extends FrameLayout {
+ /**
+ * When in this mode the VideoThumbnailView is a lightweight AsyncImageView with an ImageButton
+ * to play the video. Clicking play will launch a full screen player
+ */
+ private static final int MODE_IMAGE_THUMBNAIL = 0;
+
+ /**
+ * When in this mode the VideoThumbnailVideo will include a VideoView, and the play button will
+ * play the video inline. When in this mode, the loop and playOnLoad attributes can be applied
+ * to auto-play or loop the video.
+ */
+ private static final int MODE_PLAYABLE_VIDEO = 1;
+
+ private final int mMode;
+ private final boolean mPlayOnLoad;
+ private final boolean mAllowCrop;
+ private final VideoView mVideoView;
+ private final ImageButton mPlayButton;
+ private final AsyncImageView mThumbnailImage;
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private Uri mVideoSource;
+ private boolean mAnimating;
+ private boolean mVideoLoaded;
+
+ public VideoThumbnailView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ final TypedArray typedAttributes =
+ context.obtainStyledAttributes(attrs, R.styleable.VideoThumbnailView);
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.video_thumbnail_view, this, true);
+
+ mPlayOnLoad = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_playOnLoad, false);
+ final boolean loop =
+ typedAttributes.getBoolean(R.styleable.VideoThumbnailView_loop, false);
+ mMode = typedAttributes.getInt(R.styleable.VideoThumbnailView_mode, MODE_IMAGE_THUMBNAIL);
+ mAllowCrop = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_allowCrop, false);
+
+ mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
+ mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
+
+ if (mMode == MODE_PLAYABLE_VIDEO) {
+ mVideoView = new VideoView(context);
+ // Video view tries to request focus on start which pulls focus from the user's intended
+ // focus when we add this control. Remove focusability to prevent this. The play
+ // button can still be focused
+ mVideoView.setFocusable(false);
+ mVideoView.setFocusableInTouchMode(false);
+ mVideoView.clearFocus();
+ addView(mVideoView, 0, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(final MediaPlayer mediaPlayer) {
+ mVideoLoaded = true;
+ mVideoWidth = mediaPlayer.getVideoWidth();
+ mVideoHeight = mediaPlayer.getVideoHeight();
+ mediaPlayer.setLooping(loop);
+ trySwitchToVideo();
+ }
+ });
+ mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(final MediaPlayer mediaPlayer) {
+ mPlayButton.setVisibility(View.VISIBLE);
+ }
+ });
+ mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(final MediaPlayer mediaPlayer, final int i, final int i2) {
+ return true;
+ }
+ });
+ } else {
+ mVideoView = null;
+ }
+
+ mPlayButton = (ImageButton) findViewById(R.id.video_thumbnail_play_button);
+ if (loop) {
+ mPlayButton.setVisibility(View.GONE);
+ } else {
+ mPlayButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ if (mVideoSource == null) {
+ return;
+ }
+
+ if (mMode == MODE_PLAYABLE_VIDEO) {
+ mVideoView.seekTo(0);
+ start();
+ } else {
+ UIIntents.get().launchFullScreenVideoViewer(getContext(), mVideoSource);
+ }
+ }
+ });
+ mPlayButton.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View view) {
+ // Button prevents long click from propagating up, do it manually
+ VideoThumbnailView.this.performLongClick();
+ return true;
+ }
+ });
+ }
+
+ mThumbnailImage = (AsyncImageView) findViewById(R.id.video_thumbnail_image);
+ if (mAllowCrop) {
+ mThumbnailImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
+ mThumbnailImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
+ mThumbnailImage.setScaleType(ScaleType.CENTER_CROP);
+ } else {
+ // This is the default setting in the layout, so No-op.
+ }
+ final int maxHeight = typedAttributes.getDimensionPixelSize(
+ R.styleable.VideoThumbnailView_android_maxHeight, ImageRequest.UNSPECIFIED_SIZE);
+ if (maxHeight != ImageRequest.UNSPECIFIED_SIZE) {
+ mThumbnailImage.setMaxHeight(maxHeight);
+ mThumbnailImage.setAdjustViewBounds(true);
+ }
+
+ typedAttributes.recycle();
+ }
+
+ @Override
+ protected void onAnimationStart() {
+ super.onAnimationStart();
+ mAnimating = true;
+ }
+
+ @Override
+ protected void onAnimationEnd() {
+ super.onAnimationEnd();
+ mAnimating = false;
+ trySwitchToVideo();
+ }
+
+ private void trySwitchToVideo() {
+ if (mAnimating) {
+ // Don't start video or hide image until after animation completes
+ return;
+ }
+
+ if (!mVideoLoaded) {
+ // Video hasn't loaded, nothing more to do
+ return;
+ }
+
+ if (mPlayOnLoad) {
+ start();
+ } else {
+ mVideoView.seekTo(0);
+ }
+ }
+
+ private boolean hasVideoSize() {
+ return mVideoWidth != ImageRequest.UNSPECIFIED_SIZE &&
+ mVideoHeight != ImageRequest.UNSPECIFIED_SIZE;
+ }
+
+ public void start() {
+ Assert.equals(MODE_PLAYABLE_VIDEO, mMode);
+ mPlayButton.setVisibility(View.GONE);
+ mThumbnailImage.setVisibility(View.GONE);
+ mVideoView.start();
+ }
+
+ // TODO: The check could be added to MessagePartData itself so that all users of MessagePartData
+ // get the right behavior, instead of requiring all the users to do similar checks.
+ private static boolean shouldUseGenericVideoIcon(final boolean incomingMessage) {
+ return incomingMessage && !VideoThumbnailRequest.shouldShowIncomingVideoThumbnails();
+ }
+
+ public void setSource(final MessagePartData part, final boolean incomingMessage) {
+ if (part == null) {
+ clearSource();
+ } else {
+ mVideoSource = part.getContentUri();
+ if (shouldUseGenericVideoIcon(incomingMessage)) {
+ mThumbnailImage.setImageResource(R.drawable.generic_video_icon);
+ mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
+ mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
+ } else {
+ mThumbnailImage.setImageResourceId(
+ new MessagePartVideoThumbnailRequestDescriptor(part));
+ if (mVideoView != null) {
+ mVideoView.setVideoURI(mVideoSource);
+ }
+ mVideoWidth = part.getWidth();
+ mVideoHeight = part.getHeight();
+ }
+ }
+ }
+
+ public void setSource(final Uri videoSource, final boolean incomingMessage) {
+ if (videoSource == null) {
+ clearSource();
+ } else {
+ mVideoSource = videoSource;
+ if (shouldUseGenericVideoIcon(incomingMessage)) {
+ mThumbnailImage.setImageResource(R.drawable.generic_video_icon);
+ mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
+ mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
+ } else {
+ mThumbnailImage.setImageResourceId(
+ new MessagePartVideoThumbnailRequestDescriptor(videoSource));
+ if (mVideoView != null) {
+ mVideoView.setVideoURI(videoSource);
+ }
+ }
+ }
+ }
+
+ private void clearSource() {
+ mVideoSource = null;
+ mThumbnailImage.setImageResourceId(null);
+ mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
+ mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
+ if (mVideoView != null) {
+ mVideoView.setVideoURI(null);
+ }
+ }
+
+ @Override
+ public void setMinimumWidth(final int minWidth) {
+ super.setMinimumWidth(minWidth);
+ if (mVideoView != null) {
+ mVideoView.setMinimumWidth(minWidth);
+ }
+ }
+
+ @Override
+ public void setMinimumHeight(final int minHeight) {
+ super.setMinimumHeight(minHeight);
+ if (mVideoView != null) {
+ mVideoView.setMinimumHeight(minHeight);
+ }
+ }
+
+ public void setColorFilter(int color) {
+ mThumbnailImage.setColorFilter(color);
+ mPlayButton.setColorFilter(color);
+ }
+
+ public void clearColorFilter() {
+ mThumbnailImage.clearColorFilter();
+ mPlayButton.clearColorFilter();
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ if (mAllowCrop) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+ int desiredWidth = 1;
+ int desiredHeight = 1;
+ if (mVideoView != null) {
+ mVideoView.measure(widthMeasureSpec, heightMeasureSpec);
+ }
+ mThumbnailImage.measure(widthMeasureSpec, heightMeasureSpec);
+ if (hasVideoSize()) {
+ desiredWidth = mVideoWidth;
+ desiredHeight = mVideoHeight;
+ } else {
+ desiredWidth = mThumbnailImage.getMeasuredWidth();
+ desiredHeight = mThumbnailImage.getMeasuredHeight();
+ }
+
+ final int minimumWidth = getMinimumWidth();
+ final int minimumHeight = getMinimumHeight();
+
+ // Constrain the scale to fit within the supplied size
+ final float maxScale = Math.max(
+ MeasureSpec.getSize(widthMeasureSpec) / (float) desiredWidth,
+ MeasureSpec.getSize(heightMeasureSpec) / (float) desiredHeight);
+
+ // Scale up to reach minimum width/height
+ final float widthScale = Math.max(1, minimumWidth / (float) desiredWidth);
+ final float heightScale = Math.max(1, minimumHeight / (float) desiredHeight);
+ final float scale = Math.min(maxScale, Math.max(widthScale, heightScale));
+ desiredWidth = (int) (desiredWidth * scale);
+ desiredHeight = (int) (desiredHeight * scale);
+
+ setMeasuredDimension(desiredWidth, desiredHeight);
+ }
+
+ @Override
+ protected void onLayout(final boolean changed, final int left, final int top, final int right,
+ final int bottom) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.layout(0, 0, right - left, bottom - top);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/ViewPagerTabStrip.java b/src/com/android/messaging/ui/ViewPagerTabStrip.java
new file mode 100644
index 0000000..e088296
--- /dev/null
+++ b/src/com/android/messaging/ui/ViewPagerTabStrip.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.util.OsUtil;
+
+public class ViewPagerTabStrip extends LinearLayout {
+ private int mSelectedUnderlineThickness;
+ private final Paint mSelectedUnderlinePaint;
+
+ private int mIndexForSelection;
+ private float mSelectionOffset;
+
+ public ViewPagerTabStrip(Context context) {
+ this(context, null);
+ }
+
+ public ViewPagerTabStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final Resources res = context.getResources();
+
+ mSelectedUnderlineThickness =
+ res.getDimensionPixelSize(R.dimen.pager_tab_underline_selected);
+ int underlineColor = res.getColor(R.color.contact_picker_tab_underline);
+ int backgroundColor = res.getColor(R.color.action_bar_background_color);
+
+ mSelectedUnderlinePaint = new Paint();
+ mSelectedUnderlinePaint.setColor(underlineColor);
+
+ setBackgroundColor(backgroundColor);
+ setWillNotDraw(false);
+ }
+
+ /**
+ * Notifies this view that view pager has been scrolled. We save the tab index
+ * and selection offset for interpolating the position and width of selection
+ * underline.
+ */
+ void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ mIndexForSelection = position;
+ mSelectionOffset = positionOffset;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int childCount = getChildCount();
+
+ // Thick colored underline below the current selection
+ if (childCount > 0) {
+ View selectedTitle = getChildAt(mIndexForSelection);
+ int selectedLeft = selectedTitle.getLeft();
+ int selectedRight = selectedTitle.getRight();
+ final boolean isRtl = isRtl();
+ final boolean hasNextTab = isRtl ? mIndexForSelection > 0
+ : (mIndexForSelection < (getChildCount() - 1));
+ if ((mSelectionOffset > 0.0f) && hasNextTab) {
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mIndexForSelection + (isRtl ? -1 : 1));
+ int nextLeft = nextTitle.getLeft();
+ int nextRight = nextTitle.getRight();
+
+ selectedLeft = (int) (mSelectionOffset * nextLeft +
+ (1.0f - mSelectionOffset) * selectedLeft);
+ selectedRight = (int) (mSelectionOffset * nextRight +
+ (1.0f - mSelectionOffset) * selectedRight);
+ }
+
+ int height = getHeight();
+ canvas.drawRect(selectedLeft, height - mSelectedUnderlineThickness,
+ selectedRight, height, mSelectedUnderlinePaint);
+ }
+ }
+
+ private boolean isRtl() {
+ return OsUtil.isAtLeastJB_MR2() ? getLayoutDirection() == View.LAYOUT_DIRECTION_RTL : false;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/ViewPagerTabs.java b/src/com/android/messaging/ui/ViewPagerTabs.java
new file mode 100644
index 0000000..f31a071
--- /dev/null
+++ b/src/com/android/messaging/ui/ViewPagerTabs.java
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Outline;
+import android.graphics.drawable.ColorDrawable;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Lightweight implementation of ViewPager tabs. This looks similar to traditional actionBar tabs,
+ * but allows for the view containing the tabs to be placed anywhere on screen. Text-related
+ * attributes can also be assigned in XML - these will get propogated to the child TextViews
+ * automatically.
+ *
+ * Note: this file is taken from the AOSP /packages/apps/ContactsCommon/src/com/android/contacts/
+ * common/list/ViewPagerTabs.java. Some platform specific API calls (e.g. ViewOutlineProvider which
+ * assumes L and above) have been modified to support down to Api Level 16.
+ */
+public class ViewPagerTabs extends HorizontalScrollView implements ViewPager.OnPageChangeListener {
+
+ ViewPager mPager;
+ private ViewPagerTabStrip mTabStrip;
+
+ /**
+ * Linearlayout that will contain the TextViews serving as tabs. This is the only child
+ * of the parent HorizontalScrollView.
+ */
+ final int mTextStyle;
+ final ColorStateList mTextColor;
+ final int mTextSize;
+ final boolean mTextAllCaps;
+ int mPrevSelected = -1;
+ int mSidePadding;
+
+ private static final int TAB_SIDE_PADDING_IN_DPS = 10;
+
+ // TODO: This should use <declare-styleable> in the future
+ private static final int[] ATTRS = new int[] {
+ android.R.attr.textSize,
+ android.R.attr.textStyle,
+ android.R.attr.textColor,
+ android.R.attr.textAllCaps
+ };
+
+ /**
+ * Shows a toast with the tab description when long-clicked.
+ */
+ private class OnTabLongClickListener implements OnLongClickListener {
+ final String mTabDescription;
+
+ public OnTabLongClickListener(String tabDescription) {
+ mTabDescription = tabDescription;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ final int[] screenPos = new int[2];
+ getLocationOnScreen(screenPos);
+
+ final Context context = getContext();
+ final int width = getWidth();
+ final int height = getHeight();
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+ Toast toast = Toast.makeText(context, mTabDescription, Toast.LENGTH_SHORT);
+
+ // Show the toast under the tab
+ toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ (screenPos[0] + width / 2) - screenWidth / 2, screenPos[1] + height);
+
+ toast.show();
+ return true;
+ }
+ }
+
+ public ViewPagerTabs(Context context) {
+ this(context, null);
+ }
+
+ public ViewPagerTabs(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ViewPagerTabs(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setFillViewport(true);
+
+ mSidePadding = (int) (getResources().getDisplayMetrics().density * TAB_SIDE_PADDING_IN_DPS);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
+ mTextSize = a.getDimensionPixelSize(0, 0);
+ mTextStyle = a.getInt(1, 0);
+ mTextColor = a.getColorStateList(2);
+ mTextAllCaps = a.getBoolean(3, false);
+
+ mTabStrip = new ViewPagerTabStrip(context);
+ addView(mTabStrip,
+ new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
+ a.recycle();
+
+ // enable shadow casting from view bounds
+ if (OsUtil.isAtLeastL()) {
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRect(0, 0, view.getWidth(), view.getHeight());
+ }
+ });
+ }
+ }
+
+ public void setViewPager(ViewPager viewPager) {
+ mPager = viewPager;
+ addTabs(mPager.getAdapter());
+ }
+
+ private void addTabs(PagerAdapter adapter) {
+ mTabStrip.removeAllViews();
+
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ addTab(adapter.getPageTitle(i), i);
+ }
+ }
+
+ private void addTab(CharSequence tabTitle, final int position) {
+ final TextView textView = new TextView(getContext());
+ textView.setText(tabTitle);
+ textView.setBackgroundResource(R.drawable.contact_picker_tab_background_selector);
+ textView.setGravity(Gravity.CENTER);
+ textView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPager.setCurrentItem(getRtlPosition(position));
+ }
+ });
+
+ // Assign various text appearance related attributes to child views.
+ if (mTextStyle > 0) {
+ textView.setTypeface(textView.getTypeface(), mTextStyle);
+ }
+ if (mTextSize > 0) {
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
+ }
+ if (mTextColor != null) {
+ textView.setTextColor(mTextColor);
+ }
+ textView.setAllCaps(mTextAllCaps);
+ textView.setPadding(mSidePadding, 0, mSidePadding, 0);
+ mTabStrip.addView(textView, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT, 1));
+ // Default to the first child being selected
+ if (position == 0) {
+ mPrevSelected = 0;
+ textView.setSelected(true);
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ position = getRtlPosition(position);
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ mTabStrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ position = getRtlPosition(position);
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) {
+ mTabStrip.getChildAt(mPrevSelected).setSelected(false);
+ }
+ final View selectedChild = mTabStrip.getChildAt(position);
+ selectedChild.setSelected(true);
+
+ // Update scroll position
+ final int scrollPos = selectedChild.getLeft() - (getWidth() - selectedChild.getWidth()) / 2;
+ smoothScrollTo(scrollPos, 0);
+ mPrevSelected = position;
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+
+ private int getRtlPosition(int position) {
+ if (OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources()
+ .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ return mTabStrip.getChildCount() - 1 - position;
+ }
+ return position;
+ }
+
+ public int getSelectedItemPosition() {
+ return mPrevSelected;
+ }
+}
diff --git a/src/com/android/messaging/ui/WidgetPickConversationActivity.java b/src/com/android/messaging/ui/WidgetPickConversationActivity.java
new file mode 100644
index 0000000..60e1318
--- /dev/null
+++ b/src/com/android/messaging/ui/WidgetPickConversationActivity.java
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.ui.conversationlist.ShareIntentFragment;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.widget.WidgetConversationProvider;
+
+public class WidgetPickConversationActivity extends BaseBugleActivity implements
+ ShareIntentFragment.HostInterface {
+
+ private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+
+ // Set the result to CANCELED. This will cause the widget host to cancel
+ // out of the widget placement if they press the back button.
+ setResult(RESULT_CANCELED);
+
+ // Find the widget id from the intent.
+ final Intent intent = getIntent();
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ mAppWidgetId = extras.getInt(
+ AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+
+ // If they gave us an intent without the widget id, just bail.
+ if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ finish();
+ }
+
+ final ShareIntentFragment convPicker = new ShareIntentFragment();
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(ShareIntentFragment.HIDE_NEW_CONVERSATION_BUTTON_KEY, true);
+ convPicker.setArguments(bundle);
+ convPicker.show(getFragmentManager(), "ShareIntentFragment");
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+ if (!AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)) {
+ // Unsupported action.
+ Assert.fail("Unsupported action type: " + action);
+ }
+ }
+
+ @Override
+ public void onConversationClick(final ConversationListItemData conversationListItemData) {
+ saveConversationidPref(mAppWidgetId, conversationListItemData.getConversationId());
+
+ // Push widget update to surface with newly set prefix
+ WidgetConversationProvider.rebuildWidget(this, mAppWidgetId);
+
+ // Make sure we pass back the original appWidgetId
+ Intent resultValue = new Intent();
+ resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ setResult(RESULT_OK, resultValue);
+ finish();
+ }
+
+ @Override
+ public void onCreateConversationClick() {
+ // We should never get here because we're hiding the new conversation button in the
+ // ShareIntentFragment by setting HIDE_NEW_CONVERSATION_BUTTON_KEY in the arguments.
+ finish();
+ }
+
+ // Write the ConversationId to the SharedPreferences object for this widget
+ static void saveConversationidPref(int appWidgetId, String conversationId) {
+ final BuglePrefs prefs = Factory.get().getWidgetPrefs();
+ prefs.putString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, conversationId);
+ }
+
+ // Read the ConversationId from the SharedPreferences object for this widget.
+ public static String getConversationIdPref(int appWidgetId) {
+ final BuglePrefs prefs = Factory.get().getWidgetPrefs();
+ return prefs.getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, null);
+ }
+
+ // Delete the ConversationId preference from the SharedPreferences object for this widget.
+ public static void deleteConversationIdPref(int appWidgetId) {
+ final BuglePrefs prefs = Factory.get().getWidgetPrefs();
+ prefs.remove(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId);
+ }
+
+}
diff --git a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
new file mode 100644
index 0000000..21529c6
--- /dev/null
+++ b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
@@ -0,0 +1,302 @@
+/*
+ * 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.animation;
+
+import android.animation.TypeEvaluator;
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.widget.PopupWindow;
+
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.ThreadUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Animates viewToAnimate from startRect to the place where it is in the layout, viewToAnimate
+ * should be in its final destination location before startAfterLayoutComplete is called.
+ * viewToAnimate will be drawn scaled and offset in a popupWindow.
+ * This class handles the case where the viewToAnimate moves during the animation
+ */
+public class PopupTransitionAnimation extends Animation {
+ /** The view we're animating */
+ private final View mViewToAnimate;
+
+ /** The rect to start the slide in animation from */
+ private final Rect mStartRect;
+
+ /** The rect of the currently animated view */
+ private Rect mCurrentRect;
+
+ /** The rect that we're animating to. This can change during the animation */
+ private final Rect mDestRect;
+
+ /** The bounds of the popup in window coordinates. Does not include notification bar */
+ private final Rect mPopupRect;
+
+ /** The bounds of the action bar in window coordinates. We clip the popup to below this */
+ private final Rect mActionBarRect;
+
+ /** Interpolates between the start and end rect for every animation tick */
+ private final TypeEvaluator<Rect> mRectEvaluator;
+
+ /** The popup window that holds contains the animating view */
+ private PopupWindow mPopupWindow;
+
+ /** The layout root for the popup which is where the animated view is rendered */
+ private View mPopupRoot;
+
+ /** The action bar's view */
+ private final View mActionBarView;
+
+ private Runnable mOnStartCallback;
+ private Runnable mOnStopCallback;
+
+ public PopupTransitionAnimation(final Rect startRect, final View viewToAnimate) {
+ mViewToAnimate = viewToAnimate;
+ mStartRect = startRect;
+ mCurrentRect = new Rect(mStartRect);
+ mDestRect = new Rect();
+ mPopupRect = new Rect();
+ mActionBarRect = new Rect();
+ final Activity activity = (Activity) viewToAnimate.getRootView().getContext();
+ mActionBarView = activity.getWindow().getDecorView().findViewById(
+ android.support.v7.appcompat.R.id.action_bar);
+ mRectEvaluator = RectEvaluatorCompat.create();
+ setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
+ setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
+ setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(final Animation animation) {
+ if (mOnStartCallback != null) {
+ mOnStartCallback.run();
+ }
+ mEvents.append("oAS,");
+ }
+
+ @Override
+ public void onAnimationEnd(final Animation animation) {
+ if (mOnStopCallback != null) {
+ mOnStopCallback.run();
+ }
+ dismiss();
+ mEvents.append("oAE,");
+ }
+
+ @Override
+ public void onAnimationRepeat(final Animation animation) {
+ }
+ });
+ }
+
+ private final StringBuilder mEvents = new StringBuilder();
+ private final Runnable mCleanupRunnable = new Runnable() {
+ @Override
+ public void run() {
+ LogUtil.w(LogUtil.BUGLE_TAG, "PopupTransitionAnimation: " + mEvents);
+ }
+ };
+
+ /**
+ * Ensures the animation is ready before starting the animation.
+ * viewToAnimate must first be layed out so we know where we will animate to
+ */
+ public void startAfterLayoutComplete() {
+ // We want layout to occur, and then we immediately animate it in, so hide it initially to
+ // reduce jank on the first frame
+ mViewToAnimate.setVisibility(View.INVISIBLE);
+ mViewToAnimate.setAlpha(0);
+
+ final Runnable startAnimation = new Runnable() {
+ boolean mRunComplete = false;
+ boolean mFirstTry = true;
+
+ @Override
+ public void run() {
+ if (mRunComplete) {
+ return;
+ }
+
+ mViewToAnimate.getGlobalVisibleRect(mDestRect);
+ // In Android views which are visible but haven't computed their size yet have a
+ // size of 1x1 because anything with a size of 0x0 is considered hidden. We can't
+ // start the animation until after the size is greater than 1x1
+ if (mDestRect.width() <= 1 || mDestRect.height() <= 1) {
+ // Layout hasn't occurred yet
+ if (!mFirstTry) {
+ // Give up if this is not the first try, since layout change still doesn't
+ // yield a size for the view. This is likely because the media picker is
+ // full screen so there's no space left for the animated view. We give up
+ // on animation, but need to make sure the view that was initially
+ // hidden is re-shown.
+ mViewToAnimate.setAlpha(1);
+ mViewToAnimate.setVisibility(View.VISIBLE);
+ } else {
+ mFirstTry = false;
+ UiUtils.doOnceAfterLayoutChange(mViewToAnimate, this);
+ }
+ return;
+ }
+
+ mRunComplete = true;
+ mViewToAnimate.startAnimation(PopupTransitionAnimation.this);
+ mViewToAnimate.invalidate();
+ // http://b/20856505: The PopupWindow sometimes does not get dismissed.
+ ThreadUtil.getMainThreadHandler().postDelayed(mCleanupRunnable, getDuration() * 2);
+ }
+ };
+
+ startAnimation.run();
+ }
+
+ public PopupTransitionAnimation setOnStartCallback(final Runnable onStart) {
+ mOnStartCallback = onStart;
+ return this;
+ }
+
+ public PopupTransitionAnimation setOnStopCallback(final Runnable onStop) {
+ mOnStopCallback = onStop;
+ return this;
+ }
+
+ @Override
+ protected void applyTransformation(final float interpolatedTime, final Transformation t) {
+ if (mPopupWindow == null) {
+ initPopupWindow();
+ }
+ // Update mDestRect as it may have moved during the animation
+ mPopupRect.set(UiUtils.getMeasuredBoundsOnScreen(mPopupRoot));
+ mActionBarRect.set(UiUtils.getMeasuredBoundsOnScreen(mActionBarView));
+ computeDestRect();
+
+ // Update currentRect to the new animated coordinates, and request mPopupRoot to redraw
+ // itself at the new coordinates
+ mCurrentRect = mRectEvaluator.evaluate(interpolatedTime, mStartRect, mDestRect);
+ mPopupRoot.invalidate();
+
+ if (interpolatedTime >= 0.98) {
+ mEvents.append("aT").append(interpolatedTime).append(',');
+ }
+ if (interpolatedTime == 1) {
+ dismiss();
+ }
+ }
+
+ private void dismiss() {
+ mEvents.append("d,");
+ mViewToAnimate.setAlpha(1);
+ mViewToAnimate.setVisibility(View.VISIBLE);
+ // Delay dismissing the popup window to let mViewToAnimate draw under it and reduce the
+ // flash
+ ThreadUtil.getMainThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mPopupWindow.dismiss();
+ } catch (IllegalArgumentException e) {
+ // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity
+ // has already ended while we were animating
+ }
+ ThreadUtil.getMainThreadHandler().removeCallbacks(mCleanupRunnable);
+ }
+ });
+ }
+
+ @Override
+ public boolean willChangeBounds() {
+ return false;
+ }
+
+ /**
+ * Computes mDestRect (the position in window space of the placeholder view that we should
+ * animate to). Some frames during the animation fail to compute getGlobalVisibleRect, so use
+ * the last known values in that case
+ */
+ private void computeDestRect() {
+ final int prevTop = mDestRect.top;
+ final int prevLeft = mDestRect.left;
+ final int prevRight = mDestRect.right;
+ final int prevBottom = mDestRect.bottom;
+
+ if (!getViewScreenMeasureRect(mViewToAnimate, mDestRect)) {
+ mDestRect.top = prevTop;
+ mDestRect.left = prevLeft;
+ mDestRect.bottom = prevBottom;
+ mDestRect.right = prevRight;
+ }
+ }
+
+ /**
+ * Sets up the PopupWindow that the view will animate in. Animating the size and position of a
+ * popup can be choppy, so instead we make the popup fill the entire space of the screen, and
+ * animate the position of viewToAnimate within the popup using a Transformation
+ */
+ private void initPopupWindow() {
+ mPopupRoot = new View(mViewToAnimate.getContext()) {
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ canvas.save();
+ canvas.clipRect(getLeft(), mActionBarRect.bottom - mPopupRect.top, getRight(),
+ getBottom());
+ canvas.drawColor(Color.TRANSPARENT);
+ final float previousAlpha = mViewToAnimate.getAlpha();
+ mViewToAnimate.setAlpha(1);
+ // The view's global position includes the notification bar height, but
+ // the popup window may or may not cover the notification bar (depending on screen
+ // rotation, IME status etc.), so we need to compensate for this difference by
+ // offseting vertically.
+ canvas.translate(mCurrentRect.left, mCurrentRect.top - mPopupRect.top);
+
+ final float viewWidth = mViewToAnimate.getWidth();
+ final float viewHeight = mViewToAnimate.getHeight();
+ if (viewWidth > 0 && viewHeight > 0) {
+ canvas.scale(mCurrentRect.width() / viewWidth,
+ mCurrentRect.height() / viewHeight);
+ }
+ canvas.clipRect(0, 0, mCurrentRect.width(), mCurrentRect.height());
+ if (!mPopupRect.isEmpty()) {
+ // HACK: Layout is unstable until mPopupRect is non-empty.
+ mViewToAnimate.draw(canvas);
+ }
+ mViewToAnimate.setAlpha(previousAlpha);
+ canvas.restore();
+ }
+ };
+ mPopupWindow = new PopupWindow(mViewToAnimate.getContext());
+ mPopupWindow.setBackgroundDrawable(null);
+ mPopupWindow.setContentView(mPopupRoot);
+ mPopupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
+ mPopupWindow.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
+ mPopupWindow.setTouchable(false);
+ // We must pass a non-zero value for the y offset, or else the system resets the status bar
+ // color to black (M only) during the animation. The actual position of the window (and
+ // the animated view inside it) are still correct, regardless of what we pass for the y
+ // parameter (e.g. 1 and 100 both work). Not entirely sure why this works.
+ mPopupWindow.showAtLocation(mViewToAnimate, Gravity.TOP, 0, 1);
+ }
+
+ private static boolean getViewScreenMeasureRect(final View view, final Rect outRect) {
+ outRect.set(UiUtils.getMeasuredBoundsOnScreen(view));
+ return !outRect.isEmpty();
+ }
+}
diff --git a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java b/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java
new file mode 100644
index 0000000..e3c60fc
--- /dev/null
+++ b/src/com/android/messaging/ui/animation/RectEvaluatorCompat.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.animation;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.graphics.Rect;
+
+import com.android.messaging.util.OsUtil;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
+ * It's backward compatible to Api Level 11.
+ */
+public class RectEvaluatorCompat implements TypeEvaluator<Rect> {
+ public static TypeEvaluator<Rect> create() {
+ if (OsUtil.isAtLeastJB_MR2()) {
+ return new RectEvaluator();
+ } else {
+ return new RectEvaluatorCompat();
+ }
+ }
+
+ @Override
+ public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+ int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
+ int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
+ int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
+ int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
+ return new Rect(left, top, right, bottom);
+ }
+}
diff --git a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
new file mode 100644
index 0000000..6abfdf9
--- /dev/null
+++ b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
@@ -0,0 +1,208 @@
+/*
+ * 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.animation;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewOverlay;
+import android.widget.FrameLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * <p>
+ * Shows a vertical "explode" animation for any view inside a view group (e.g. views inside a
+ * ListView). During the animation, a snapshot is taken for the view to the animated and
+ * presented in a popup window or view overlay on top of the original view group. The background
+ * of the view (a highlight) vertically expands (explodes) during the animation.
+ * </p>
+ * <p>
+ * The exact implementation of the animation depends on platform API level. For JB_MR2 and later,
+ * the implementation utilizes ViewOverlay to perform highly performant overlay animations; for
+ * older API levels, the implementation falls back to using a full screen popup window to stage
+ * the animation.
+ * </p>
+ * <p>
+ * To start this animation, call {@link #startAnimationForView(ViewGroup, View, View, boolean, int)}
+ * </p>
+ */
+public class ViewGroupItemVerticalExplodeAnimation {
+ /**
+ * Starts a vertical explode animation for a given view situated in a given container.
+ *
+ * @param container the container of the view which determines the explode animation's final
+ * size
+ * @param viewToAnimate the view to be animated. The view will be highlighted by the explode
+ * highlight, which expands from the size of the view to the size of the container.
+ * @param animationStagingView the view that stages the animation. Since viewToAnimate may be
+ * removed from the view tree during the animation, we need a view that'll be alive
+ * for the duration of the animation so that the animation won't get cancelled.
+ * @param snapshotView whether a snapshot of the view to animate is needed.
+ */
+ public static void startAnimationForView(final ViewGroup container, final View viewToAnimate,
+ final View animationStagingView, final boolean snapshotView, final int duration) {
+ if (OsUtil.isAtLeastJB_MR2() && (viewToAnimate.getContext() instanceof Activity)) {
+ new ViewExplodeAnimationJellyBeanMR2(viewToAnimate, container, snapshotView, duration)
+ .startAnimation();
+ } else {
+ // Pre JB_MR2, this animation can cause rendering failures which causes the framework
+ // to fall back to software rendering where camera preview isn't supported (b/18264647)
+ // just skip the animation to avoid this case.
+ }
+ }
+
+ /**
+ * Implementation class for API level >= 18.
+ */
+ @TargetApi(18)
+ private static class ViewExplodeAnimationJellyBeanMR2 {
+ private final View mViewToAnimate;
+ private final ViewGroup mContainer;
+ private final View mSnapshot;
+ private final Bitmap mViewBitmap;
+ private final int mDuration;
+
+ public ViewExplodeAnimationJellyBeanMR2(final View viewToAnimate, final ViewGroup container,
+ final boolean snapshotView, final int duration) {
+ mViewToAnimate = viewToAnimate;
+ mContainer = container;
+ mDuration = duration;
+ if (snapshotView) {
+ mViewBitmap = snapshotView(viewToAnimate);
+ mSnapshot = new View(viewToAnimate.getContext());
+ } else {
+ mSnapshot = null;
+ mViewBitmap = null;
+ }
+ }
+
+ public void startAnimation() {
+ final Context context = mViewToAnimate.getContext();
+ final Resources resources = context.getResources();
+ final View decorView = ((Activity) context).getWindow().getDecorView();
+ final ViewOverlay viewOverlay = decorView.getOverlay();
+ if (viewOverlay instanceof ViewGroupOverlay) {
+ final ViewGroupOverlay overlay = (ViewGroupOverlay) viewOverlay;
+
+ // Add a shadow layer to the overlay.
+ final FrameLayout shadowContainerLayer = new FrameLayout(context);
+ final Drawable oldBackground = mViewToAnimate.getBackground();
+ final Rect containerRect = UiUtils.getMeasuredBoundsOnScreen(mContainer);
+ final Rect decorRect = UiUtils.getMeasuredBoundsOnScreen(decorView);
+ // Position the container rect relative to the decor rect since the decor rect
+ // defines whether the view overlay will be positioned.
+ containerRect.offset(-decorRect.left, -decorRect.top);
+ shadowContainerLayer.setLeft(containerRect.left);
+ shadowContainerLayer.setTop(containerRect.top);
+ shadowContainerLayer.setBottom(containerRect.bottom);
+ shadowContainerLayer.setRight(containerRect.right);
+ shadowContainerLayer.setBackgroundColor(resources.getColor(
+ R.color.open_conversation_animation_background_shadow));
+ // Per design request, temporarily clear out the background of the item content
+ // to not show any ripple effects during animation.
+ if (!(oldBackground instanceof ColorDrawable)) {
+ mViewToAnimate.setBackground(null);
+ }
+ overlay.add(shadowContainerLayer);
+
+ // Add a expand layer and position it with in the shadow background, so it can
+ // be properly clipped to the container bounds during the animation.
+ final View expandLayer = new View(context);
+ final int elevation = resources.getDimensionPixelSize(
+ R.dimen.explode_animation_highlight_elevation);
+ final Rect viewRect = UiUtils.getMeasuredBoundsOnScreen(mViewToAnimate);
+ // Frame viewRect from screen space to containerRect space.
+ viewRect.offset(-containerRect.left - decorRect.left,
+ -containerRect.top - decorRect.top);
+ // Since the expand layer expands at the same rate above and below, we need to
+ // compute the expand scale using the bigger of the top/bottom distances.
+ final int expandLayerHalfHeight = viewRect.height() / 2;
+ final int topDist = viewRect.top;
+ final int bottomDist = containerRect.height() - viewRect.bottom;
+ final float scale = expandLayerHalfHeight == 0 ? 1 :
+ ((float) Math.max(topDist, bottomDist) + expandLayerHalfHeight) /
+ expandLayerHalfHeight;
+ // Position the expand layer initially to exactly match the animated item.
+ shadowContainerLayer.addView(expandLayer);
+ expandLayer.setLeft(viewRect.left);
+ expandLayer.setTop(viewRect.top);
+ expandLayer.setBottom(viewRect.bottom);
+ expandLayer.setRight(viewRect.right);
+ expandLayer.setBackgroundColor(resources.getColor(
+ R.color.conversation_background));
+ ViewCompat.setElevation(expandLayer, elevation);
+
+ // Conditionally stage the snapshot in the overlay.
+ if (mSnapshot != null) {
+ shadowContainerLayer.addView(mSnapshot);
+ mSnapshot.setLeft(viewRect.left);
+ mSnapshot.setTop(viewRect.top);
+ mSnapshot.setBottom(viewRect.bottom);
+ mSnapshot.setRight(viewRect.right);
+ mSnapshot.setBackground(new BitmapDrawable(resources, mViewBitmap));
+ ViewCompat.setElevation(mSnapshot, elevation);
+ }
+
+ // Apply a scale animation to scale to full screen.
+ expandLayer.animate().scaleY(scale)
+ .setDuration(mDuration)
+ .setInterpolator(UiUtils.EASE_IN_INTERPOLATOR)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ // Clean up the views added to overlay on animation finish.
+ overlay.remove(shadowContainerLayer);
+ mViewToAnimate.setBackground(oldBackground);
+ if (mViewBitmap != null) {
+ mViewBitmap.recycle();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Take a snapshot of the given review, return a Bitmap object that's owned by the caller.
+ */
+ static Bitmap snapshotView(final View view) {
+ // Save the content of the view into a bitmap.
+ final Bitmap viewBitmap = Bitmap.createBitmap(view.getWidth(),
+ view.getHeight(), Bitmap.Config.ARGB_8888);
+ // Strip the view of its background when taking a snapshot so that things like touch
+ // feedback don't get accidentally snapshotted.
+ final Drawable viewBackground = view.getBackground();
+ ImageUtils.setBackgroundDrawableOnView(view, null);
+ view.draw(new Canvas(viewBitmap));
+ ImageUtils.setBackgroundDrawableOnView(view, viewBackground);
+ return viewBitmap;
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java b/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java
new file mode 100644
index 0000000..b7cb7ae
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java
@@ -0,0 +1,463 @@
+/*
+ * 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.appsettings;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.provider.Telephony;
+import android.support.v4.app.NavUtils;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.ApnDatabase;
+import com.android.messaging.sms.BugleApnSettingsLoader;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.PhoneUtils;
+
+public class ApnEditorActivity extends BugleActionBarActivity {
+ private static final int ERROR_DIALOG_ID = 0;
+ private static final String ERROR_MESSAGE_KEY = "error_msg";
+ private ApnEditorFragment mApnEditorFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ // Display the fragment as the main content.
+ mApnEditorFragment = new ApnEditorFragment();
+ mApnEditorFragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
+ ParticipantData.DEFAULT_SELF_SUB_ID));
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, mApnEditorFragment)
+ .commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle args) {
+
+ if (id == ERROR_DIALOG_ID) {
+ String msg = args.getString(ERROR_MESSAGE_KEY);
+
+ return new AlertDialog.Builder(this)
+ .setPositiveButton(android.R.string.ok, null)
+ .setMessage(msg)
+ .create();
+ }
+
+ return super.onCreateDialog(id);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK: {
+ if (mApnEditorFragment.validateAndSave(false)) {
+ finish();
+ }
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+ super.onPrepareDialog(id, dialog);
+
+ if (id == ERROR_DIALOG_ID) {
+ final String msg = args.getString(ERROR_MESSAGE_KEY);
+
+ if (msg != null) {
+ ((AlertDialog) dialog).setMessage(msg);
+ }
+ }
+ }
+
+ public static class ApnEditorFragment extends PreferenceFragment implements
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private static final String SAVED_POS = "pos";
+
+ private static final int MENU_DELETE = Menu.FIRST;
+ private static final int MENU_SAVE = Menu.FIRST + 1;
+ private static final int MENU_CANCEL = Menu.FIRST + 2;
+
+ private EditTextPreference mMmsProxy;
+ private EditTextPreference mMmsPort;
+ private EditTextPreference mName;
+ private EditTextPreference mMmsc;
+ private EditTextPreference mMcc;
+ private EditTextPreference mMnc;
+ private static String sNotSet;
+
+ private String mCurMnc;
+ private String mCurMcc;
+
+ private Cursor mCursor;
+ private boolean mNewApn;
+ private boolean mFirstTime;
+ private String mCurrentId;
+
+ private int mSubId;
+
+ /**
+ * Standard projection for the interesting columns of a normal note.
+ */
+ private static final String[] sProjection = new String[] {
+ Telephony.Carriers._ID, // 0
+ Telephony.Carriers.NAME, // 1
+ Telephony.Carriers.MMSC, // 2
+ Telephony.Carriers.MCC, // 3
+ Telephony.Carriers.MNC, // 4
+ Telephony.Carriers.NUMERIC, // 5
+ Telephony.Carriers.MMSPROXY, // 6
+ Telephony.Carriers.MMSPORT, // 7
+ Telephony.Carriers.TYPE, // 8
+ };
+
+ private static final int ID_INDEX = 0;
+ private static final int NAME_INDEX = 1;
+ private static final int MMSC_INDEX = 2;
+ private static final int MCC_INDEX = 3;
+ private static final int MNC_INDEX = 4;
+ private static final int NUMERIC_INDEX = 5;
+ private static final int MMSPROXY_INDEX = 6;
+ private static final int MMSPORT_INDEX = 7;
+ private static final int TYPE_INDEX = 8;
+
+ private SQLiteDatabase mDatabase;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.apn_editor);
+
+ setHasOptionsMenu(true);
+
+ sNotSet = getResources().getString(R.string.apn_not_set);
+ mName = (EditTextPreference) findPreference("apn_name");
+ mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
+ mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
+ mMmsc = (EditTextPreference) findPreference("apn_mmsc");
+ mMcc = (EditTextPreference) findPreference("apn_mcc");
+ mMnc = (EditTextPreference) findPreference("apn_mnc");
+
+ final Intent intent = getActivity().getIntent();
+
+ mFirstTime = savedInstanceState == null;
+ mCurrentId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_APN_ROW_ID);
+ mNewApn = mCurrentId == null;
+
+ mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
+
+ if (mNewApn) {
+ fillUi();
+ } else {
+ // Do initial query not on the UI thread
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (mCurrentId != null) {
+ String selection = Telephony.Carriers._ID + " =?";
+ String[] selectionArgs = new String[]{ mCurrentId };
+ mCursor = mDatabase.query(ApnDatabase.APN_TABLE, sProjection, selection,
+ selectionArgs, null, null, null, null);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (mCursor == null) {
+ getActivity().finish();
+ return;
+ }
+ mCursor.moveToFirst();
+
+ fillUi();
+ }
+ }.execute((Void) null);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ super.onPause();
+ }
+
+ public void setSubId(final int subId) {
+ mSubId = subId;
+ }
+
+ private void fillUi() {
+ if (mNewApn) {
+ mMcc.setText(null);
+ mMnc.setText(null);
+ String numeric = PhoneUtils.get(mSubId).getSimOperatorNumeric();
+ // MCC is first 3 chars and then in 2 - 3 chars of MNC
+ if (numeric != null && numeric.length() > 4) {
+ // Country code
+ String mcc = numeric.substring(0, 3);
+ // Network code
+ String mnc = numeric.substring(3);
+ // Auto populate MNC and MCC for new entries, based on what SIM reports
+ mMcc.setText(mcc);
+ mMnc.setText(mnc);
+ mCurMnc = mnc;
+ mCurMcc = mcc;
+ }
+ mName.setText(null);
+ mMmsProxy.setText(null);
+ mMmsPort.setText(null);
+ mMmsc.setText(null);
+ } else if (mFirstTime) {
+ mFirstTime = false;
+ // Fill in all the values from the db in both text editor and summary
+ mName.setText(mCursor.getString(NAME_INDEX));
+ mMmsProxy.setText(mCursor.getString(MMSPROXY_INDEX));
+ mMmsPort.setText(mCursor.getString(MMSPORT_INDEX));
+ mMmsc.setText(mCursor.getString(MMSC_INDEX));
+ mMcc.setText(mCursor.getString(MCC_INDEX));
+ mMnc.setText(mCursor.getString(MNC_INDEX));
+ }
+
+ mName.setSummary(checkNull(mName.getText()));
+ mMmsProxy.setSummary(checkNull(mMmsProxy.getText()));
+ mMmsPort.setSummary(checkNull(mMmsPort.getText()));
+ mMmsc.setSummary(checkNull(mMmsc.getText()));
+ mMcc.setSummary(checkNull(mMcc.getText()));
+ mMnc.setSummary(checkNull(mMnc.getText()));
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ // If it's a new APN, then cancel will delete the new entry in onPause
+ if (!mNewApn) {
+ menu.add(0, MENU_DELETE, 0, R.string.menu_delete_apn)
+ .setIcon(R.drawable.ic_delete_small_dark);
+ }
+ menu.add(0, MENU_SAVE, 0, R.string.menu_save_apn)
+ .setIcon(android.R.drawable.ic_menu_save);
+ menu.add(0, MENU_CANCEL, 0, R.string.menu_discard_apn_change)
+ .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_DELETE:
+ deleteApn();
+ return true;
+
+ case MENU_SAVE:
+ if (validateAndSave(false)) {
+ getActivity().finish();
+ }
+ return true;
+
+ case MENU_CANCEL:
+ getActivity().finish();
+ return true;
+
+ case android.R.id.home:
+ getActivity().onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle icicle) {
+ super.onSaveInstanceState(icicle);
+ if (validateAndSave(true) && mCursor != null) {
+ icicle.putInt(SAVED_POS, mCursor.getInt(ID_INDEX));
+ }
+ }
+
+ /**
+ * Check the key fields' validity and save if valid.
+ * @param force save even if the fields are not valid, if the app is
+ * being suspended
+ * @return true if the data was saved
+ */
+ private boolean validateAndSave(boolean force) {
+ final String name = checkNotSet(mName.getText());
+ final String mcc = checkNotSet(mMcc.getText());
+ final String mnc = checkNotSet(mMnc.getText());
+
+ if (getErrorMsg() != null && !force) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(ERROR_MESSAGE_KEY, getErrorMsg());
+ getActivity().showDialog(ERROR_DIALOG_ID, bundle);
+ return false;
+ }
+
+ // Make database changes not on the UI thread
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ ContentValues values = new ContentValues();
+
+ // Add a dummy name "Untitled", if the user exits the screen without adding a
+ // name but entered other information worth keeping.
+ values.put(Telephony.Carriers.NAME, name.length() < 1 ?
+ getResources().getString(R.string.untitled_apn) : name);
+ values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
+ values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
+ values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));
+
+ values.put(Telephony.Carriers.TYPE, BugleApnSettingsLoader.APN_TYPE_MMS);
+
+ values.put(Telephony.Carriers.MCC, mcc);
+ values.put(Telephony.Carriers.MNC, mnc);
+
+ values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
+
+ if (mCurMnc != null && mCurMcc != null) {
+ if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
+ values.put(Telephony.Carriers.CURRENT, 1);
+ }
+ }
+
+ if (mNewApn) {
+ mDatabase.insert(ApnDatabase.APN_TABLE, null, values);
+ } else {
+ // update the APN
+ String selection = Telephony.Carriers._ID + " =?";
+ String[] selectionArgs = new String[]{ mCurrentId };
+ int updated = mDatabase.update(ApnDatabase.APN_TABLE, values,
+ selection, selectionArgs);
+ }
+ return null;
+ }
+ }.execute((Void) null);
+
+ return true;
+ }
+
+ private void deleteApn() {
+ // Make database changes not on the UI thread
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // delete the APN
+ String where = Telephony.Carriers._ID + " =?";
+ String[] whereArgs = new String[]{ mCurrentId };
+
+ mDatabase.delete(ApnDatabase.APN_TABLE, where, whereArgs);
+ return null;
+ }
+ }.execute((Void) null);
+
+ getActivity().finish();
+ }
+
+ private String checkNull(String value) {
+ if (value == null || value.length() == 0) {
+ return sNotSet;
+ } else {
+ return value;
+ }
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ Preference pref = findPreference(key);
+ if (pref != null) {
+ pref.setSummary(checkNull(sharedPreferences.getString(key, "")));
+ }
+ }
+
+ private String getErrorMsg() {
+ String errorMsg = null;
+
+ String name = checkNotSet(mName.getText());
+ String mcc = checkNotSet(mMcc.getText());
+ String mnc = checkNotSet(mMnc.getText());
+
+ if (name.length() < 1) {
+ errorMsg = getString(R.string.error_apn_name_empty);
+ } else if (mcc.length() != 3) {
+ errorMsg = getString(R.string.error_mcc_not3);
+ } else if ((mnc.length() & 0xFFFE) != 2) {
+ errorMsg = getString(R.string.error_mnc_not23);
+ }
+
+ return errorMsg;
+ }
+
+ private String checkNotSet(String value) {
+ if (value == null || value.equals(sNotSet)) {
+ return "";
+ } else {
+ return value;
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/ApnPreference.java b/src/com/android/messaging/ui/appsettings/ApnPreference.java
new file mode 100644
index 0000000..74c6a08
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/ApnPreference.java
@@ -0,0 +1,151 @@
+/*
+ * 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.appsettings;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.ui.UIIntents;
+
+/**
+ * ApnPreference implements a pref, typically used as a list item, that has a title/summary on
+ * the left and a radio button on the right.
+ *
+ */
+public class ApnPreference extends Preference implements
+ CompoundButton.OnCheckedChangeListener, OnClickListener {
+ static final String TAG = "ApnPreference";
+
+ public ApnPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public ApnPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.apnPreferenceStyle);
+ }
+
+ public ApnPreference(Context context) {
+ this(context, null);
+ }
+
+ private static String mSelectedKey = null;
+ private static CompoundButton mCurrentChecked = null;
+ private boolean mProtectFromCheckedChange = false;
+ private boolean mSelectable = true;
+ private int mSubId = ParticipantData.DEFAULT_SELF_SUB_ID;
+
+ @Override
+ public View getView(View convertView, ViewGroup parent) {
+ View view = super.getView(convertView, parent);
+
+ View widget = view.findViewById(R.id.apn_radiobutton);
+ if ((widget != null) && widget instanceof RadioButton) {
+ RadioButton rb = (RadioButton) widget;
+ if (mSelectable) {
+ rb.setOnCheckedChangeListener(this);
+
+ boolean isChecked = getKey().equals(mSelectedKey);
+ if (isChecked) {
+ mCurrentChecked = rb;
+ mSelectedKey = getKey();
+ }
+
+ mProtectFromCheckedChange = true;
+ rb.setChecked(isChecked);
+ mProtectFromCheckedChange = false;
+ } else {
+ rb.setVisibility(View.GONE);
+ }
+ setApnRadioButtonContentDescription(rb);
+ }
+
+ View textLayout = view.findViewById(R.id.text_layout);
+ if ((textLayout != null) && textLayout instanceof RelativeLayout) {
+ textLayout.setOnClickListener(this);
+ }
+
+ return view;
+ }
+
+ public void setApnRadioButtonContentDescription(final CompoundButton buttonView) {
+ final View widget = (View) buttonView.getParent();
+ final TextView tv = (TextView) widget.findViewById(android.R.id.title);
+ final String apnTitle = tv.getText().toString();
+ buttonView.setContentDescription(apnTitle);
+ }
+
+ public boolean isChecked() {
+ return getKey().equals(mSelectedKey);
+ }
+
+ public void setChecked() {
+ mSelectedKey = getKey();
+ }
+
+ public void setSubId(final int subId) {
+ mSubId = subId;
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Log.i(TAG, "ID: " + getKey() + " :" + isChecked);
+ if (mProtectFromCheckedChange) {
+ return;
+ }
+
+ if (isChecked) {
+ if (mCurrentChecked != null) {
+ mCurrentChecked.setChecked(false);
+ }
+ mCurrentChecked = buttonView;
+ mSelectedKey = getKey();
+ callChangeListener(mSelectedKey);
+ } else {
+ mCurrentChecked = null;
+ mSelectedKey = null;
+ }
+ setApnRadioButtonContentDescription(buttonView);
+ }
+
+ public void onClick(android.view.View v) {
+ if ((v != null) && (R.id.text_layout == v.getId())) {
+ Context context = getContext();
+ if (context != null) {
+ context.startActivity(
+ UIIntents.get().getApnEditorIntent(context, getKey(), mSubId));
+ }
+ }
+ }
+
+ public void setSelectable(boolean selectable) {
+ mSelectable = selectable;
+ }
+
+ public boolean getSelectable() {
+ return mSelectable;
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java
new file mode 100644
index 0000000..28dfc2a
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java
@@ -0,0 +1,406 @@
+/*
+ * 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.appsettings;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserManager;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.provider.Telephony;
+import android.support.v4.app.NavUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.ApnDatabase;
+import com.android.messaging.sms.BugleApnSettingsLoader;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+public class ApnSettingsActivity extends BugleActionBarActivity {
+ private static final int DIALOG_RESTORE_DEFAULTAPN = 1001;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ // Display the fragment as the main content.
+ final ApnSettingsFragment fragment = new ApnSettingsFragment();
+ fragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
+ ParticipantData.DEFAULT_SELF_SUB_ID));
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, fragment)
+ .commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ if (id == DIALOG_RESTORE_DEFAULTAPN) {
+ ProgressDialog dialog = new ProgressDialog(this);
+ dialog.setMessage(getResources().getString(R.string.restore_default_apn));
+ dialog.setCancelable(false);
+ return dialog;
+ }
+ return null;
+ }
+
+ public static class ApnSettingsFragment extends PreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ public static final String EXTRA_POSITION = "position";
+
+ public static final String APN_ID = "apn_id";
+
+ private static final String[] APN_PROJECTION = {
+ Telephony.Carriers._ID, // 0
+ Telephony.Carriers.NAME, // 1
+ Telephony.Carriers.APN, // 2
+ Telephony.Carriers.TYPE // 3
+ };
+ private static final int ID_INDEX = 0;
+ private static final int NAME_INDEX = 1;
+ private static final int APN_INDEX = 2;
+ private static final int TYPES_INDEX = 3;
+
+ private static final int MENU_NEW = Menu.FIRST;
+ private static final int MENU_RESTORE = Menu.FIRST + 1;
+
+ private static final int EVENT_RESTORE_DEFAULTAPN_START = 1;
+ private static final int EVENT_RESTORE_DEFAULTAPN_COMPLETE = 2;
+
+ private static boolean mRestoreDefaultApnMode;
+
+ private RestoreApnUiHandler mRestoreApnUiHandler;
+ private RestoreApnProcessHandler mRestoreApnProcessHandler;
+ private HandlerThread mRestoreDefaultApnThread;
+
+ private String mSelectedKey;
+
+ private static final ContentValues sCurrentNullMap;
+ private static final ContentValues sCurrentSetMap;
+
+ private UserManager mUm;
+
+ private boolean mUnavailable;
+ private int mSubId;
+
+ static {
+ sCurrentNullMap = new ContentValues(1);
+ sCurrentNullMap.putNull(Telephony.Carriers.CURRENT);
+
+ sCurrentSetMap = new ContentValues(1);
+ sCurrentSetMap.put(Telephony.Carriers.CURRENT, "2"); // 2 for user-selected APN,
+ // 1 for Bugle-selected APN
+ }
+
+ private SQLiteDatabase mDatabase;
+
+ public void setSubId(final int subId) {
+ mSubId = subId;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
+
+ if (OsUtil.isAtLeastL()) {
+ mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
+ if (!mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ setHasOptionsMenu(true);
+ }
+ } else {
+ setHasOptionsMenu(true);
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final ListView lv = (ListView) getView().findViewById(android.R.id.list);
+ TextView empty = (TextView) getView().findViewById(android.R.id.empty);
+ if (empty != null) {
+ empty.setText(R.string.apn_settings_not_available);
+ lv.setEmptyView(empty);
+ }
+
+ if (OsUtil.isAtLeastL() &&
+ mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ mUnavailable = true;
+ setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
+ return;
+ }
+
+ addPreferencesFromResource(R.xml.apn_settings);
+
+ lv.setItemsCanFocus(true);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mUnavailable) {
+ return;
+ }
+
+ if (!mRestoreDefaultApnMode) {
+ fillList();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ if (mUnavailable) {
+ return;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (mRestoreDefaultApnThread != null) {
+ mRestoreDefaultApnThread.quit();
+ }
+ }
+
+ private void fillList() {
+ final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.get(mSubId).getMccMnc());
+
+ new AsyncTask<Void, Void, Cursor>() {
+ @Override
+ protected Cursor doInBackground(Void... params) {
+ String selection = Telephony.Carriers.NUMERIC + " =?";
+ String[] selectionArgs = new String[]{ mccMnc };
+ final Cursor cursor = mDatabase.query(ApnDatabase.APN_TABLE, APN_PROJECTION,
+ selection, selectionArgs, null, null, null, null);
+ return cursor;
+ }
+
+ @Override
+ protected void onPostExecute(Cursor cursor) {
+ if (cursor != null) {
+ try {
+ PreferenceGroup apnList = (PreferenceGroup)
+ findPreference(getString(R.string.apn_list_pref_key));
+ apnList.removeAll();
+
+ mSelectedKey = BugleApnSettingsLoader.getFirstTryApn(mDatabase, mccMnc);
+ while (cursor.moveToNext()) {
+ String name = cursor.getString(NAME_INDEX);
+ String apn = cursor.getString(APN_INDEX);
+ String key = cursor.getString(ID_INDEX);
+ String type = cursor.getString(TYPES_INDEX);
+
+ if (BugleApnSettingsLoader.isValidApnType(type,
+ BugleApnSettingsLoader.APN_TYPE_MMS)) {
+ ApnPreference pref = new ApnPreference(getActivity());
+ pref.setKey(key);
+ pref.setTitle(name);
+ pref.setSummary(apn);
+ pref.setPersistent(false);
+ pref.setOnPreferenceChangeListener(ApnSettingsFragment.this);
+ pref.setSelectable(true);
+
+ // Turn on the radio button for the currently selected APN. If
+ // there is no selected APN, don't select an APN.
+ if ((mSelectedKey != null && mSelectedKey.equals(key))) {
+ pref.setChecked();
+ }
+ apnList.addPreference(pref);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ }.execute((Void) null);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ if (!mUnavailable) {
+ menu.add(0, MENU_NEW, 0,
+ getResources().getString(R.string.menu_new_apn))
+ .setIcon(R.drawable.ic_add_gray)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(0, MENU_RESTORE, 0,
+ getResources().getString(R.string.menu_restore_default_apn))
+ .setIcon(android.R.drawable.ic_menu_upload);
+ }
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_NEW:
+ addNewApn();
+ return true;
+
+ case MENU_RESTORE:
+ restoreDefaultApn();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void addNewApn() {
+ startActivity(UIIntents.get().getApnEditorIntent(getActivity(), null, mSubId));
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ startActivity(
+ UIIntents.get().getApnEditorIntent(getActivity(), preference.getKey(), mSubId));
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (newValue instanceof String) {
+ setSelectedApnKey((String) newValue);
+ }
+
+ return true;
+ }
+
+ // current=2 means user selected APN
+ private static final String UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?";
+ private static final String[] UPDATE_SELECTION_ARGS = new String[] { "2" };
+ private void setSelectedApnKey(final String key) {
+ mSelectedKey = key;
+
+ // Make database changes not on the UI thread
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ // null out the previous "current=2" APN
+ mDatabase.update(ApnDatabase.APN_TABLE, sCurrentNullMap,
+ UPDATE_SELECTION, UPDATE_SELECTION_ARGS);
+
+ // set the new "current" APN (2)
+ String selection = Telephony.Carriers._ID + " =?";
+ String[] selectionArgs = new String[]{ key };
+
+ mDatabase.update(ApnDatabase.APN_TABLE, sCurrentSetMap,
+ selection, selectionArgs);
+ return null;
+ }
+ }.execute((Void) null);
+ }
+
+ private boolean restoreDefaultApn() {
+ getActivity().showDialog(DIALOG_RESTORE_DEFAULTAPN);
+ mRestoreDefaultApnMode = true;
+
+ if (mRestoreApnUiHandler == null) {
+ mRestoreApnUiHandler = new RestoreApnUiHandler();
+ }
+
+ if (mRestoreApnProcessHandler == null ||
+ mRestoreDefaultApnThread == null) {
+ mRestoreDefaultApnThread = new HandlerThread(
+ "Restore default APN Handler: Process Thread");
+ mRestoreDefaultApnThread.start();
+ mRestoreApnProcessHandler = new RestoreApnProcessHandler(
+ mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);
+ }
+
+ mRestoreApnProcessHandler.sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);
+ return true;
+ }
+
+ private class RestoreApnUiHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RESTORE_DEFAULTAPN_COMPLETE:
+ fillList();
+ getPreferenceScreen().setEnabled(true);
+ mRestoreDefaultApnMode = false;
+ final Activity activity = getActivity();
+ activity.dismissDialog(DIALOG_RESTORE_DEFAULTAPN);
+ Toast.makeText(activity, getResources().getString(
+ R.string.restore_default_apn_completed), Toast.LENGTH_LONG)
+ .show();
+ break;
+ }
+ }
+ }
+
+ private class RestoreApnProcessHandler extends Handler {
+ private Handler mCachedRestoreApnUiHandler;
+
+ public RestoreApnProcessHandler(Looper looper, Handler restoreApnUiHandler) {
+ super(looper);
+ this.mCachedRestoreApnUiHandler = restoreApnUiHandler;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RESTORE_DEFAULTAPN_START:
+ ApnDatabase.forceBuildAndLoadApnTables();
+ mCachedRestoreApnUiHandler.sendEmptyMessage(
+ EVENT_RESTORE_DEFAULTAPN_COMPLETE);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
new file mode 100644
index 0000000..906009f
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
@@ -0,0 +1,262 @@
+/*
+ * 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.appsettings;
+
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.preference.RingtonePreference;
+import android.preference.TwoStatePreference;
+import android.provider.Settings;
+import android.support.v4.app.NavUtils;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.LicenseActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+public class ApplicationSettingsActivity extends BugleActionBarActivity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ final boolean topLevel = getIntent().getBooleanExtra(
+ UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
+ if (topLevel) {
+ getSupportActionBar().setTitle(getString(R.string.settings_activity_title));
+ }
+
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(android.R.id.content, new ApplicationSettingsFragment());
+ ft.commit();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (super.onCreateOptionsMenu(menu)) {
+ return true;
+ }
+ getMenuInflater().inflate(R.menu.settings_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ case R.id.action_license:
+ final Intent intent = new Intent(this, LicenseActivity.class);
+ startActivity(intent);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public static class ApplicationSettingsFragment extends PreferenceFragment implements
+ OnSharedPreferenceChangeListener {
+
+ private String mNotificationsEnabledPreferenceKey;
+ private TwoStatePreference mNotificationsEnabledPreference;
+ private String mRingtonePreferenceKey;
+ private RingtonePreference mRingtonePreference;
+ private Preference mVibratePreference;
+ private String mSmsDisabledPrefKey;
+ private Preference mSmsDisabledPreference;
+ private String mSmsEnabledPrefKey;
+ private Preference mSmsEnabledPreference;
+ private boolean mIsSmsPreferenceClicked;
+
+ public ApplicationSettingsFragment() {
+ // Required empty constructor
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getPreferenceManager().setSharedPreferencesName(BuglePrefs.SHARED_PREFERENCES_NAME);
+ addPreferencesFromResource(R.xml.preferences_application);
+
+ mNotificationsEnabledPreferenceKey =
+ getString(R.string.notifications_enabled_pref_key);
+ mNotificationsEnabledPreference = (TwoStatePreference) findPreference(
+ mNotificationsEnabledPreferenceKey);
+ mRingtonePreferenceKey = getString(R.string.notification_sound_pref_key);
+ mRingtonePreference = (RingtonePreference) findPreference(mRingtonePreferenceKey);
+ mVibratePreference = findPreference(
+ getString(R.string.notification_vibration_pref_key));
+ mSmsDisabledPrefKey = getString(R.string.sms_disabled_pref_key);
+ mSmsDisabledPreference = findPreference(mSmsDisabledPrefKey);
+ mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key);
+ mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey);
+ mIsSmsPreferenceClicked = false;
+
+ final SharedPreferences prefs = getPreferenceScreen().getSharedPreferences();
+ updateSoundSummary(prefs);
+
+ if (!DebugUtils.isDebugEnabled()) {
+ final Preference debugCategory = findPreference(getString(
+ R.string.debug_pref_key));
+ getPreferenceScreen().removePreference(debugCategory);
+ }
+
+ final PreferenceScreen advancedScreen = (PreferenceScreen) findPreference(
+ getString(R.string.advanced_pref_key));
+ final boolean topLevel = getActivity().getIntent().getBooleanExtra(
+ UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
+ if (topLevel) {
+ advancedScreen.setIntent(UIIntents.get()
+ .getAdvancedSettingsIntent(getPreferenceScreen().getContext()));
+ } else {
+ // Hide the Advanced settings screen if this is not top-level; these are shown at
+ // the parent SettingsActivity.
+ getPreferenceScreen().removePreference(advancedScreen);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick (PreferenceScreen preferenceScreen,
+ Preference preference) {
+ if (preference.getKey() == mSmsDisabledPrefKey ||
+ preference.getKey() == mSmsEnabledPrefKey) {
+ mIsSmsPreferenceClicked = true;
+ }
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ private void updateSoundSummary(final SharedPreferences sharedPreferences) {
+ // The silent ringtone just returns an empty string
+ String ringtoneName = mRingtonePreference.getContext().getString(
+ R.string.silent_ringtone);
+
+ String ringtoneString = sharedPreferences.getString(mRingtonePreferenceKey, null);
+
+ // Bootstrap the default setting in the preferences so that we have a valid selection
+ // in the dialog the first time that the user opens it.
+ if (ringtoneString == null) {
+ ringtoneString = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
+ final SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(mRingtonePreferenceKey, ringtoneString);
+ editor.apply();
+ }
+
+ if (!TextUtils.isEmpty(ringtoneString)) {
+ final Uri ringtoneUri = Uri.parse(ringtoneString);
+ final Ringtone tone = RingtoneManager.getRingtone(mRingtonePreference.getContext(),
+ ringtoneUri);
+
+ if (tone != null) {
+ ringtoneName = tone.getTitle(mRingtonePreference.getContext());
+ }
+ }
+
+ mRingtonePreference.setSummary(ringtoneName);
+ }
+
+ private void updateSmsEnabledPreferences() {
+ if (!OsUtil.isAtLeastKLP()) {
+ getPreferenceScreen().removePreference(mSmsDisabledPreference);
+ getPreferenceScreen().removePreference(mSmsEnabledPreference);
+ } else {
+ final String defaultSmsAppLabel = getString(R.string.default_sms_app,
+ PhoneUtils.getDefault().getDefaultSmsAppLabel());
+ boolean isSmsEnabledBeforeState;
+ boolean isSmsEnabledCurrentState;
+ if (PhoneUtils.getDefault().isDefaultSmsApp()) {
+ if (getPreferenceScreen().findPreference(mSmsEnabledPrefKey) == null) {
+ getPreferenceScreen().addPreference(mSmsEnabledPreference);
+ isSmsEnabledBeforeState = false;
+ } else {
+ isSmsEnabledBeforeState = true;
+ }
+ isSmsEnabledCurrentState = true;
+ getPreferenceScreen().removePreference(mSmsDisabledPreference);
+ mSmsEnabledPreference.setSummary(defaultSmsAppLabel);
+ } else {
+ if (getPreferenceScreen().findPreference(mSmsDisabledPrefKey) == null) {
+ getPreferenceScreen().addPreference(mSmsDisabledPreference);
+ isSmsEnabledBeforeState = true;
+ } else {
+ isSmsEnabledBeforeState = false;
+ }
+ isSmsEnabledCurrentState = false;
+ getPreferenceScreen().removePreference(mSmsEnabledPreference);
+ mSmsDisabledPreference.setSummary(defaultSmsAppLabel);
+ }
+ updateNotificationsPreferences();
+ }
+ mIsSmsPreferenceClicked = false;
+ }
+
+ private void updateNotificationsPreferences() {
+ final boolean canNotify = !OsUtil.isAtLeastKLP()
+ || PhoneUtils.getDefault().isDefaultSmsApp();
+ mNotificationsEnabledPreference.setEnabled(canNotify);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // We do this on start rather than on resume because the sound picker is in a
+ // separate activity.
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateSmsEnabledPreferences();
+ updateNotificationsPreferences();
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
+ final String key) {
+ if (key.equals(mNotificationsEnabledPreferenceKey)) {
+ updateNotificationsPreferences();
+ } else if (key.equals(mRingtonePreferenceKey)) {
+ updateSoundSummary(sharedPreferences);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java b/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java
new file mode 100644
index 0000000..739d2dc
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java
@@ -0,0 +1,92 @@
+/*
+ * 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.appsettings;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.RadioButton;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BuglePrefs;
+
+/**
+ * Displays an on/off switch for group MMS setting for a given subscription.
+ */
+public class GroupMmsSettingDialog {
+ private final Context mContext;
+ private final int mSubId;
+ private AlertDialog mDialog;
+
+ /**
+ * Shows a new group MMS setting dialog.
+ */
+ public static void showDialog(final Context context, final int subId) {
+ new GroupMmsSettingDialog(context, subId).show();
+ }
+
+ private GroupMmsSettingDialog(final Context context, final int subId) {
+ mContext = context;
+ mSubId = subId;
+ }
+
+ private void show() {
+ Assert.isNull(mDialog);
+ mDialog = new AlertDialog.Builder(mContext)
+ .setView(createView())
+ .setTitle(R.string.group_mms_pref_title)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private void changeGroupMmsSettings(final boolean enable) {
+ Assert.notNull(mDialog);
+ BuglePrefs.getSubscriptionPrefs(mSubId).putBoolean(
+ mContext.getString(R.string.group_mms_pref_key), enable);
+ mDialog.dismiss();
+ }
+
+ private View createView() {
+ final LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final View rootView = inflater.inflate(R.layout.group_mms_setting_dialog, null, false);
+ final RadioButton disableButton = (RadioButton)
+ rootView.findViewById(R.id.disable_group_mms_button);
+ final RadioButton enableButton = (RadioButton)
+ rootView.findViewById(R.id.enable_group_mms_button);
+ disableButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ changeGroupMmsSettings(false);
+ }
+ });
+ enableButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ changeGroupMmsSettings(true);
+ }
+ });
+ final boolean mmsEnabled = BuglePrefs.getSubscriptionPrefs(mSubId).getBoolean(
+ mContext.getString(R.string.group_mms_pref_key),
+ mContext.getResources().getBoolean(R.bool.group_mms_pref_default));
+ enableButton.setChecked(mmsEnabled);
+ disableButton.setChecked(!mmsEnabled);
+ return rootView;
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java b/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java
new file mode 100644
index 0000000..e02823f
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java
@@ -0,0 +1,246 @@
+/*
+ * 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.appsettings;
+
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.support.v4.app.NavUtils;
+import android.text.TextUtils;
+import android.view.MenuItem;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.ParticipantRefresh;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.ApnDatabase;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.PhoneUtils;
+
+public class PerSubscriptionSettingsActivity extends BugleActionBarActivity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ final String title = getIntent().getStringExtra(
+ UIIntents.UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE);
+ if (!TextUtils.isEmpty(title)) {
+ getSupportActionBar().setTitle(title);
+ } else {
+ // This will fall back to the default title, i.e. "Messaging settings," so No-op.
+ }
+
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ final PerSubscriptionSettingsFragment fragment = new PerSubscriptionSettingsFragment();
+ ft.replace(android.R.id.content, fragment);
+ ft.commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public static class PerSubscriptionSettingsFragment extends PreferenceFragment
+ implements OnSharedPreferenceChangeListener {
+ private PhoneNumberPreference mPhoneNumberPreference;
+ private Preference mGroupMmsPreference;
+ private String mGroupMmsPrefKey;
+ private String mPhoneNumberKey;
+ private int mSubId;
+
+ public PerSubscriptionSettingsFragment() {
+ // Required empty constructor
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Get sub id from launch intent
+ final Intent intent = getActivity().getIntent();
+ Assert.notNull(intent);
+ mSubId = (intent != null) ? intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
+ ParticipantData.DEFAULT_SELF_SUB_ID) : ParticipantData.DEFAULT_SELF_SUB_ID;
+
+ final BuglePrefs subPrefs = Factory.get().getSubscriptionPrefs(mSubId);
+ getPreferenceManager().setSharedPreferencesName(subPrefs.getSharedPreferencesName());
+ addPreferencesFromResource(R.xml.preferences_per_subscription);
+
+ mPhoneNumberKey = getString(R.string.mms_phone_number_pref_key);
+ mPhoneNumberPreference = (PhoneNumberPreference) findPreference(mPhoneNumberKey);
+ final PreferenceCategory advancedCategory = (PreferenceCategory)
+ findPreference(getString(R.string.advanced_category_pref_key));
+ final PreferenceCategory mmsCategory = (PreferenceCategory)
+ findPreference(getString(R.string.mms_messaging_category_pref_key));
+
+ mPhoneNumberPreference.setDefaultPhoneNumber(
+ PhoneUtils.get(mSubId).getCanonicalForSelf(false/*allowOverride*/), mSubId);
+
+ mGroupMmsPrefKey = getString(R.string.group_mms_pref_key);
+ mGroupMmsPreference = findPreference(mGroupMmsPrefKey);
+ if (!MmsConfig.get(mSubId).getGroupMmsEnabled()) {
+ // Always show group messaging setting even if the SIM has no number
+ // If broadcast sms is selected, the SIM number is not needed
+ // If group mms is selected, the phone number dialog will popup when message
+ // is being sent, making sure we will have a self number for group mms.
+ mmsCategory.removePreference(mGroupMmsPreference);
+ } else {
+ mGroupMmsPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference pref) {
+ GroupMmsSettingDialog.showDialog(getActivity(), mSubId);
+ return true;
+ }
+ });
+ updateGroupMmsPrefSummary();
+ }
+
+ if (!MmsConfig.get(mSubId).getSMSDeliveryReportsEnabled()) {
+ final Preference deliveryReportsPref = findPreference(
+ getString(R.string.delivery_reports_pref_key));
+ mmsCategory.removePreference(deliveryReportsPref);
+ }
+ final Preference wirelessAlertPref = findPreference(getString(
+ R.string.wireless_alerts_key));
+ if (!isCellBroadcastAppLinkEnabled()) {
+ advancedCategory.removePreference(wirelessAlertPref);
+ } else {
+ wirelessAlertPref.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ try {
+ startActivity(UIIntents.get().getWirelessAlertsIntent());
+ } catch (final ActivityNotFoundException e) {
+ // Handle so we shouldn't crash if the wireless alerts
+ // implementation is broken.
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "Failed to launch wireless alerts activity", e);
+ }
+ return true;
+ }
+ });
+ }
+
+ // Access Point Names (APNs)
+ final Preference apnsPref = findPreference(getString(R.string.sms_apns_key));
+
+ if (MmsUtils.useSystemApnTable() && !ApnDatabase.doesDatabaseExist()) {
+ // Don't remove the ability to edit the local APN prefs if this device lets us
+ // access the system APN, but we can't find the MCC/MNC in the APN table and we
+ // created the local APN table in case the MCC/MNC was in there. In other words,
+ // if the local APN table exists, let the user edit it.
+ advancedCategory.removePreference(apnsPref);
+ } else {
+ final PreferenceScreen apnsScreen = (PreferenceScreen) findPreference(
+ getString(R.string.sms_apns_key));
+ apnsScreen.setIntent(UIIntents.get()
+ .getApnSettingsIntent(getPreferenceScreen().getContext(), mSubId));
+ }
+
+ // We want to disable preferences if we are not the default app, but we do all of the
+ // above first so that the user sees the correct information on the screen
+ if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
+ mGroupMmsPreference.setEnabled(false);
+ final Preference autoRetrieveMmsPreference =
+ findPreference(getString(R.string.auto_retrieve_mms_pref_key));
+ autoRetrieveMmsPreference.setEnabled(false);
+ final Preference deliveryReportsPreference =
+ findPreference(getString(R.string.delivery_reports_pref_key));
+ deliveryReportsPreference.setEnabled(false);
+ }
+ }
+
+ private boolean isCellBroadcastAppLinkEnabled() {
+ if (!MmsConfig.get(mSubId).getShowCellBroadcast()) {
+ return false;
+ }
+ try {
+ final PackageManager pm = getActivity().getPackageManager();
+ return pm.getApplicationEnabledSetting(UIIntents.CMAS_COMPONENT)
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ } catch (final IllegalArgumentException ignored) {
+ // CMAS app not installed.
+ }
+ return false;
+ }
+
+ private void updateGroupMmsPrefSummary() {
+ final boolean groupMmsEnabled = getPreferenceScreen().getSharedPreferences().getBoolean(
+ mGroupMmsPrefKey, getResources().getBoolean(R.bool.group_mms_pref_default));
+ mGroupMmsPreference.setSummary(groupMmsEnabled ?
+ R.string.enable_group_mms : R.string.disable_group_mms);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
+ final String key) {
+ if (key.equals(mGroupMmsPrefKey)) {
+ updateGroupMmsPrefSummary();
+ } else if (key.equals(mPhoneNumberKey)) {
+ // Save the changed phone number in preferences specific to the sub id
+ final String newPhoneNumber = mPhoneNumberPreference.getText();
+ final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
+ if (TextUtils.isEmpty(newPhoneNumber)) {
+ subPrefs.remove(mPhoneNumberKey);
+ } else {
+ subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
+ newPhoneNumber);
+ }
+ // Update the self participants so the new phone number will be reflected
+ // everywhere in the UI.
+ ParticipantRefresh.refreshSelfParticipants();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java b/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java
new file mode 100644
index 0000000..0c9c018
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java
@@ -0,0 +1,116 @@
+/*
+ * 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.appsettings;
+
+import android.content.Context;
+import android.preference.EditTextPreference;
+import android.support.v4.text.BidiFormatter;
+import android.support.v4.text.TextDirectionHeuristicsCompat;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.messaging.R;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Preference that displays a phone number and allows editing via a dialog.
+ * <p>
+ * A default number can be assigned, which is shown in the preference view and
+ * used to populate the dialog editor when the preference value is not set. If
+ * the user sets the preference to a number equivalent to the default, the
+ * underlying preference is cleared.
+ */
+public class PhoneNumberPreference extends EditTextPreference {
+
+ private String mDefaultPhoneNumber;
+ private int mSubId;
+
+ public PhoneNumberPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mDefaultPhoneNumber = "";
+ }
+
+ public void setDefaultPhoneNumber(final String phoneNumber, final int subscriptionId) {
+ mDefaultPhoneNumber = phoneNumber;
+ mSubId = subscriptionId;
+ }
+
+ @Override
+ protected void onBindView(final View view) {
+ // Show the preference value if it's set, or the default number if not.
+ // If we don't have a default, fall back to a static string (e.g. Unknown).
+ String value = getText();
+ if (TextUtils.isEmpty(value)) {
+ value = mDefaultPhoneNumber;
+ }
+ final String displayValue = (!TextUtils.isEmpty(value))
+ ? PhoneUtils.get(mSubId).formatForDisplay(value)
+ : getContext().getString(R.string.unknown_phone_number_pref_display_value);
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final String phoneNumber = bidiFormatter.unicodeWrap
+ (displayValue, TextDirectionHeuristicsCompat.LTR);
+ // Set the value as the summary and let the superclass populate the views
+ setSummary(phoneNumber);
+ super.onBindView(view);
+ }
+
+ @Override
+ protected void onBindDialogView(final View view) {
+ super.onBindDialogView(view);
+
+ final String value = getText();
+
+ // If the preference is empty, populate the EditText with the default number instead.
+ if (TextUtils.isEmpty(value) && !TextUtils.isEmpty(mDefaultPhoneNumber)) {
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final String phoneNumber = bidiFormatter.unicodeWrap
+ (PhoneUtils.get(mSubId).getCanonicalBySystemLocale(mDefaultPhoneNumber),
+ TextDirectionHeuristicsCompat.LTR);
+ getEditText().setText(phoneNumber);
+ }
+ getEditText().setInputType(InputType.TYPE_CLASS_PHONE);
+ }
+
+ @Override
+ protected void onDialogClosed(final boolean positiveResult) {
+ if (positiveResult && mDefaultPhoneNumber != null) {
+ final String value = getEditText().getText().toString();
+ final PhoneUtils phoneUtils = PhoneUtils.get(mSubId);
+ final String phoneNumber = phoneUtils.getCanonicalBySystemLocale(value);
+ final String defaultPhoneNumber = phoneUtils.getCanonicalBySystemLocale(
+ mDefaultPhoneNumber);
+
+ // If the new value is the default, clear the preference.
+ if (phoneNumber.equals(defaultPhoneNumber)) {
+ setText("");
+ return;
+ }
+ }
+ super.onDialogClosed(positiveResult);
+ }
+
+ @Override
+ public void setText(final String text) {
+ super.setText(text);
+
+ // EditTextPreference doesn't show the value on the preference view, but we do.
+ // We thus need to force a rebind of the view when a new value is set.
+ notifyChanged();
+ }
+}
diff --git a/src/com/android/messaging/ui/appsettings/SettingsActivity.java b/src/com/android/messaging/ui/appsettings/SettingsActivity.java
new file mode 100644
index 0000000..75266d8
--- /dev/null
+++ b/src/com/android/messaging/ui/appsettings/SettingsActivity.java
@@ -0,0 +1,178 @@
+/*
+ * 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.appsettings;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.SettingsData;
+import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
+import com.android.messaging.datamodel.data.SettingsData.SettingsItem;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows the "master" settings activity that contains two parts, one for application-wide settings
+ * (dubbed "General settings"), and one or more for per-subscription settings (dubbed "Messaging
+ * settings" for single-SIM, and the actual SIM name for multi-SIM). Clicking on either item
+ * (e.g. "General settings") will open the detail settings activity (ApplicationSettingsActivity
+ * in this case).
+ */
+public class SettingsActivity extends BugleActionBarActivity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ // Directly open the detailed settings page as the top-level settings activity if this is
+ // not a multi-SIM device.
+ if (PhoneUtils.getDefault().getActiveSubscriptionCount() <= 1) {
+ UIIntents.get().launchApplicationSettingsActivity(this, true /* topLevel */);
+ finish();
+ } else {
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, new SettingsFragment())
+ .commit();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public static class SettingsFragment extends Fragment implements SettingsDataListener {
+ private ListView mListView;
+ private SettingsListAdapter mAdapter;
+ private final Binding<SettingsData> mBinding = BindingBase.createBinding(this);
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinding.bind(DataModel.get().createSettingsData(getActivity(), this));
+ mBinding.getData().init(getLoaderManager(), mBinding);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.settings_fragment, container, false);
+ mListView = (ListView) view.findViewById(android.R.id.list);
+ mAdapter = new SettingsListAdapter(getActivity());
+ mListView.setAdapter(mAdapter);
+ return view;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mBinding.unbind();
+ }
+
+ @Override
+ public void onSelfParticipantDataLoaded(SettingsData data) {
+ mBinding.ensureBound(data);
+ mAdapter.setSettingsItems(data.getSettingsItems());
+ }
+
+ /**
+ * An adapter that displays a list of SettingsItem.
+ */
+ private class SettingsListAdapter extends ArrayAdapter<SettingsItem> {
+ public SettingsListAdapter(final Context context) {
+ super(context, R.layout.settings_item_view, new ArrayList<SettingsItem>());
+ }
+
+ public void setSettingsItems(final List<SettingsItem> newList) {
+ clear();
+ addAll(newList);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public View getView(final int position, final View convertView,
+ final ViewGroup parent) {
+ View itemView;
+ if (convertView != null) {
+ itemView = convertView;
+ } else {
+ final LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ itemView = inflater.inflate(
+ R.layout.settings_item_view, parent, false);
+ }
+ final SettingsItem item = getItem(position);
+ final TextView titleTextView = (TextView) itemView.findViewById(R.id.title);
+ final TextView subtitleTextView = (TextView) itemView.findViewById(R.id.subtitle);
+ final String summaryText = item.getDisplayDetail();
+ titleTextView.setText(item.getDisplayName());
+ if (!TextUtils.isEmpty(summaryText)) {
+ subtitleTextView.setText(summaryText);
+ subtitleTextView.setVisibility(View.VISIBLE);
+ } else {
+ subtitleTextView.setVisibility(View.GONE);
+ }
+ itemView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ switch (item.getType()) {
+ case SettingsItem.TYPE_GENERAL_SETTINGS:
+ UIIntents.get().launchApplicationSettingsActivity(getActivity(),
+ false /* topLevel */);
+ break;
+
+ case SettingsItem.TYPE_PER_SUBSCRIPTION_SETTINGS:
+ UIIntents.get().launchPerSubscriptionSettingsActivity(getActivity(),
+ item.getSubId(), item.getActivityTitle());
+ break;
+
+ default:
+ Assert.fail("unrecognized setting type!");
+ break;
+ }
+ }
+ });
+ return itemView;
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java
new file mode 100644
index 0000000..a540597
--- /dev/null
+++ b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java
@@ -0,0 +1,56 @@
+/*
+ * 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.attachmentchooser;
+
+import android.app.Fragment;
+import android.os.Bundle;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost;
+import com.android.messaging.util.Assert;
+
+public class AttachmentChooserActivity extends BugleActionBarActivity implements
+ AttachmentChooserFragmentHost {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.attachment_chooser_activity);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ if (fragment instanceof AttachmentChooserFragment) {
+ final String conversationId =
+ getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+ Assert.notNull(conversationId);
+ final AttachmentChooserFragment chooserFragment =
+ (AttachmentChooserFragment) fragment;
+ chooserFragment.setConversationId(conversationId);
+ chooserFragment.setHost(this);
+ }
+ }
+
+ @Override
+ public void onConfirmSelection() {
+ setResult(RESULT_OK);
+ finish();
+ }
+}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java
new file mode 100644
index 0000000..b39dc3e
--- /dev/null
+++ b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java
@@ -0,0 +1,183 @@
+/*
+ * 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.attachmentchooser;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+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 android.widget.ArrayAdapter;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+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.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.attachmentchooser.AttachmentGridView.AttachmentGridHost;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AttachmentChooserFragment extends Fragment implements DraftMessageDataListener,
+ AttachmentGridHost {
+ public interface AttachmentChooserFragmentHost {
+ void onConfirmSelection();
+ }
+
+ private AttachmentGridView mAttachmentGridView;
+ private AttachmentGridAdapter mAdapter;
+ private AttachmentChooserFragmentHost mHost;
+
+ @VisibleForTesting
+ Binding<DraftMessageData> mBinding = BindingBase.createBinding(this);
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.attachment_chooser_fragment, container, false);
+ mAttachmentGridView = (AttachmentGridView) view.findViewById(R.id.grid);
+ mAdapter = new AttachmentGridAdapter(getActivity());
+ mAttachmentGridView.setAdapter(mAdapter);
+ mAttachmentGridView.setHost(this);
+ setHasOptionsMenu(true);
+ return view;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mBinding.unbind();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.attachment_chooser_menu, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_confirm_selection:
+ confirmSelection();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @VisibleForTesting
+ void confirmSelection() {
+ if (mBinding.isBound()) {
+ mBinding.getData().removeExistingAttachments(
+ mAttachmentGridView.getUnselectedAttachments());
+ mBinding.getData().saveToStorage(mBinding);
+ mHost.onConfirmSelection();
+ }
+ }
+
+ public void setConversationId(final String conversationId) {
+ mBinding.bind(DataModel.get().createDraftMessageData(conversationId));
+ mBinding.getData().addListener(this);
+ mBinding.getData().loadFromStorage(mBinding, null, false);
+ }
+
+ public void setHost(final AttachmentChooserFragmentHost host) {
+ mHost = host;
+ }
+
+ @Override
+ public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
+ mBinding.ensureBound(data);
+ if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
+ DraftMessageData.ATTACHMENTS_CHANGED) {
+ mAdapter.onAttachmentsLoaded(data.getReadOnlyAttachments());
+ }
+ }
+
+ @Override
+ public void onDraftAttachmentLimitReached(final DraftMessageData data) {
+ // Do nothing since the user is in the process of unselecting attachments.
+ }
+
+ @Override
+ public void onDraftAttachmentLoadFailed() {
+ // Do nothing since the user is in the process of unselecting attachments.
+ }
+
+ @Override
+ public void displayPhoto(final Rect viewRect, final Uri photoUri) {
+ final Uri imagesUri = MessagingContentProvider.buildDraftImagesUri(
+ mBinding.getData().getConversationId());
+ UIIntents.get().launchFullScreenPhotoViewer(
+ getActivity(), photoUri, viewRect, imagesUri);
+ }
+
+ @Override
+ public void updateSelectionCount(int count) {
+ if (getActivity() instanceof BugleActionBarActivity) {
+ final ActionBar actionBar = ((BugleActionBarActivity) getActivity())
+ .getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(getResources().getString(
+ R.string.attachment_chooser_selection, count));
+ }
+ }
+ }
+
+ class AttachmentGridAdapter extends ArrayAdapter<MessagePartData> {
+ public AttachmentGridAdapter(final Context context) {
+ super(context, R.layout.attachment_grid_item_view, new ArrayList<MessagePartData>());
+ }
+
+ public void onAttachmentsLoaded(final List<MessagePartData> attachments) {
+ clear();
+ addAll(attachments);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ AttachmentGridItemView itemView;
+ final MessagePartData item = getItem(position);
+ if (convertView != null && convertView instanceof AttachmentGridItemView) {
+ itemView = (AttachmentGridItemView) convertView;
+ } else {
+ final LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ itemView = (AttachmentGridItemView) inflater.inflate(
+ R.layout.attachment_grid_item_view, parent, false);
+ }
+ itemView.bind(item, mAttachmentGridView);
+ return itemView;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java
new file mode 100644
index 0000000..8bb7356
--- /dev/null
+++ b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java
@@ -0,0 +1,119 @@
+/*
+ * 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.attachmentchooser;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.ui.AttachmentPreviewFactory;
+import com.android.messaging.util.Assert;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Shows an item in the attachment picker grid.
+ */
+public class AttachmentGridItemView extends FrameLayout {
+ public interface HostInterface {
+ boolean isItemSelected(MessagePartData attachment);
+ void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment);
+ void onItemClicked(AttachmentGridItemView view, MessagePartData attachment);
+ }
+
+ @VisibleForTesting
+ MessagePartData mAttachmentData;
+ private FrameLayout mAttachmentViewContainer;
+ private CheckBox mCheckBox;
+ private HostInterface mHostInterface;
+
+ public AttachmentGridItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAttachmentViewContainer = (FrameLayout) findViewById(R.id.attachment_container);
+ mCheckBox = (CheckBox) findViewById(R.id.checkbox);
+ mCheckBox.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHostInterface.onItemCheckedChanged(AttachmentGridItemView.this, mAttachmentData);
+ }
+ });
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHostInterface.onItemClicked(AttachmentGridItemView.this, mAttachmentData);
+ }
+ });
+ addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ // Enlarge the clickable region for the checkbox.
+ final int touchAreaIncrease = getResources().getDimensionPixelOffset(
+ R.dimen.attachment_grid_checkbox_area_increase);
+ final Rect region = new Rect();
+ mCheckBox.getHitRect(region);
+ region.inset(-touchAreaIncrease, -touchAreaIncrease);
+ setTouchDelegate(new TouchDelegate(region, mCheckBox));
+ }
+ });
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ // The grid view auto-fits the columns, so we want to let the height match the width
+ // to make the attachment preview square.
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+
+ public void bind(final MessagePartData attachment, final HostInterface hostInterface) {
+ Assert.isTrue(attachment.isAttachment());
+ mHostInterface = hostInterface;
+ updateSelectedState();
+ if (mAttachmentData == null || !mAttachmentData.equals(attachment)) {
+ mAttachmentData = attachment;
+ updateAttachmentView();
+ }
+ }
+
+ @VisibleForTesting
+ HostInterface testGetHostInterface() {
+ return mHostInterface;
+ }
+
+ public void updateSelectedState() {
+ mCheckBox.setChecked(mHostInterface.isItemSelected(mAttachmentData));
+ }
+
+ private void updateAttachmentView() {
+ mAttachmentViewContainer.removeAllViews();
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(inflater,
+ mAttachmentData, mAttachmentViewContainer,
+ AttachmentPreviewFactory.TYPE_CHOOSER_GRID, true /* startImageRequest */, null);
+ mAttachmentViewContainer.addView(attachmentView);
+ }
+}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java
new file mode 100644
index 0000000..abf61dc
--- /dev/null
+++ b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java
@@ -0,0 +1,172 @@
+/*
+ * 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.attachmentchooser;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.widget.GridView;
+
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentGridAdapter;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.UiUtils;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Displays a grid of attachment previews for the user to choose which to select/unselect
+ */
+public class AttachmentGridView extends GridView implements
+ AttachmentGridItemView.HostInterface {
+ public interface AttachmentGridHost {
+ void displayPhoto(final Rect viewRect, final Uri photoUri);
+ void updateSelectionCount(final int count);
+ }
+
+ // By default everything is selected so only need to keep track of the unselected set.
+ private final Set<MessagePartData> mUnselectedSet;
+ private AttachmentGridHost mHost;
+
+ public AttachmentGridView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mUnselectedSet = new HashSet<>();
+ }
+
+ public void setHost(final AttachmentGridHost host) {
+ mHost = host;
+ }
+
+ @Override
+ public boolean isItemSelected(final MessagePartData attachment) {
+ return !mUnselectedSet.contains(attachment);
+ }
+
+ @Override
+ public void onItemClicked(final AttachmentGridItemView view, final MessagePartData attachment) {
+ // If the item is an image, show the photo viewer. All the other types (video, audio,
+ // vcard) have internal click handling for showing previews so we don't need to handle them
+ if (attachment.isImage()) {
+ mHost.displayPhoto(UiUtils.getMeasuredBoundsOnScreen(view), attachment.getContentUri());
+ }
+ }
+
+ @Override
+ public void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment) {
+ // Toggle selection.
+ if (isItemSelected(attachment)) {
+ mUnselectedSet.add(attachment);
+ } else {
+ mUnselectedSet.remove(attachment);
+ }
+ view.updateSelectedState();
+ updateSelectionCount();
+ }
+
+ public Set<MessagePartData> getUnselectedAttachments() {
+ return Collections.unmodifiableSet(mUnselectedSet);
+ }
+
+ private void updateSelectionCount() {
+ final int count = getAdapter().getCount() - mUnselectedSet.size();
+ Assert.isTrue(count >= 0);
+ mHost.updateSelectionCount(count);
+ }
+
+ private void refreshViews() {
+ if (getAdapter() instanceof AttachmentGridAdapter) {
+ ((AttachmentGridAdapter) getAdapter()).notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ final SavedState savedState = new SavedState(superState);
+ savedState.unselectedParts = mUnselectedSet
+ .toArray(new MessagePartData[mUnselectedSet.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());
+ mUnselectedSet.clear();
+ for (int i = 0; i < savedState.unselectedParts.length; i++) {
+ final MessagePartData unselectedPart = savedState.unselectedParts[i];
+ mUnselectedSet.add(unselectedPart);
+ }
+ refreshViews();
+ }
+
+ /**
+ * Persists the item selection state to saved instance state so we can restore on activity
+ * recreation
+ */
+ public static class SavedState extends BaseSavedState {
+ MessagePartData[] unselectedParts;
+
+ SavedState(final Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(final Parcel in) {
+ super(in);
+
+ // Read parts
+ final int partCount = in.readInt();
+ unselectedParts = new MessagePartData[partCount];
+ for (int i = 0; i < partCount; i++) {
+ unselectedParts[i] = ((MessagePartData) in.readParcelable(
+ MessagePartData.class.getClassLoader()));
+ }
+ }
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ super.writeToParcel(out, flags);
+
+ // Write parts
+ out.writeInt(unselectedParts.length);
+ for (final MessagePartData image : unselectedParts) {
+ out.writeParcelable(image, flags);
+ }
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.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/contact/AddContactsConfirmationDialog.java b/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java
new file mode 100644
index 0000000..9c1393d
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java
@@ -0,0 +1,85 @@
+/*
+ * 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.contact;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.AccessibilityUtil;
+
+public class AddContactsConfirmationDialog implements DialogInterface.OnClickListener {
+ private final Context mContext;
+ private final Uri mAvatarUri;
+ private final String mNormalizedDestination;
+
+ public AddContactsConfirmationDialog(final Context context, final Uri avatarUri,
+ final String normalizedDestination) {
+ mContext = context;
+ mAvatarUri = avatarUri;
+ mNormalizedDestination = normalizedDestination;
+ }
+
+ public void show() {
+ final int confirmAddContactStringId = R.string.add_contact_confirmation;
+ final int cancelStringId = android.R.string.cancel;
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.add_contact_confirmation_dialog_title)
+ .setView(createBodyView())
+ .setPositiveButton(confirmAddContactStringId, this)
+ .setNegativeButton(cancelStringId, null)
+ .create();
+ alertDialog.show();
+ final Resources resources = mContext.getResources();
+ final Button cancelButton = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
+ if (cancelButton != null) {
+ cancelButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color));
+ }
+ final Button addButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ if (addButton != null) {
+ addButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color));
+ }
+ }
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ UIIntents.get().launchAddContactActivity(mContext, mNormalizedDestination);
+ }
+
+ private View createBodyView() {
+ final View view = LayoutInflater.from(mContext).inflate(
+ R.layout.add_contacts_confirmation_dialog_body, null);
+ final ContactIconView iconView = (ContactIconView) view.findViewById(R.id.contact_icon);
+ iconView.setImageResourceUri(mAvatarUri);
+ final TextView textView = (TextView) view.findViewById(R.id.participant_name);
+ textView.setText(mNormalizedDestination);
+ // Accessibility reason : in case phone numbers are mixed in the display name,
+ // we need to vocalize it for talkback.
+ final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
+ mContext.getResources(), mNormalizedDestination);
+ textView.setContentDescription(vocalizedDisplayName);
+ return view;
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java b/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java
new file mode 100644
index 0000000..7263c54
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java
@@ -0,0 +1,62 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.CustomHeaderPagerListViewHolder;
+import com.android.messaging.ui.contact.ContactListItemView.HostInterface;
+
+/**
+ * Holds the all contacts view for the contact picker's view pager.
+ */
+public class AllContactsListViewHolder extends CustomHeaderPagerListViewHolder {
+ public AllContactsListViewHolder(final Context context, final HostInterface clivHostInterface) {
+ super(context, new ContactListAdapter(context, null, clivHostInterface,
+ true /* needAlphabetHeader */));
+ }
+
+ @Override
+ protected int getLayoutResId() {
+ return R.layout.all_contacts_list_view;
+ }
+
+ @Override
+ protected int getPageTitleResId() {
+ return R.string.contact_picker_all_contacts_tab_title;
+ }
+
+ @Override
+ protected int getEmptyViewResId() {
+ return R.id.empty_view;
+ }
+
+ @Override
+ protected int getListViewResId() {
+ return R.id.all_contacts_list;
+ }
+
+ @Override
+ protected int getEmptyViewTitleResId() {
+ return R.string.contact_list_empty_text;
+ }
+
+ @Override
+ protected int getEmptyViewImageResId() {
+ return R.drawable.ic_oobe_freq_list;
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
new file mode 100644
index 0000000..7df62de
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
@@ -0,0 +1,138 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.graphics.drawable.StateListDrawable;
+import android.net.Uri;
+import android.support.v4.text.BidiFormatter;
+import android.support.v4.text.TextDirectionHeuristicsCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.android.ex.chips.DropdownChipLayouter;
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ContactRecipientEntryUtils;
+
+/**
+ * An implementation for {@link DropdownChipLayouter}. Layouts the dropdown
+ * list in the ContactRecipientAutoCompleteView in Material style.
+ */
+public class ContactDropdownLayouter extends DropdownChipLayouter {
+ private final ContactListItemView.HostInterface mClivHostInterface;
+
+ public ContactDropdownLayouter(final LayoutInflater inflater, final Context context,
+ final ContactListItemView.HostInterface clivHostInterface) {
+ super(inflater, context);
+ mClivHostInterface = new ContactListItemView.HostInterface() {
+
+ @Override
+ public void onContactListItemClicked(final ContactListItemData item,
+ final ContactListItemView view) {
+ // The chips UI will handle auto-complete item click events, so No-op here.
+ }
+
+ @Override
+ public boolean isContactSelected(final ContactListItemData item) {
+ // In chips drop down we don't show any selected checkmark per design.
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Bind a drop down view to a RecipientEntry. We'd like regular dropdown items (BASE_RECIPIENT)
+ * to behave the same as regular ContactListItemViews, while using the chips library's
+ * item styling for alternates dropdown items (happens when you click on a chip).
+ */
+ @Override
+ public View bindView(final View convertView, final ViewGroup parent, final RecipientEntry entry,
+ final int position, AdapterType type, final String substring,
+ final StateListDrawable deleteDrawable) {
+ if (type != AdapterType.BASE_RECIPIENT) {
+ if (type == AdapterType.SINGLE_RECIPIENT) {
+ // Treat single recipients the same way as alternates. The base implementation of
+ // single recipients would try to simplify the destination by tokenizing. We'd
+ // like to always show the full destination address per design request.
+ type = AdapterType.RECIPIENT_ALTERNATES;
+ }
+ return super.bindView(convertView, parent, entry, position, type, substring,
+ deleteDrawable);
+ }
+
+ // Default to show all the information
+ // RTL : To format contact name and detail if they happen to be phone numbers.
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final String displayName = bidiFormatter.unicodeWrap(
+ ContactRecipientEntryUtils.getDisplayNameForContactList(entry),
+ TextDirectionHeuristicsCompat.LTR);
+ final String destination = bidiFormatter.unicodeWrap(
+ ContactRecipientEntryUtils.formatDestination(entry),
+ TextDirectionHeuristicsCompat.LTR);
+ final View itemView = reuseOrInflateView(convertView, parent, type);
+
+ // Bold the string that is matched.
+ final CharSequence[] styledResults =
+ getStyledResults(substring, displayName, destination);
+
+ Assert.isTrue(itemView instanceof ContactListItemView);
+ final ContactListItemView contactListItemView = (ContactListItemView) itemView;
+ contactListItemView.setImageClickHandlerDisabled(true);
+ contactListItemView.bind(entry, styledResults[0], styledResults[1],
+ mClivHostInterface, (type == AdapterType.SINGLE_RECIPIENT));
+ return itemView;
+ }
+
+ @Override
+ protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
+ AdapterType type) {
+ if (showImage && view instanceof ContactIconView) {
+ final ContactIconView contactView = (ContactIconView) view;
+ // These show contact cards by default, but that isn't what we want here
+ contactView.setImageClickHandlerDisabled(true);
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ ParticipantData.getFromRecipientEntry(entry));
+ contactView.setImageResourceUri(avatarUri);
+ } else {
+ super.bindIconToView(showImage, entry, view, type);
+ }
+ }
+
+ @Override
+ protected int getItemLayoutResId(AdapterType type) {
+ switch (type) {
+ case BASE_RECIPIENT:
+ return R.layout.contact_list_item_view;
+ case RECIPIENT_ALTERNATES:
+ return R.layout.chips_alternates_dropdown_item;
+ default:
+ return R.layout.chips_alternates_dropdown_item;
+ }
+ }
+
+ @Override
+ protected int getAlternateItemLayoutResId(AdapterType type) {
+ return getItemLayoutResId(type);
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactListAdapter.java b/src/com/android/messaging/ui/contact/ContactListAdapter.java
new file mode 100644
index 0000000..d466b61
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactListAdapter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.contact;
+
+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 android.widget.SectionIndexer;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+
+public class ContactListAdapter extends CursorAdapter implements SectionIndexer {
+ private final ContactListItemView.HostInterface mClivHostInterface;
+ private final boolean mNeedAlphabetHeader;
+ private ContactSectionIndexer mSectionIndexer;
+
+ public ContactListAdapter(final Context context, final Cursor cursor,
+ final ContactListItemView.HostInterface clivHostInterface,
+ final boolean needAlphabetHeader) {
+ super(context, cursor, 0);
+ mClivHostInterface = clivHostInterface;
+ mNeedAlphabetHeader = needAlphabetHeader;
+ mSectionIndexer = new ContactSectionIndexer(cursor);
+ }
+
+ @Override
+ public void bindView(final View view, final Context context, final Cursor cursor) {
+ Assert.isTrue(view instanceof ContactListItemView);
+ final ContactListItemView contactListItemView = (ContactListItemView) view;
+ String alphabetHeader = null;
+ if (mNeedAlphabetHeader) {
+ final int position = cursor.getPosition();
+ final int section = mSectionIndexer.getSectionForPosition(position);
+ // Check if the position is the first in the section.
+ if (mSectionIndexer.getPositionForSection(section) == position) {
+ alphabetHeader = (String) mSectionIndexer.getSections()[section];
+ }
+ }
+ contactListItemView.bind(cursor, mClivHostInterface, mNeedAlphabetHeader, alphabetHeader);
+ }
+
+ @Override
+ public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(context);
+ return layoutInflater.inflate(R.layout.contact_list_item_view, parent, false);
+ }
+
+ @Override
+ public Cursor swapCursor(final Cursor newCursor) {
+ mSectionIndexer = new ContactSectionIndexer(newCursor);
+ return super.swapCursor(newCursor);
+ }
+
+ @Override
+ public Object[] getSections() {
+ return mSectionIndexer.getSections();
+ }
+
+ @Override
+ public int getPositionForSection(final int sectionIndex) {
+ return mSectionIndexer.getPositionForSection(sectionIndex);
+ }
+
+ @Override
+ public int getSectionForPosition(final int position) {
+ return mSectionIndexer.getSectionForPosition(position);
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactListItemView.java b/src/com/android/messaging/ui/contact/ContactListItemView.java
new file mode 100644
index 0000000..6904da6
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactListItemView.java
@@ -0,0 +1,177 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * The view for a single entry in a contact list.
+ */
+public class ContactListItemView extends LinearLayout implements OnClickListener {
+ public interface HostInterface {
+ void onContactListItemClicked(ContactListItemData item, ContactListItemView view);
+ boolean isContactSelected(ContactListItemData item);
+ }
+
+ @VisibleForTesting
+ final ContactListItemData mData;
+ private TextView mContactNameTextView;
+ private TextView mContactDetailsTextView;
+ private TextView mContactDetailTypeTextView;
+ private TextView mAlphabetHeaderTextView;
+ private ContactIconView mContactIconView;
+ private ImageView mContactCheckmarkView;
+ private HostInterface mHostInterface;
+ private boolean mShouldShowAlphabetHeader;
+
+ public ContactListItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = DataModel.get().createContactListItemData();
+ }
+
+ @Override
+ protected void onFinishInflate () {
+ mContactNameTextView = (TextView) findViewById(R.id.contact_name);
+ mContactDetailsTextView = (TextView) findViewById(R.id.contact_details);
+ mContactDetailTypeTextView = (TextView) findViewById(R.id.contact_detail_type);
+ mAlphabetHeaderTextView = (TextView) findViewById(R.id.alphabet_header);
+ mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
+ mContactCheckmarkView = (ImageView) findViewById(R.id.contact_checkmark);
+ }
+
+ /**
+ * Fills in the data associated with this view by binding to a contact cursor provided by
+ * ContactUtil.
+ * @param cursor the contact cursor.
+ * @param hostInterface host interface to this view.
+ * @param shouldShowAlphabetHeader whether an alphabetical header should shown on the side
+ * of this view. If {@code headerLabel} is empty, we will still leave space for it.
+ * @param headerLabel the alphabetical header on the side of this view, if it should be shown.
+ */
+ public void bind(final Cursor cursor, final HostInterface hostInterface,
+ final boolean shouldShowAlphabetHeader, final String headerLabel) {
+ mData.bind(cursor, headerLabel);
+ mHostInterface = hostInterface;
+ mShouldShowAlphabetHeader = shouldShowAlphabetHeader;
+ setOnClickListener(this);
+ updateViewAppearance();
+ }
+
+ /**
+ * Binds a RecipientEntry. This is used by the chips text view's dropdown layout.
+ * @param recipientEntry the source RecipientEntry provided by ContactDropdownLayouter, which
+ * was in turn directly from one of the existing chips, or from filtered results
+ * generated by ContactRecipientAdapter.
+ * @param styledName display name where the portion that matches the search text is bold.
+ * @param styledDestination number where the portion that matches the search text is bold.
+ * @param hostInterface host interface to this view.
+ * @param isSingleRecipient whether this item is shown as the only line item in the single
+ * recipient drop down from the chips view. If this is the case, we always show the
+ * contact avatar even if it's not a first-level entry.
+ */
+ public void bind(final RecipientEntry recipientEntry, final CharSequence styledName,
+ final CharSequence styledDestination, final HostInterface hostInterface,
+ final boolean isSingleRecipient) {
+ mData.bind(recipientEntry, styledName, styledDestination, isSingleRecipient);
+ mHostInterface = hostInterface;
+ mShouldShowAlphabetHeader = false;
+ updateViewAppearance();
+ }
+
+ private void updateViewAppearance() {
+ mContactNameTextView.setText(mData.getDisplayName());
+ mContactDetailsTextView.setText(mData.getDestination());
+ mContactDetailTypeTextView.setText(Phone.getTypeLabel(getResources(),
+ mData.getDestinationType(), mData.getDestinationLabel()));
+ final RecipientEntry recipientEntry = mData.getRecipientEntry();
+ final String destinationString = String.valueOf(mData.getDestination());
+ if (mData.getIsSimpleContactItem()) {
+ // This is a special number-with-avatar type of contact (for unknown contact chips
+ // and for direct "send to destination" item). In this case, make sure we only show
+ // the display name (phone number) and the avatar and hide everything else.
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ ParticipantData.getFromRecipientEntry(recipientEntry));
+ mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(),
+ mData.getLookupKey(), destinationString);
+ mContactIconView.setVisibility(VISIBLE);
+ mContactCheckmarkView.setVisibility(GONE);
+ mContactDetailTypeTextView.setVisibility(GONE);
+ mContactDetailsTextView.setVisibility(GONE);
+ mContactNameTextView.setVisibility(VISIBLE);
+ } else if (mData.getIsFirstLevel()) {
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ ParticipantData.getFromRecipientEntry(recipientEntry));
+ mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(),
+ mData.getLookupKey(), destinationString);
+ mContactIconView.setVisibility(VISIBLE);
+ mContactNameTextView.setVisibility(VISIBLE);
+ final boolean isSelected = mHostInterface.isContactSelected(mData);
+ setSelected(isSelected);
+ mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE);
+ mContactDetailsTextView.setVisibility(VISIBLE);
+ mContactDetailTypeTextView.setVisibility(VISIBLE);
+ } else {
+ mContactIconView.setImageResourceUri(null);
+ mContactIconView.setVisibility(INVISIBLE);
+ mContactNameTextView.setVisibility(GONE);
+ final boolean isSelected = mHostInterface.isContactSelected(mData);
+ setSelected(isSelected);
+ mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE);
+ mContactDetailsTextView.setVisibility(VISIBLE);
+ mContactDetailTypeTextView.setVisibility(VISIBLE);
+ }
+
+ if (mShouldShowAlphabetHeader) {
+ mAlphabetHeaderTextView.setVisibility(VISIBLE);
+ mAlphabetHeaderTextView.setText(mData.getAlphabetHeader());
+ } else {
+ mAlphabetHeaderTextView.setVisibility(GONE);
+ }
+ }
+
+ /**
+ * {@inheritDoc} from OnClickListener
+ */
+ @Override
+ public void onClick(final View v) {
+ Assert.isTrue(v == this);
+ Assert.isTrue(mHostInterface != null);
+ mHostInterface.onContactListItemClicked(mData, this);
+ }
+
+ public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) {
+ mContactIconView.setImageClickHandlerDisabled(isHandlerDisabled);
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactPickerFragment.java b/src/com/android/messaging/ui/contact/ContactPickerFragment.java
new file mode 100644
index 0000000..d803087
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactPickerFragment.java
@@ -0,0 +1,607 @@
+/*
+ * 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.contact;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.widget.Toolbar;
+import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.transition.Explode;
+import android.transition.Transition;
+import android.transition.Transition.EpicenterCallback;
+import android.transition.TransitionManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.action.ActionMonitor;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.ContactPickerData;
+import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.ui.CustomHeaderPagerViewHolder;
+import com.android.messaging.ui.CustomHeaderViewPager;
+import com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation;
+import com.android.messaging.ui.contact.ContactRecipientAutoCompleteView.ContactChipsChangeListener;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.RunsOnMainThread;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+
+/**
+ * Shows lists of contacts to start conversations with.
+ */
+public class ContactPickerFragment extends Fragment implements ContactPickerDataListener,
+ ContactListItemView.HostInterface, ContactChipsChangeListener, OnMenuItemClickListener,
+ GetOrCreateConversationActionListener {
+ public static final String FRAGMENT_TAG = "contactpicker";
+
+ // Undefined contact picker mode. We should never be in this state after the host activity has
+ // been created.
+ public static final int MODE_UNDEFINED = 0;
+
+ // The initial contact picker mode for starting a new conversation with one contact.
+ public static final int MODE_PICK_INITIAL_CONTACT = 1;
+
+ // The contact picker mode where one initial contact has been picked and we are showing
+ // only the chips edit box.
+ public static final int MODE_CHIPS_ONLY = 2;
+
+ // The contact picker mode for picking more contacts after starting the initial 1-1.
+ public static final int MODE_PICK_MORE_CONTACTS = 3;
+
+ // The contact picker mode when max number of participants is reached.
+ public static final int MODE_PICK_MAX_PARTICIPANTS = 4;
+
+ public interface ContactPickerFragmentHost {
+ void onGetOrCreateNewConversation(String conversationId);
+ void onBackButtonPressed();
+ void onInitiateAddMoreParticipants();
+ void onParticipantCountChanged(boolean canAddMoreParticipants);
+ void invalidateActionBar();
+ }
+
+ @VisibleForTesting
+ final Binding<ContactPickerData> mBinding = BindingBase.createBinding(this);
+
+ private ContactPickerFragmentHost mHost;
+ private ContactRecipientAutoCompleteView mRecipientTextView;
+ private CustomHeaderViewPager mCustomHeaderViewPager;
+ private AllContactsListViewHolder mAllContactsListViewHolder;
+ private FrequentContactsListViewHolder mFrequentContactsListViewHolder;
+ private View mRootView;
+ private View mPendingExplodeView;
+ private View mComposeDivider;
+ private Toolbar mToolbar;
+ private int mContactPickingMode = MODE_UNDEFINED;
+
+ // Keeps track of the currently selected phone numbers in the chips view to enable fast lookup.
+ private Set<String> mSelectedPhoneNumbers = null;
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAllContactsListViewHolder = new AllContactsListViewHolder(getActivity(), this);
+ mFrequentContactsListViewHolder = new FrequentContactsListViewHolder(getActivity(), this);
+
+ if (ContactUtil.hasReadContactsPermission()) {
+ mBinding.bind(DataModel.get().createContactPickerData(getActivity(), this));
+ mBinding.getData().init(getLoaderManager(), mBinding);
+ }
+ }
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.contact_picker_fragment, container, false);
+ mRecipientTextView = (ContactRecipientAutoCompleteView)
+ view.findViewById(R.id.recipient_text_view);
+ mRecipientTextView.setThreshold(0);
+ mRecipientTextView.setDropDownAnchor(R.id.compose_contact_divider);
+
+ mRecipientTextView.setContactChipsListener(this);
+ mRecipientTextView.setDropdownChipLayouter(new ContactDropdownLayouter(inflater,
+ getActivity(), this));
+ mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity(), this));
+ mRecipientTextView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before,
+ final int count) {
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count,
+ final int after) {
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ updateTextInputButtonsVisibility();
+ }
+ });
+
+ final CustomHeaderPagerViewHolder[] viewHolders = {
+ mFrequentContactsListViewHolder,
+ mAllContactsListViewHolder };
+
+ mCustomHeaderViewPager = (CustomHeaderViewPager) view.findViewById(R.id.contact_pager);
+ mCustomHeaderViewPager.setViewHolders(viewHolders);
+ mCustomHeaderViewPager.setViewPagerTabHeight(CustomHeaderViewPager.DEFAULT_TAB_STRIP_SIZE);
+ mCustomHeaderViewPager.setBackgroundColor(getResources()
+ .getColor(R.color.contact_picker_background));
+
+ // The view pager defaults to the frequent contacts page.
+ mCustomHeaderViewPager.setCurrentItem(0);
+
+ mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
+ mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_light);
+ mToolbar.setNavigationContentDescription(R.string.back);
+ mToolbar.setNavigationOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHost.onBackButtonPressed();
+ }
+ });
+
+ mToolbar.inflateMenu(R.menu.compose_menu);
+ mToolbar.setOnMenuItemClickListener(this);
+
+ mComposeDivider = view.findViewById(R.id.compose_contact_divider);
+ mRootView = view;
+ return view;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Called when the host activity has been created. At this point, the host activity should
+ * have set the contact picking mode for us so that we may update our visuals.
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ Assert.isTrue(mContactPickingMode != MODE_UNDEFINED);
+ updateVisualsForContactPickingMode(false /* animate */);
+ mHost.invalidateActionBar();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // We could not have bound to the data if the permission was denied.
+ if (mBinding.isBound()) {
+ mBinding.unbind();
+ }
+
+ if (mMonitor != null) {
+ mMonitor.unregister();
+ }
+ mMonitor = null;
+ }
+
+ @Override
+ public boolean onMenuItemClick(final MenuItem menuItem) {
+ switch (menuItem.getItemId()) {
+ case R.id.action_ime_dialpad_toggle:
+ final int baseInputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ if ((mRecipientTextView.getInputType() & InputType.TYPE_CLASS_PHONE) !=
+ InputType.TYPE_CLASS_PHONE) {
+ mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_PHONE);
+ menuItem.setIcon(R.drawable.ic_ime_light);
+ } else {
+ mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_TEXT);
+ menuItem.setIcon(R.drawable.ic_numeric_dialpad);
+ }
+ ImeUtil.get().showImeKeyboard(getActivity(), mRecipientTextView);
+ return true;
+
+ case R.id.action_add_more_participants:
+ mHost.onInitiateAddMoreParticipants();
+ return true;
+
+ case R.id.action_confirm_participants:
+ maybeGetOrCreateConversation();
+ return true;
+
+ case R.id.action_delete_text:
+ Assert.equals(MODE_PICK_INITIAL_CONTACT, mContactPickingMode);
+ mRecipientTextView.setText("");
+ return true;
+ }
+ return false;
+ }
+
+ @Override // From ContactPickerDataListener
+ public void onAllContactsCursorUpdated(final Cursor data) {
+ mBinding.ensureBound();
+ mAllContactsListViewHolder.onContactsCursorUpdated(data);
+ }
+
+ @Override // From ContactPickerDataListener
+ public void onFrequentContactsCursorUpdated(final Cursor data) {
+ mBinding.ensureBound();
+ mFrequentContactsListViewHolder.onContactsCursorUpdated(data);
+ if (data != null && data.getCount() == 0) {
+ // Show the all contacts list when there's no frequents.
+ mCustomHeaderViewPager.setCurrentItem(1);
+ }
+ }
+
+ @Override // From ContactListItemView.HostInterface
+ public void onContactListItemClicked(final ContactListItemData item,
+ final ContactListItemView view) {
+ if (!isContactSelected(item)) {
+ if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
+ mPendingExplodeView = view;
+ }
+ mRecipientTextView.appendRecipientEntry(item.getRecipientEntry());
+ } else if (mContactPickingMode != MODE_PICK_INITIAL_CONTACT) {
+ mRecipientTextView.removeRecipientEntry(item.getRecipientEntry());
+ }
+ }
+
+ @Override // From ContactListItemView.HostInterface
+ public boolean isContactSelected(final ContactListItemData item) {
+ return mSelectedPhoneNumbers != null &&
+ mSelectedPhoneNumbers.contains(PhoneUtils.getDefault().getCanonicalBySystemLocale(
+ item.getRecipientEntry().getDestination()));
+ }
+
+ /**
+ * Call this immediately after attaching the fragment, or when there's a ui state change that
+ * changes our host (i.e. restore from saved instance state).
+ */
+ public void setHost(final ContactPickerFragmentHost host) {
+ mHost = host;
+ }
+
+ public void setContactPickingMode(final int mode, final boolean animate) {
+ if (mContactPickingMode != mode) {
+ // Guard against impossible transitions.
+ Assert.isTrue(
+ // We may start from undefined mode to any mode when we are restoring state.
+ (mContactPickingMode == MODE_UNDEFINED) ||
+ (mContactPickingMode == MODE_PICK_INITIAL_CONTACT && mode == MODE_CHIPS_ONLY) ||
+ (mContactPickingMode == MODE_CHIPS_ONLY && mode == MODE_PICK_MORE_CONTACTS) ||
+ (mContactPickingMode == MODE_PICK_MORE_CONTACTS
+ && mode == MODE_PICK_MAX_PARTICIPANTS) ||
+ (mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS
+ && mode == MODE_PICK_MORE_CONTACTS));
+
+ mContactPickingMode = mode;
+ updateVisualsForContactPickingMode(animate);
+ }
+ }
+
+ private void showImeKeyboard() {
+ Assert.notNull(mRecipientTextView);
+ mRecipientTextView.requestFocus();
+
+ // showImeKeyboard() won't work until the layout is ready, so wait until layout is complete
+ // before showing the soft keyboard.
+ UiUtils.doOnceAfterLayoutChange(mRootView, new Runnable() {
+ @Override
+ public void run() {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ ImeUtil.get().showImeKeyboard(activity, mRecipientTextView);
+ }
+ }
+ });
+ mRecipientTextView.invalidate();
+ }
+
+ private void updateVisualsForContactPickingMode(final boolean animate) {
+ // Don't update visuals if the visuals haven't been inflated yet.
+ if (mRootView != null) {
+ final Menu menu = mToolbar.getMenu();
+ final MenuItem addMoreParticipantsItem = menu.findItem(
+ R.id.action_add_more_participants);
+ final MenuItem confirmParticipantsItem = menu.findItem(
+ R.id.action_confirm_participants);
+ switch (mContactPickingMode) {
+ case MODE_PICK_INITIAL_CONTACT:
+ addMoreParticipantsItem.setVisible(false);
+ confirmParticipantsItem.setVisible(false);
+ mCustomHeaderViewPager.setVisibility(View.VISIBLE);
+ mComposeDivider.setVisibility(View.INVISIBLE);
+ mRecipientTextView.setEnabled(true);
+ showImeKeyboard();
+ break;
+
+ case MODE_CHIPS_ONLY:
+ if (animate) {
+ if (mPendingExplodeView == null) {
+ // The user didn't click on any contact item, so use the toolbar as
+ // the view to "explode."
+ mPendingExplodeView = mToolbar;
+ }
+ startExplodeTransitionForContactLists(false /* show */);
+
+ ViewGroupItemVerticalExplodeAnimation.startAnimationForView(
+ mCustomHeaderViewPager, mPendingExplodeView, mRootView,
+ true /* snapshotView */, UiUtils.COMPOSE_TRANSITION_DURATION);
+ showHideContactPagerWithAnimation(false /* show */);
+ } else {
+ mCustomHeaderViewPager.setVisibility(View.GONE);
+ }
+
+ addMoreParticipantsItem.setVisible(true);
+ confirmParticipantsItem.setVisible(false);
+ mComposeDivider.setVisibility(View.VISIBLE);
+ mRecipientTextView.setEnabled(true);
+ break;
+
+ case MODE_PICK_MORE_CONTACTS:
+ if (animate) {
+ // Correctly set the start visibility state for the view pager and
+ // individual list items (hidden initially), so that the transition
+ // manager can properly track the visibility change for the explode.
+ mCustomHeaderViewPager.setVisibility(View.VISIBLE);
+ toggleContactListItemsVisibilityForPendingTransition(false /* show */);
+ startExplodeTransitionForContactLists(true /* show */);
+ }
+ addMoreParticipantsItem.setVisible(false);
+ confirmParticipantsItem.setVisible(true);
+ mCustomHeaderViewPager.setVisibility(View.VISIBLE);
+ mComposeDivider.setVisibility(View.INVISIBLE);
+ mRecipientTextView.setEnabled(true);
+ showImeKeyboard();
+ break;
+
+ case MODE_PICK_MAX_PARTICIPANTS:
+ addMoreParticipantsItem.setVisible(false);
+ confirmParticipantsItem.setVisible(true);
+ mCustomHeaderViewPager.setVisibility(View.VISIBLE);
+ mComposeDivider.setVisibility(View.INVISIBLE);
+ // TODO: Verify that this is okay for accessibility
+ mRecipientTextView.setEnabled(false);
+ break;
+
+ default:
+ Assert.fail("Unsupported contact picker mode!");
+ break;
+ }
+ updateTextInputButtonsVisibility();
+ }
+ }
+
+ private void updateTextInputButtonsVisibility() {
+ final Menu menu = mToolbar.getMenu();
+ final MenuItem keypadToggleItem = menu.findItem(R.id.action_ime_dialpad_toggle);
+ final MenuItem deleteTextItem = menu.findItem(R.id.action_delete_text);
+ if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
+ if (TextUtils.isEmpty(mRecipientTextView.getText())) {
+ deleteTextItem.setVisible(false);
+ keypadToggleItem.setVisible(true);
+ } else {
+ deleteTextItem.setVisible(true);
+ keypadToggleItem.setVisible(false);
+ }
+ } else {
+ deleteTextItem.setVisible(false);
+ keypadToggleItem.setVisible(false);
+ }
+ }
+
+ private void maybeGetOrCreateConversation() {
+ final ArrayList<ParticipantData> participants =
+ mRecipientTextView.getRecipientParticipantDataForConversationCreation();
+ if (ContactPickerData.isTooManyParticipants(participants.size())) {
+ UiUtils.showToast(R.string.too_many_participants);
+ } else if (participants.size() > 0 && mMonitor == null) {
+ mMonitor = GetOrCreateConversationAction.getOrCreateConversation(participants,
+ null, this);
+ }
+ }
+
+ /**
+ * Watches changes in contact chips to determine possible state transitions (e.g. creating
+ * the initial conversation, adding more participants or finish the current conversation)
+ */
+ @Override
+ public void onContactChipsChanged(final int oldCount, final int newCount) {
+ Assert.isTrue(oldCount != newCount);
+ if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
+ // Initial picking mode. Start a conversation once a recipient has been picked.
+ maybeGetOrCreateConversation();
+ } else if (mContactPickingMode == MODE_CHIPS_ONLY) {
+ // oldCount == 0 means we are restoring from savedInstanceState to add the existing
+ // chips, don't switch to "add more participants" mode in this case.
+ if (oldCount > 0 && mRecipientTextView.isFocused()) {
+ // Chips only mode. The user may have picked an additional contact or deleted the
+ // only existing contact. Either way, switch to picking more participants mode.
+ mHost.onInitiateAddMoreParticipants();
+ }
+ }
+ mHost.onParticipantCountChanged(ContactPickerData.getCanAddMoreParticipants(newCount));
+
+ // Refresh our local copy of the selected chips set to keep it up-to-date.
+ mSelectedPhoneNumbers = mRecipientTextView.getSelectedDestinations();
+ invalidateContactLists();
+ }
+
+ /**
+ * Listens for notification that invalid contacts have been removed during resolving them.
+ * These contacts were not local contacts, valid email, or valid phone numbers
+ */
+ @Override
+ public void onInvalidContactChipsPruned(final int prunedCount) {
+ Assert.isTrue(prunedCount > 0);
+ UiUtils.showToast(R.plurals.add_invalid_contact_error, prunedCount);
+ }
+
+ /**
+ * Listens for notification that the user has pressed enter/done on the keyboard with all
+ * contacts in place and we should create or go to the existing conversation now
+ */
+ @Override
+ public void onEntryComplete() {
+ if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT ||
+ mContactPickingMode == MODE_PICK_MORE_CONTACTS ||
+ mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS) {
+ // Avoid multiple calls to create in race cases (hit done right after selecting contact)
+ maybeGetOrCreateConversation();
+ }
+ }
+
+ private void invalidateContactLists() {
+ mAllContactsListViewHolder.invalidateList();
+ mFrequentContactsListViewHolder.invalidateList();
+ }
+
+ /**
+ * Kicks off a scene transition that animates visibility changes of individual contact list
+ * items via explode animation.
+ * @param show whether the contact lists are to be shown or hidden.
+ */
+ private void startExplodeTransitionForContactLists(final boolean show) {
+ if (!OsUtil.isAtLeastL()) {
+ // Explode animation is not supported pre-L.
+ return;
+ }
+ final Explode transition = new Explode();
+ final Rect epicenter = mPendingExplodeView == null ? null :
+ UiUtils.getMeasuredBoundsOnScreen(mPendingExplodeView);
+ transition.setDuration(UiUtils.COMPOSE_TRANSITION_DURATION);
+ transition.setInterpolator(UiUtils.EASE_IN_INTERPOLATOR);
+ transition.setEpicenterCallback(new EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(final Transition transition) {
+ return epicenter;
+ }
+ });
+
+ // Kick off the delayed scene explode transition. Anything happens after this line in this
+ // method before the next frame will be tracked by the transition manager for visibility
+ // changes and animated accordingly.
+ TransitionManager.beginDelayedTransition(mCustomHeaderViewPager,
+ transition);
+
+ toggleContactListItemsVisibilityForPendingTransition(show);
+ }
+
+ /**
+ * Toggle the visibility of contact list items in the contact lists for them to be tracked by
+ * the transition manager for pending explode transition.
+ */
+ private void toggleContactListItemsVisibilityForPendingTransition(final boolean show) {
+ if (!OsUtil.isAtLeastL()) {
+ // Explode animation is not supported pre-L.
+ return;
+ }
+ mAllContactsListViewHolder.toggleVisibilityForPendingTransition(show, mPendingExplodeView);
+ mFrequentContactsListViewHolder.toggleVisibilityForPendingTransition(show,
+ mPendingExplodeView);
+ }
+
+ private void showHideContactPagerWithAnimation(final boolean show) {
+ final boolean isPagerVisible = (mCustomHeaderViewPager.getVisibility() == View.VISIBLE);
+ if (show == isPagerVisible) {
+ return;
+ }
+
+ mCustomHeaderViewPager.animate().alpha(show ? 1F : 0F)
+ .setStartDelay(!show ? UiUtils.COMPOSE_TRANSITION_DURATION : 0)
+ .withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ mCustomHeaderViewPager.setVisibility(View.VISIBLE);
+ mCustomHeaderViewPager.setAlpha(show ? 0F : 1F);
+ }
+ })
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mCustomHeaderViewPager.setVisibility(show ? View.VISIBLE : View.GONE);
+ mCustomHeaderViewPager.setAlpha(1F);
+ }
+ });
+ }
+
+ @Override
+ public void onContactCustomColorLoaded(final ContactPickerData data) {
+ mBinding.ensureBound(data);
+ invalidateContactLists();
+ }
+
+ public void updateActionBar(final ActionBar actionBar) {
+ // Hide the action bar for contact picker mode. The custom ToolBar containing chips UI
+ // etc. will take the spot of the action bar.
+ actionBar.hide();
+ UiUtils.setStatusBarColor(getActivity(),
+ getResources().getColor(R.color.compose_notification_bar_background));
+ }
+
+ private GetOrCreateConversationActionMonitor mMonitor;
+
+ @Override
+ @RunsOnMainThread
+ public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
+ final Object data, final String conversationId) {
+ Assert.isTrue(monitor == mMonitor);
+ Assert.isTrue(conversationId != null);
+
+ mRecipientTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE |
+ InputType.TYPE_CLASS_TEXT);
+ mHost.onGetOrCreateNewConversation(conversationId);
+
+ mMonitor = null;
+ }
+
+ @Override
+ @RunsOnMainThread
+ public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
+ final Object data) {
+ Assert.isTrue(monitor == mMonitor);
+ LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
+ mMonitor = null;
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
new file mode 100644
index 0000000..25f422e
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
@@ -0,0 +1,286 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MergeCursor;
+import android.support.v4.util.Pair;
+import android.text.TextUtils;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.widget.Filter;
+
+import com.android.ex.chips.BaseRecipientAdapter;
+import com.android.ex.chips.RecipientAlternatesAdapter;
+import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.ContactRecipientEntryUtils;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * An extension on the base {@link BaseRecipientAdapter} that uses data layer from Bugle,
+ * such as the ContactRecipientPhotoManager that uses our own MediaResourceManager, and
+ * contact lookup that relies on ContactUtil. It provides data source and filtering ability
+ * for {@link ContactRecipientAutoCompleteView}
+ */
+public final class ContactRecipientAdapter extends BaseRecipientAdapter {
+ public ContactRecipientAdapter(final Context context,
+ final ContactListItemView.HostInterface clivHost) {
+ this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost);
+ }
+
+ public ContactRecipientAdapter(final Context context, final int preferredMaxResultCount,
+ final int queryMode, final ContactListItemView.HostInterface clivHost) {
+ super(context, preferredMaxResultCount, queryMode);
+ setPhotoManager(new ContactRecipientPhotoManager(context, clivHost));
+ }
+
+ @Override
+ public boolean forceShowAddress() {
+ // We should always use the SingleRecipientAddressAdapter
+ // And never use the RecipientAlternatesAdapter
+ return true;
+ }
+
+ @Override
+ public Filter getFilter() {
+ return new ContactFilter();
+ }
+
+ /**
+ * A Filter for a RecipientEditTextView that queries Bugle's ContactUtil for auto-complete
+ * results.
+ */
+ public class ContactFilter extends Filter {
+ // Used to sort filtered contacts when it has combined results from email and phone.
+ private final RecipientEntryComparator mComparator = new RecipientEntryComparator();
+
+ /**
+ * Returns a cursor containing the filtered results in contacts given the search text,
+ * and a boolean indicating whether the results are sorted.
+ *
+ * The queries are synchronously performed since this is not run on the main thread.
+ *
+ * Some locales (e.g. JPN) expect email addresses to be auto-completed for MMS.
+ * If this is the case, perform two queries on phone number followed by email and
+ * return the merged results.
+ */
+ @DoesNotRunOnMainThread
+ private Pair<Cursor, Boolean> getFilteredResultsCursor(final Context context,
+ final String searchText) {
+ Assert.isNotMainThread();
+ if (BugleGservices.get().getBoolean(
+ BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS,
+ BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT)) {
+ return Pair.create((Cursor) new MergeCursor(new Cursor[] {
+ ContactUtil.filterPhones(getContext(), searchText)
+ .performSynchronousQuery(),
+ ContactUtil.filterEmails(getContext(), searchText)
+ .performSynchronousQuery()
+ }), false /* the merged cursor is not sorted */);
+ } else {
+ return Pair.create(ContactUtil.filterDestination(getContext(), searchText)
+ .performSynchronousQuery(), true);
+ }
+ }
+
+ @Override
+ protected FilterResults performFiltering(final CharSequence constraint) {
+ Assert.isNotMainThread();
+ final FilterResults results = new FilterResults();
+
+ // No query, return empty results.
+ if (TextUtils.isEmpty(constraint)) {
+ clearTempEntries();
+ return results;
+ }
+
+ final String searchText = constraint.toString();
+
+ // Query for auto-complete results, since performFiltering() is not done on the
+ // main thread, perform the cursor loader queries directly.
+ final Pair<Cursor, Boolean> filteredResults = getFilteredResultsCursor(getContext(),
+ searchText);
+ final Cursor cursor = filteredResults.first;
+ final boolean sorted = filteredResults.second;
+ if (cursor != null) {
+ try {
+ final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
+
+ // First check if the constraint is a valid SMS destination. If so, add the
+ // destination as a suggestion item to the drop down.
+ if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
+ entries.add(ContactRecipientEntryUtils
+ .constructSendToDestinationEntry(searchText));
+ }
+
+ HashSet<Long> existingContactIds = new HashSet<Long>();
+ while (cursor.moveToNext()) {
+ // Make sure there's only one first-level contact (i.e. contact for which
+ // we show the avatar picture and name) for every contact id.
+ final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ final boolean isFirstLevel = !existingContactIds.contains(contactId);
+ if (isFirstLevel) {
+ existingContactIds.add(contactId);
+ }
+ entries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
+ isFirstLevel));
+ }
+
+ if (!sorted) {
+ Collections.sort(entries, mComparator);
+ }
+ results.values = entries;
+ results.count = 1;
+
+ } finally {
+ cursor.close();
+ }
+ }
+ return results;
+ }
+
+ @Override
+ protected void publishResults(final CharSequence constraint, final FilterResults results) {
+ mCurrentConstraint = constraint;
+ clearTempEntries();
+
+ if (results.values != null) {
+ @SuppressWarnings("unchecked")
+ final List<RecipientEntry> entries = (List<RecipientEntry>) results.values;
+ updateEntries(entries);
+ } else {
+ updateEntries(Collections.<RecipientEntry>emptyList());
+ }
+ }
+
+ private class RecipientEntryComparator implements Comparator<RecipientEntry> {
+ private final Collator mCollator;
+
+ public RecipientEntryComparator() {
+ mCollator = Collator.getInstance(Locale.getDefault());
+ mCollator.setStrength(Collator.PRIMARY);
+ }
+
+ /**
+ * Compare two RecipientEntry's, first by locale-aware display name comparison, then by
+ * contact id comparison, finally by first-level-ness comparison.
+ */
+ @Override
+ public int compare(RecipientEntry lhs, RecipientEntry rhs) {
+ // Send-to-destinations always appear before everything else.
+ final boolean sendToLhs = ContactRecipientEntryUtils
+ .isSendToDestinationContact(lhs);
+ final boolean sendToRhs = ContactRecipientEntryUtils
+ .isSendToDestinationContact(lhs);
+ if (sendToLhs != sendToRhs) {
+ if (sendToLhs) {
+ return -1;
+ } else if (sendToRhs) {
+ return 1;
+ }
+ }
+
+ final int displayNameCompare = mCollator.compare(lhs.getDisplayName(),
+ rhs.getDisplayName());
+ if (displayNameCompare != 0) {
+ return displayNameCompare;
+ }
+
+ // Long.compare could accomplish the following three lines, but this is only
+ // available in API 19+
+ final long lhsContactId = lhs.getContactId();
+ final long rhsContactId = rhs.getContactId();
+ final int contactCompare = lhsContactId < rhsContactId ? -1 :
+ (lhsContactId == rhsContactId ? 0 : 1);
+ if (contactCompare != 0) {
+ return contactCompare;
+ }
+
+ // These are the same contact. Make sure first-level contacts always
+ // appear at the front.
+ if (lhs.isFirstLevel()) {
+ return -1;
+ } else if (rhs.isFirstLevel()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when we need to substitute temporary recipient chips with better alternatives.
+ * For example, if a list of comma-delimited phone numbers are pasted into the edit box,
+ * we want to be able to look up in the ContactUtil for exact matches and get contact
+ * details such as name and photo thumbnail for the contact to display a better chip.
+ */
+ @Override
+ public void getMatchingRecipients(final ArrayList<String> inAddresses,
+ final RecipientMatchCallback callback) {
+ final int addressesSize = Math.min(
+ RecipientAlternatesAdapter.MAX_LOOKUPS, inAddresses.size());
+ final HashSet<String> addresses = new HashSet<String>();
+ for (int i = 0; i < addressesSize; i++) {
+ final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
+ addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
+ }
+
+ final Map<String, RecipientEntry> recipientEntries =
+ new HashMap<String, RecipientEntry>();
+ // query for each address
+ for (final String address : addresses) {
+ final Cursor cursor = ContactUtil.lookupDestination(getContext(), address)
+ .performSynchronousQuery();
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ // There may be multiple matches to the same number, always take the
+ // first match.
+ // TODO: May need to consider if there's an existing conversation
+ // that matches this particular contact and prioritize that contact.
+ final RecipientEntry entry =
+ ContactUtil.createRecipientEntryForPhoneQuery(cursor, true);
+ recipientEntries.put(address, entry);
+ }
+
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ // report matches
+ callback.matchesFound(recipientEntries);
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java b/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java
new file mode 100644
index 0000000..c7c2731
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java
@@ -0,0 +1,289 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.support.v7.appcompat.R;
+import android.text.Editable;
+import android.text.TextPaint;
+import android.text.TextWatcher;
+import android.text.util.Rfc822Tokenizer;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView;
+
+import com.android.ex.chips.RecipientEditTextView;
+import com.android.ex.chips.RecipientEntry;
+import com.android.ex.chips.recipientchip.DrawableRecipientChip;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.util.ContactRecipientEntryUtils;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * An extension for {@link RecipientEditTextView} which shows a list of Materialized contact chips.
+ * It uses Bugle's ContactUtil to perform contact lookup, and is able to return the list of
+ * recipients in the form of a ParticipantData list.
+ */
+public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
+ public interface ContactChipsChangeListener {
+ void onContactChipsChanged(int oldCount, int newCount);
+ void onInvalidContactChipsPruned(int prunedCount);
+ void onEntryComplete();
+ }
+
+ private final int mTextHeight;
+ private ContactChipsChangeListener mChipsChangeListener;
+
+ /**
+ * Watches changes in contact chips to determine possible state transitions.
+ */
+ private class ContactChipsWatcher implements TextWatcher {
+ /**
+ * Tracks the old chips count before text changes. Note that we currently don't compare
+ * the entire chip sets but just the cheaper-to-do before and after counts, because
+ * the chips view don't allow for replacing chips.
+ */
+ private int mLastChipsCount = 0;
+
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before,
+ final int count) {
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count,
+ final int after) {
+ // We don't take mLastChipsCount from here but from the last afterTextChanged() run.
+ // The reason is because at this point, any chip spans to be removed is already removed
+ // from s in the chips text view.
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ final int currentChipsCount = s.getSpans(0, s.length(),
+ DrawableRecipientChip.class).length;
+ if (currentChipsCount != mLastChipsCount) {
+ // When a sanitizing task is running, we don't want to notify any chips count
+ // change, but we do want to track the last chip count.
+ if (mChipsChangeListener != null && mCurrentSanitizeTask == null) {
+ mChipsChangeListener.onContactChipsChanged(mLastChipsCount, currentChipsCount);
+ }
+ mLastChipsCount = currentChipsCount;
+ }
+ }
+ }
+
+ private static final String TEXT_HEIGHT_SAMPLE = "a";
+
+ public ContactRecipientAutoCompleteView(final Context context, final AttributeSet attrs) {
+ super(new ContextThemeWrapper(context, R.style.ColorAccentGrayOverrideStyle), attrs);
+
+ // Get the height of the text, given the currently set font face and size.
+ final Rect textBounds = new Rect(0, 0, 0, 0);
+ final TextPaint paint = getPaint();
+ paint.getTextBounds(TEXT_HEIGHT_SAMPLE, 0, TEXT_HEIGHT_SAMPLE.length(), textBounds);
+ mTextHeight = textBounds.height();
+
+ setTokenizer(new Rfc822Tokenizer());
+ addTextChangedListener(new ContactChipsWatcher());
+ setOnFocusListShrinkRecipients(false);
+
+ setBackground(context.getResources().getDrawable(
+ R.drawable.abc_textfield_search_default_mtrl_alpha));
+ }
+
+ public void setContactChipsListener(final ContactChipsChangeListener listener) {
+ mChipsChangeListener = listener;
+ }
+
+ /**
+ * A tuple of chips which AsyncContactChipSanitizeTask reports as progress to have the
+ * chip actually replaced/removed on the UI thread.
+ */
+ private class ChipReplacementTuple {
+ public final DrawableRecipientChip removedChip;
+ public final RecipientEntry replacedChipEntry;
+
+ public ChipReplacementTuple(final DrawableRecipientChip removedChip,
+ final RecipientEntry replacedChipEntry) {
+ this.removedChip = removedChip;
+ this.replacedChipEntry = replacedChipEntry;
+ }
+ }
+
+ /**
+ * An AsyncTask that cleans up contact chips on every chips commit (i.e. get or create a new
+ * conversation with the given chips).
+ */
+ private class AsyncContactChipSanitizeTask extends
+ AsyncTask<Void, ChipReplacementTuple, Integer> {
+
+ @Override
+ protected Integer doInBackground(final Void... params) {
+ final DrawableRecipientChip[] recips = getText()
+ .getSpans(0, getText().length(), DrawableRecipientChip.class);
+ int invalidChipsRemoved = 0;
+ for (final DrawableRecipientChip recipient : recips) {
+ final RecipientEntry entry = recipient.getEntry();
+ if (entry != null) {
+ if (entry.isValid()) {
+ if (RecipientEntry.isCreatedRecipient(entry.getContactId()) ||
+ ContactRecipientEntryUtils.isSendToDestinationContact(entry)) {
+ // This is a generated/send-to contact chip, try to look it up and
+ // display a chip for the corresponding local contact.
+ final Cursor lookupResult = ContactUtil.lookupDestination(getContext(),
+ entry.getDestination()).performSynchronousQuery();
+ if (lookupResult != null && lookupResult.moveToNext()) {
+ // Found a match, remove the generated entry and replace with
+ // a better local entry.
+ publishProgress(new ChipReplacementTuple(recipient,
+ ContactUtil.createRecipientEntryForPhoneQuery(
+ lookupResult, true)));
+ } else if (PhoneUtils.isValidSmsMmsDestination(
+ entry.getDestination())){
+ // No match was found, but we have a valid destination so let's at
+ // least create an entry that shows an avatar.
+ publishProgress(new ChipReplacementTuple(recipient,
+ ContactRecipientEntryUtils.constructNumberWithAvatarEntry(
+ entry.getDestination())));
+ } else {
+ // Not a valid contact. Remove and show an error.
+ publishProgress(new ChipReplacementTuple(recipient, null));
+ invalidChipsRemoved++;
+ }
+ }
+ } else {
+ publishProgress(new ChipReplacementTuple(recipient, null));
+ invalidChipsRemoved++;
+ }
+ }
+ }
+ return invalidChipsRemoved;
+ }
+
+ @Override
+ protected void onProgressUpdate(final ChipReplacementTuple... values) {
+ for (final ChipReplacementTuple tuple : values) {
+ if (tuple.removedChip != null) {
+ final Editable text = getText();
+ final int chipStart = text.getSpanStart(tuple.removedChip);
+ final int chipEnd = text.getSpanEnd(tuple.removedChip);
+ if (chipStart >= 0 && chipEnd >= 0) {
+ text.delete(chipStart, chipEnd);
+ }
+
+ if (tuple.replacedChipEntry != null) {
+ appendRecipientEntry(tuple.replacedChipEntry);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final Integer invalidChipsRemoved) {
+ mCurrentSanitizeTask = null;
+ if (invalidChipsRemoved > 0) {
+ mChipsChangeListener.onInvalidContactChipsPruned(invalidChipsRemoved);
+ }
+ }
+ }
+
+ /**
+ * We don't use SafeAsyncTask but instead use a single threaded executor to ensure that
+ * all sanitization tasks are serially executed so as not to interfere with each other.
+ */
+ private static final Executor SANITIZE_EXECUTOR = Executors.newSingleThreadExecutor();
+
+ private AsyncContactChipSanitizeTask mCurrentSanitizeTask;
+
+ /**
+ * Whenever the caller wants to start a new conversation with the list of chips we have,
+ * make sure we asynchronously:
+ * 1. Remove invalid chips.
+ * 2. Attempt to resolve unknown contacts to known local contacts.
+ * 3. Convert still unknown chips to chips with generated avatar.
+ *
+ * Note that we don't need to perform this synchronously since we can
+ * resolve any unknown contacts to local contacts when needed.
+ */
+ private void sanitizeContactChips() {
+ if (mCurrentSanitizeTask != null && !mCurrentSanitizeTask.isCancelled()) {
+ mCurrentSanitizeTask.cancel(false);
+ mCurrentSanitizeTask = null;
+ }
+ mCurrentSanitizeTask = new AsyncContactChipSanitizeTask();
+ mCurrentSanitizeTask.executeOnExecutor(SANITIZE_EXECUTOR);
+ }
+
+ /**
+ * Returns a list of ParticipantData from the entered chips in order to create
+ * new conversation.
+ */
+ public ArrayList<ParticipantData> getRecipientParticipantDataForConversationCreation() {
+ final DrawableRecipientChip[] recips = getText()
+ .getSpans(0, getText().length(), DrawableRecipientChip.class);
+ final ArrayList<ParticipantData> contacts =
+ new ArrayList<ParticipantData>(recips.length);
+ for (final DrawableRecipientChip recipient : recips) {
+ final RecipientEntry entry = recipient.getEntry();
+ if (entry != null && entry.isValid() && entry.getDestination() != null &&
+ PhoneUtils.isValidSmsMmsDestination(entry.getDestination())) {
+ contacts.add(ParticipantData.getFromRecipientEntry(recipient.getEntry()));
+ }
+ }
+ sanitizeContactChips();
+ return contacts;
+ }
+
+ /**c
+ * Gets a set of currently selected chips' emails/phone numbers. This will facilitate the
+ * consumer with determining quickly whether a contact is currently selected.
+ */
+ public Set<String> getSelectedDestinations() {
+ Set<String> set = new HashSet<String>();
+ final DrawableRecipientChip[] recips = getText()
+ .getSpans(0, getText().length(), DrawableRecipientChip.class);
+
+ for (final DrawableRecipientChip recipient : recips) {
+ final RecipientEntry entry = recipient.getEntry();
+ if (entry != null && entry.isValid() && entry.getDestination() != null) {
+ set.add(PhoneUtils.getDefault().getCanonicalBySystemLocale(
+ entry.getDestination()));
+ }
+ }
+ return set;
+ }
+
+ @Override
+ public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ mChipsChangeListener.onEntryComplete();
+ }
+ return super.onEditorAction(view, actionId, event);
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java b/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java
new file mode 100644
index 0000000..d69ba64
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java
@@ -0,0 +1,96 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.ex.chips.PhotoManager;
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
+import com.android.messaging.datamodel.media.BindableMediaRequest;
+import com.android.messaging.datamodel.media.ImageResource;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.ThreadUtil;
+
+/**
+ * An implementation of {@link PhotoManager} that hooks up the chips UI's photos with our own
+ * {@link MediaResourceManager} for retrieving and caching contact avatars.
+ */
+public class ContactRecipientPhotoManager implements PhotoManager {
+ private static final String IMAGE_BYTES_REQUEST_STATIC_BINDING_ID = "imagebytes";
+ private final Context mContext;
+ private final int mIconSize;
+ private final ContactListItemView.HostInterface mClivHostInterface;
+
+ public ContactRecipientPhotoManager(final Context context,
+ final ContactListItemView.HostInterface clivHostInterface) {
+ mContext = context;
+ mIconSize = context.getResources().getDimensionPixelSize(
+ R.dimen.compose_message_chip_height) - context.getResources().getDimensionPixelSize(
+ R.dimen.compose_message_chip_padding) * 2;
+ mClivHostInterface = clivHostInterface;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void populatePhotoBytesAsync(final RecipientEntry entry,
+ final PhotoManagerCallback callback) {
+ // Post all media resource request to the main thread.
+ ThreadUtil.getMainThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ ParticipantData.getFromRecipientEntry(entry));
+ final AvatarRequestDescriptor descriptor =
+ new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize);
+ final BindableMediaRequest<ImageResource> req = descriptor.buildAsyncMediaRequest(
+ mContext,
+ new MediaResourceLoadListener<ImageResource>() {
+ @Override
+ public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
+ final ImageResource resource, final boolean isCached) {
+ entry.setPhotoBytes(resource.getBytes());
+ callback.onPhotoBytesAsynchronouslyPopulated();
+ }
+
+ @Override
+ public void onMediaResourceLoadError(final MediaRequest<ImageResource> request,
+ final Exception exception) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Photo bytes loading failed due to " +
+ exception + " request key=" + request.getKey());
+
+ // Fall back to the default avatar image.
+ callback.onPhotoBytesAsyncLoadFailed();
+ }});
+
+ // Statically bind the request since it's not bound to any specific piece of UI.
+ req.bind(IMAGE_BYTES_REQUEST_STATIC_BINDING_ID);
+
+ Factory.get().getMediaResourceManager().requestMediaResourceAsync(req);
+ }
+ });
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
new file mode 100644
index 0000000..1d5abf3
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
@@ -0,0 +1,169 @@
+/*
+ * 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.contact;
+
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.widget.SectionIndexer;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Indexes contact alphabetical sections so we can report to the fast scrolling list view
+ * where we are in the list when the user scrolls through the contact list, allowing us to show
+ * alphabetical indicators for the fast scroller as well as list section headers.
+ */
+public class ContactSectionIndexer implements SectionIndexer {
+ private String[] mSections;
+ private ArrayList<Integer> mSectionStartingPositions;
+ private static final String BLANK_HEADER_STRING = " ";
+
+ public ContactSectionIndexer(final Cursor contactsCursor) {
+ buildIndexer(contactsCursor);
+ }
+
+ @Override
+ public Object[] getSections() {
+ return mSections;
+ }
+
+ @Override
+ public int getPositionForSection(final int sectionIndex) {
+ if (mSectionStartingPositions.isEmpty()) {
+ return 0;
+ }
+ // Clamp to the bounds of the section position array per Android API doc.
+ return mSectionStartingPositions.get(
+ Math.max(Math.min(sectionIndex, mSectionStartingPositions.size() - 1), 0));
+ }
+
+ @Override
+ public int getSectionForPosition(final int position) {
+ if (mSectionStartingPositions.isEmpty()) {
+ return 0;
+ }
+
+ // Perform a binary search on the starting positions of the sections to the find the
+ // section for the position.
+ int left = 0;
+ int right = mSectionStartingPositions.size() - 1;
+
+ // According to getSectionForPosition()'s doc, we should always clamp the value when the
+ // position is out of bound.
+ if (position <= mSectionStartingPositions.get(left)) {
+ return left;
+ } else if (position >= mSectionStartingPositions.get(right)) {
+ return right;
+ }
+
+ while (left <= right) {
+ final int mid = (left + right) / 2;
+ final int startingPos = mSectionStartingPositions.get(mid);
+ final int nextStartingPos = mSectionStartingPositions.get(mid + 1);
+ if (position >= startingPos && position < nextStartingPos) {
+ return mid;
+ } else if (position < startingPos) {
+ right = mid - 1;
+ } else if (position >= nextStartingPos) {
+ left = mid + 1;
+ }
+ }
+ Assert.fail("Invalid section indexer state: couldn't find section for pos " + position);
+ return -1;
+ }
+
+ private boolean buildIndexerFromCursorExtras(final Cursor cursor) {
+ if (cursor == null) {
+ return false;
+ }
+ final Bundle cursorExtras = cursor.getExtras();
+ if (cursorExtras == null) {
+ return false;
+ }
+ final String[] sections = cursorExtras.getStringArray(
+ Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ final int[] counts = cursorExtras.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ if (sections == null || counts == null) {
+ return false;
+ }
+
+ if (sections.length != counts.length) {
+ return false;
+ }
+
+ this.mSections = sections;
+ mSectionStartingPositions = new ArrayList<Integer>(counts.length);
+ int position = 0;
+ for (int i = 0; i < counts.length; i++) {
+ if (TextUtils.isEmpty(mSections[i])) {
+ mSections[i] = BLANK_HEADER_STRING;
+ } else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
+ mSections[i] = mSections[i].trim();
+ }
+
+ mSectionStartingPositions.add(position);
+ position += counts[i];
+ }
+ return true;
+ }
+
+ private void buildIndexerFromDisplayNames(final Cursor cursor) {
+ // Loop through the contact cursor and get the starting position for each first character.
+ // The result is stored into two arrays, one for the section header (i.e. the first
+ // character), and one for the starting position, which is guaranteed to be sorted in
+ // ascending order.
+ final ArrayList<String> sections = new ArrayList<String>();
+ mSectionStartingPositions = new ArrayList<Integer>();
+ if (cursor != null) {
+ cursor.moveToPosition(-1);
+ int currentPosition = 0;
+ while (cursor.moveToNext()) {
+ // The sort key is typically the contact's display name, so for example, a contact
+ // named "Bob" will go into section "B". The Contacts provider generally uses a
+ // a slightly more sophisticated heuristic, but as a fallback this is good enough.
+ final String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY);
+ final String section = TextUtils.isEmpty(sortKey) ? BLANK_HEADER_STRING :
+ sortKey.substring(0, 1).toUpperCase();
+
+ final int lastIndex = sections.size() - 1;
+ final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null;
+ if (!TextUtils.equals(currentSection, section)) {
+ sections.add(section);
+ mSectionStartingPositions.add(currentPosition);
+ }
+ currentPosition++;
+ }
+ }
+ mSections = new String[sections.size()];
+ sections.toArray(mSections);
+ }
+
+ private void buildIndexer(final Cursor cursor) {
+ // First check if we get indexer label extras from the contact provider; if not, fall back
+ // to building from display names.
+ if (!buildIndexerFromCursorExtras(cursor)) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "contact provider didn't provide contact label " +
+ "information, fall back to using display name!");
+ buildIndexerFromDisplayNames(cursor);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java b/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java
new file mode 100644
index 0000000..1f3c795
--- /dev/null
+++ b/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java
@@ -0,0 +1,63 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.CustomHeaderPagerListViewHolder;
+import com.android.messaging.ui.contact.ContactListItemView.HostInterface;
+
+/**
+ * Holds the frequent contacts view for the contact picker's view pager.
+ */
+public class FrequentContactsListViewHolder extends CustomHeaderPagerListViewHolder {
+ public FrequentContactsListViewHolder(final Context context,
+ final HostInterface clivHostInterface) {
+ super(context, new ContactListAdapter(context, null, clivHostInterface,
+ false /* needAlphabetHeader */));
+ }
+
+ @Override
+ protected int getLayoutResId() {
+ return R.layout.frequent_contacts_list_view;
+ }
+
+ @Override
+ protected int getPageTitleResId() {
+ return R.string.contact_picker_frequents_tab_title;
+ }
+
+ @Override
+ protected int getEmptyViewResId() {
+ return R.id.empty_view;
+ }
+
+ @Override
+ protected int getListViewResId() {
+ return R.id.frequent_contacts_list;
+ }
+
+ @Override
+ protected int getEmptyViewTitleResId() {
+ return R.string.contact_list_empty_text;
+ }
+
+ @Override
+ protected int getEmptyViewImageResId() {
+ return R.drawable.ic_oobe_freq_list;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
new file mode 100644
index 0000000..17f8f74
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
@@ -0,0 +1,962 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.text.Editable;
+import android.text.Html;
+import android.text.InputFilter;
+import android.text.InputFilter.LengthFilter;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.binding.ImmutableBindingRef;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask;
+import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.ui.AttachmentPreview;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.PlainTextEditText;
+import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This view contains the UI required to generate and send messages.
+ */
+public class ComposeMessageView extends LinearLayout
+ implements TextView.OnEditorActionListener, DraftMessageDataListener, TextWatcher,
+ ConversationInputSink {
+
+ public interface IComposeMessageViewHost extends
+ DraftMessageData.DraftMessageSubscriptionDataProvider {
+ void sendMessage(MessageData message);
+ void onComposeEditTextFocused();
+ void onAttachmentsCleared();
+ void onAttachmentsChanged(final boolean haveAttachments);
+ void displayPhoto(Uri photoUri, Rect imageBounds, boolean isDraft);
+ void promptForSelfPhoneNumber();
+ boolean isReadyForAction();
+ void warnOfMissingActionConditions(final boolean sending,
+ final Runnable commandToRunAfterActionConditionResolved);
+ void warnOfExceedingMessageLimit(final boolean showAttachmentChooser,
+ boolean tooManyVideos);
+ void notifyOfAttachmentLoadFailed();
+ void showAttachmentChooser();
+ boolean shouldShowSubjectEditor();
+ boolean shouldHideAttachmentsWhenSimSelectorShown();
+ Uri getSelfSendButtonIconUri();
+ int overrideCounterColor();
+ int getAttachmentsClearedFlags();
+ }
+
+ public static final int CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN = 10;
+
+ // There is no draft and there is no need for the SIM selector
+ private static final int SEND_WIDGET_MODE_SELF_AVATAR = 1;
+ // There is no draft but we need to show the SIM selector
+ private static final int SEND_WIDGET_MODE_SIM_SELECTOR = 2;
+ // There is a draft
+ private static final int SEND_WIDGET_MODE_SEND_BUTTON = 3;
+
+ private PlainTextEditText mComposeEditText;
+ private PlainTextEditText mComposeSubjectText;
+ private TextView mCharCounter;
+ private TextView mMmsIndicator;
+ private SimIconView mSelfSendIcon;
+ private ImageButton mSendButton;
+ private View mSubjectView;
+ private ImageButton mDeleteSubjectButton;
+ private AttachmentPreview mAttachmentPreview;
+ private ImageButton mAttachMediaButton;
+
+ private final Binding<DraftMessageData> mBinding;
+ private IComposeMessageViewHost mHost;
+ private final Context mOriginalContext;
+ private int mSendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
+
+ // Shared data model object binding from the conversation.
+ private ImmutableBindingRef<ConversationData> mConversationDataModel;
+
+ // Centrally manages all the mutual exclusive UI components accepting user input, i.e.
+ // media picker, IME keyboard and SIM selector.
+ private ConversationInputManager mInputManager;
+
+ private final ConversationDataListener mDataListener = new SimpleConversationDataListener() {
+ @Override
+ public void onConversationMetadataUpdated(ConversationData data) {
+ mConversationDataModel.ensureBound(data);
+ updateVisualsOnDraftChanged();
+ }
+
+ @Override
+ public void onConversationParticipantDataLoaded(ConversationData data) {
+ mConversationDataModel.ensureBound(data);
+ updateVisualsOnDraftChanged();
+ }
+
+ @Override
+ public void onSubscriptionListDataLoaded(ConversationData data) {
+ mConversationDataModel.ensureBound(data);
+ updateOnSelfSubscriptionChange();
+ updateVisualsOnDraftChanged();
+ }
+ };
+
+ public ComposeMessageView(final Context context, final AttributeSet attrs) {
+ super(new ContextThemeWrapper(context, R.style.ColorAccentBlueOverrideStyle), attrs);
+ mOriginalContext = context;
+ mBinding = BindingBase.createBinding(this);
+ }
+
+ /**
+ * Host calls this to bind view to DraftMessageData object
+ */
+ public void bind(final DraftMessageData data, final IComposeMessageViewHost host) {
+ mHost = host;
+ mBinding.bind(data);
+ data.addListener(this);
+ data.setSubscriptionDataProvider(host);
+
+ final int counterColor = mHost.overrideCounterColor();
+ if (counterColor != -1) {
+ mCharCounter.setTextColor(counterColor);
+ }
+ }
+
+ /**
+ * Host calls this to unbind view
+ */
+ public void unbind() {
+ mBinding.unbind();
+ mHost = null;
+ mInputManager.onDetach();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mComposeEditText = (PlainTextEditText) findViewById(
+ R.id.compose_message_text);
+ mComposeEditText.setOnEditorActionListener(this);
+ mComposeEditText.addTextChangedListener(this);
+ mComposeEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(final View v, final boolean hasFocus) {
+ if (v == mComposeEditText && hasFocus) {
+ mHost.onComposeEditTextFocused();
+ }
+ }
+ });
+ mComposeEditText.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
+ hideSimSelector();
+ }
+ }
+ });
+
+ // onFinishInflate() is called before self is loaded from db. We set the default text
+ // limit here, and apply the real limit later in updateOnSelfSubscriptionChange().
+ mComposeEditText.setFilters(new InputFilter[] {
+ new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
+ .getMaxTextLimit()) });
+
+ mSelfSendIcon = (SimIconView) findViewById(R.id.self_send_icon);
+ mSelfSendIcon.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ boolean shown = mInputManager.toggleSimSelector(true /* animate */,
+ getSelfSubscriptionListEntry());
+ hideAttachmentsWhenShowingSims(shown);
+ }
+ });
+ mSelfSendIcon.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View v) {
+ if (mHost.shouldShowSubjectEditor()) {
+ showSubjectEditor();
+ } else {
+ boolean shown = mInputManager.toggleSimSelector(true /* animate */,
+ getSelfSubscriptionListEntry());
+ hideAttachmentsWhenShowingSims(shown);
+ }
+ return true;
+ }
+ });
+
+ mComposeSubjectText = (PlainTextEditText) findViewById(
+ R.id.compose_subject_text);
+ // We need the listener to change the avatar to the send button when the user starts
+ // typing a subject without a message.
+ mComposeSubjectText.addTextChangedListener(this);
+ // onFinishInflate() is called before self is loaded from db. We set the default text
+ // limit here, and apply the real limit later in updateOnSelfSubscriptionChange().
+ mComposeSubjectText.setFilters(new InputFilter[] {
+ new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
+ .getMaxSubjectLength())});
+
+ mDeleteSubjectButton = (ImageButton) findViewById(R.id.delete_subject_button);
+ mDeleteSubjectButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View clickView) {
+ hideSubjectEditor();
+ mComposeSubjectText.setText(null);
+ mBinding.getData().setMessageSubject(null);
+ }
+ });
+
+ mSubjectView = findViewById(R.id.subject_view);
+
+ mSendButton = (ImageButton) findViewById(R.id.send_message_button);
+ mSendButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View clickView) {
+ sendMessageInternal(true /* checkMessageSize */);
+ }
+ });
+ mSendButton.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View arg0) {
+ boolean shown = mInputManager.toggleSimSelector(true /* animate */,
+ getSelfSubscriptionListEntry());
+ hideAttachmentsWhenShowingSims(shown);
+ if (mHost.shouldShowSubjectEditor()) {
+ showSubjectEditor();
+ }
+ return true;
+ }
+ });
+ mSendButton.setAccessibilityDelegate(new AccessibilityDelegate() {
+ @Override
+ public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(host, event);
+ // When the send button is long clicked, we want TalkBack to announce the real
+ // action (select SIM or edit subject), as opposed to "long press send button."
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
+ event.getText().clear();
+ event.getText().add(getResources()
+ .getText(shouldShowSimSelector(mConversationDataModel.getData()) ?
+ R.string.send_button_long_click_description_with_sim_selector :
+ R.string.send_button_long_click_description_no_sim_selector));
+ // Make this an announcement so TalkBack will read our custom message.
+ event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ }
+ }
+ });
+
+ mAttachMediaButton =
+ (ImageButton) findViewById(R.id.attach_media_button);
+ mAttachMediaButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View clickView) {
+ // Showing the media picker is treated as starting to compose the message.
+ mInputManager.showHideMediaPicker(true /* show */, true /* animate */);
+ }
+ });
+
+ mAttachmentPreview = (AttachmentPreview) findViewById(R.id.attachment_draft_view);
+ mAttachmentPreview.setComposeMessageView(this);
+
+ mCharCounter = (TextView) findViewById(R.id.char_counter);
+ mMmsIndicator = (TextView) findViewById(R.id.mms_indicator);
+ }
+
+ private void hideAttachmentsWhenShowingSims(final boolean simPickerVisible) {
+ if (!mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
+ return;
+ }
+ final boolean haveAttachments = mBinding.getData().hasAttachments();
+ if (simPickerVisible && haveAttachments) {
+ mHost.onAttachmentsChanged(false);
+ mAttachmentPreview.hideAttachmentPreview();
+ } else {
+ mHost.onAttachmentsChanged(haveAttachments);
+ mAttachmentPreview.onAttachmentsChanged(mBinding.getData());
+ }
+ }
+
+ public void setInputManager(final ConversationInputManager inputManager) {
+ mInputManager = inputManager;
+ }
+
+ public void setConversationDataModel(final ImmutableBindingRef<ConversationData> refDataModel) {
+ mConversationDataModel = refDataModel;
+ mConversationDataModel.getData().addConversationDataListener(mDataListener);
+ }
+
+ ImmutableBindingRef<DraftMessageData> getDraftDataModel() {
+ return BindingBase.createBindingReference(mBinding);
+ }
+
+ // returns true if it actually shows the subject editor and false if already showing
+ private boolean showSubjectEditor() {
+ // show the subject editor
+ if (mSubjectView.getVisibility() == View.GONE) {
+ mSubjectView.setVisibility(View.VISIBLE);
+ mSubjectView.requestFocus();
+ return true;
+ }
+ return false;
+ }
+
+ private void hideSubjectEditor() {
+ mSubjectView.setVisibility(View.GONE);
+ mComposeEditText.requestFocus();
+ }
+
+ /**
+ * {@inheritDoc} from TextView.OnEditorActionListener
+ */
+ @Override // TextView.OnEditorActionListener.onEditorAction
+ public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEND) {
+ sendMessageInternal(true /* checkMessageSize */);
+ return true;
+ }
+ return false;
+ }
+
+ private void sendMessageInternal(final boolean checkMessageSize) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "UI initiated message sending in conversation " +
+ mBinding.getData().getConversationId());
+ if (mBinding.getData().isCheckingDraft()) {
+ // Don't send message if we are currently checking draft for sending.
+ LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: still checking draft");
+ return;
+ }
+ // Check the host for pre-conditions about any action.
+ if (mHost.isReadyForAction()) {
+ mInputManager.showHideSimSelector(false /* show */, true /* animate */);
+ final String messageToSend = mComposeEditText.getText().toString();
+ mBinding.getData().setMessageText(messageToSend);
+ final String subject = mComposeSubjectText.getText().toString();
+ mBinding.getData().setMessageSubject(subject);
+ // Asynchronously check the draft against various requirements before sending.
+ mBinding.getData().checkDraftForAction(checkMessageSize,
+ mHost.getConversationSelfSubId(), new CheckDraftTaskCallback() {
+ @Override
+ public void onDraftChecked(DraftMessageData data, int result) {
+ mBinding.ensureBound(data);
+ switch (result) {
+ case CheckDraftForSendTask.RESULT_PASSED:
+ // Continue sending after check succeeded.
+ final MessageData message = mBinding.getData()
+ .prepareMessageForSending(mBinding);
+ if (message != null && message.hasContent()) {
+ playSentSound();
+ mHost.sendMessage(message);
+ hideSubjectEditor();
+ if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
+ AccessibilityUtil.announceForAccessibilityCompat(
+ ComposeMessageView.this, null,
+ R.string.sending_message);
+ }
+ }
+ break;
+
+ case CheckDraftForSendTask.RESULT_HAS_PENDING_ATTACHMENTS:
+ // Cannot send while there's still attachment(s) being loaded.
+ UiUtils.showToastAtBottom(
+ R.string.cant_send_message_while_loading_attachments);
+ break;
+
+ case CheckDraftForSendTask.RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS:
+ mHost.promptForSelfPhoneNumber();
+ break;
+
+ case CheckDraftForSendTask.RESULT_MESSAGE_OVER_LIMIT:
+ Assert.isTrue(checkMessageSize);
+ mHost.warnOfExceedingMessageLimit(
+ true /*sending*/, false /* tooManyVideos */);
+ break;
+
+ case CheckDraftForSendTask.RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED:
+ Assert.isTrue(checkMessageSize);
+ mHost.warnOfExceedingMessageLimit(
+ true /*sending*/, true /* tooManyVideos */);
+ break;
+
+ case CheckDraftForSendTask.RESULT_SIM_NOT_READY:
+ // Cannot send if there is no active subscription
+ UiUtils.showToastAtBottom(
+ R.string.cant_send_message_without_active_subscription);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }, mBinding);
+ } else {
+ mHost.warnOfMissingActionConditions(true /*sending*/,
+ new Runnable() {
+ @Override
+ public void run() {
+ sendMessageInternal(checkMessageSize);
+ }
+
+ });
+ }
+ }
+
+ public static void playSentSound() {
+ // Check if this setting is enabled before playing
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final String prefKey = context.getString(R.string.send_sound_pref_key);
+ final boolean defaultValue = context.getResources().getBoolean(
+ R.bool.send_sound_pref_default);
+ if (!prefs.getBoolean(prefKey, defaultValue)) {
+ return;
+ }
+ MediaUtil.get().playSound(context, R.raw.message_sent, null /* completionListener */);
+ }
+
+ /**
+ * {@inheritDoc} from DraftMessageDataListener
+ */
+ @Override // From DraftMessageDataListener
+ public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
+ // As this is called asynchronously when message read check bound before updating text
+ mBinding.ensureBound(data);
+
+ // We have to cache the values of the DraftMessageData because when we set
+ // mComposeEditText, its onTextChanged calls updateVisualsOnDraftChanged,
+ // which immediately reloads the text from the subject and message fields and replaces
+ // what's in the DraftMessageData.
+
+ final String subject = data.getMessageSubject();
+ final String message = data.getMessageText();
+
+ if ((changeFlags & DraftMessageData.MESSAGE_SUBJECT_CHANGED) ==
+ DraftMessageData.MESSAGE_SUBJECT_CHANGED) {
+ mComposeSubjectText.setText(subject);
+
+ // Set the cursor selection to the end since setText resets it to the start
+ mComposeSubjectText.setSelection(mComposeSubjectText.getText().length());
+ }
+
+ if ((changeFlags & DraftMessageData.MESSAGE_TEXT_CHANGED) ==
+ DraftMessageData.MESSAGE_TEXT_CHANGED) {
+ mComposeEditText.setText(message);
+
+ // Set the cursor selection to the end since setText resets it to the start
+ mComposeEditText.setSelection(mComposeEditText.getText().length());
+ }
+
+ if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
+ DraftMessageData.ATTACHMENTS_CHANGED) {
+ final boolean haveAttachments = mAttachmentPreview.onAttachmentsChanged(data);
+ mHost.onAttachmentsChanged(haveAttachments);
+ }
+
+ if ((changeFlags & DraftMessageData.SELF_CHANGED) == DraftMessageData.SELF_CHANGED) {
+ updateOnSelfSubscriptionChange();
+ }
+ updateVisualsOnDraftChanged();
+ }
+
+ @Override // From DraftMessageDataListener
+ public void onDraftAttachmentLimitReached(final DraftMessageData data) {
+ mBinding.ensureBound(data);
+ mHost.warnOfExceedingMessageLimit(false /* sending */, false /* tooManyVideos */);
+ }
+
+ private void updateOnSelfSubscriptionChange() {
+ // Refresh the length filters according to the selected self's MmsConfig.
+ mComposeEditText.setFilters(new InputFilter[] {
+ new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId())
+ .getMaxTextLimit()) });
+ mComposeSubjectText.setFilters(new InputFilter[] {
+ new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId())
+ .getMaxSubjectLength())});
+ }
+
+ @Override
+ public void onMediaItemsSelected(final Collection<MessagePartData> items) {
+ mBinding.getData().addAttachments(items);
+ announceMediaItemState(true /*isSelected*/);
+ }
+
+ @Override
+ public void onMediaItemsUnselected(final MessagePartData item) {
+ mBinding.getData().removeAttachment(item);
+ announceMediaItemState(false /*isSelected*/);
+ }
+
+ @Override
+ public void onPendingAttachmentAdded(final PendingAttachmentData pendingItem) {
+ mBinding.getData().addPendingAttachment(pendingItem, mBinding);
+ resumeComposeMessage();
+ }
+
+ private void announceMediaItemState(final boolean isSelected) {
+ final Resources res = getContext().getResources();
+ final String announcement = isSelected ? res.getString(
+ R.string.mediapicker_gallery_item_selected_content_description) :
+ res.getString(R.string.mediapicker_gallery_item_unselected_content_description);
+ AccessibilityUtil.announceForAccessibilityCompat(
+ this, null, announcement);
+ }
+
+ private void announceAttachmentState() {
+ if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
+ int attachmentCount = mBinding.getData().getReadOnlyAttachments().size()
+ + mBinding.getData().getReadOnlyPendingAttachments().size();
+ final String announcement = getContext().getResources().getQuantityString(
+ R.plurals.attachment_changed_accessibility_announcement,
+ attachmentCount, attachmentCount);
+ AccessibilityUtil.announceForAccessibilityCompat(
+ this, null, announcement);
+ }
+ }
+
+ @Override
+ public void resumeComposeMessage() {
+ mComposeEditText.requestFocus();
+ mInputManager.showHideImeKeyboard(true, true);
+ announceAttachmentState();
+ }
+
+ public void clearAttachments() {
+ mBinding.getData().clearAttachments(mHost.getAttachmentsClearedFlags());
+ mHost.onAttachmentsCleared();
+ }
+
+ public void requestDraftMessage(boolean clearLocalDraft) {
+ mBinding.getData().loadFromStorage(mBinding, null, clearLocalDraft);
+ }
+
+ public void setDraftMessage(final MessageData message) {
+ mBinding.getData().loadFromStorage(mBinding, message, false);
+ }
+
+ public void writeDraftMessage() {
+ final String messageText = mComposeEditText.getText().toString();
+ mBinding.getData().setMessageText(messageText);
+
+ final String subject = mComposeSubjectText.getText().toString();
+ mBinding.getData().setMessageSubject(subject);
+
+ mBinding.getData().saveToStorage(mBinding);
+ }
+
+ private void updateConversationSelfId(final String selfId, final boolean notify) {
+ mBinding.getData().setSelfId(selfId, notify);
+ }
+
+ private Uri getSelfSendButtonIconUri() {
+ final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
+ if (overridenSelfUri != null) {
+ return overridenSelfUri;
+ }
+ final SubscriptionListEntry subscriptionListEntry = getSelfSubscriptionListEntry();
+
+ if (subscriptionListEntry != null) {
+ return subscriptionListEntry.selectedIconUri;
+ }
+
+ // Fall back to default self-avatar in the base case.
+ final ParticipantData self = mConversationDataModel.getData().getDefaultSelfParticipant();
+ return self == null ? null : AvatarUriUtil.createAvatarUri(self);
+ }
+
+ private SubscriptionListEntry getSelfSubscriptionListEntry() {
+ return mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
+ mBinding.getData().getSelfId(), false /* excludeDefault */);
+ }
+
+ private boolean isDataLoadedForMessageSend() {
+ // Check data loading prerequisites for sending a message.
+ return mConversationDataModel != null && mConversationDataModel.isBound() &&
+ mConversationDataModel.getData().getParticipantsLoaded();
+ }
+
+ private void updateVisualsOnDraftChanged() {
+ final String messageText = mComposeEditText.getText().toString();
+ final DraftMessageData draftMessageData = mBinding.getData();
+ draftMessageData.setMessageText(messageText);
+
+ final String subject = mComposeSubjectText.getText().toString();
+ draftMessageData.setMessageSubject(subject);
+ if (!TextUtils.isEmpty(subject)) {
+ mSubjectView.setVisibility(View.VISIBLE);
+ }
+
+ final boolean hasMessageText = (TextUtils.getTrimmedLength(messageText) > 0);
+ final boolean hasSubject = (TextUtils.getTrimmedLength(subject) > 0);
+ final boolean hasWorkingDraft = hasMessageText || hasSubject ||
+ mBinding.getData().hasAttachments();
+
+ // Update the SMS text counter.
+ final int messageCount = draftMessageData.getNumMessagesToBeSent();
+ final int codePointsRemaining = draftMessageData.getCodePointsRemainingInCurrentMessage();
+ // Show the counter only if:
+ // - We are not in MMS mode
+ // - We are going to send more than one message OR we are getting close
+ boolean showCounter = false;
+ if (!draftMessageData.getIsMms() && (messageCount > 1 ||
+ codePointsRemaining <= CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN)) {
+ showCounter = true;
+ }
+
+ if (showCounter) {
+ // Update the remaining characters and number of messages required.
+ final String counterText = messageCount > 1 ? codePointsRemaining + " / " +
+ messageCount : String.valueOf(codePointsRemaining);
+ mCharCounter.setText(counterText);
+ mCharCounter.setVisibility(View.VISIBLE);
+ } else {
+ mCharCounter.setVisibility(View.INVISIBLE);
+ }
+
+ // Update the send message button. Self icon uri might be null if self participant data
+ // and/or conversation metadata hasn't been loaded by the host.
+ final Uri selfSendButtonUri = getSelfSendButtonIconUri();
+ int sendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
+ if (selfSendButtonUri != null) {
+ if (hasWorkingDraft && isDataLoadedForMessageSend()) {
+ UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
+ if (isOverriddenAvatarAGroup()) {
+ // If the host has overriden the avatar to show a group avatar where the
+ // send button sits, we have to hide the group avatar because it can be larger
+ // than the send button and pieces of the avatar will stick out from behind
+ // the send button.
+ UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null);
+ }
+ mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE);
+ sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON;
+ } else {
+ mSelfSendIcon.setImageResourceUri(selfSendButtonUri);
+ if (isOverriddenAvatarAGroup()) {
+ UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, VISIBLE, null);
+ }
+ UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null);
+ mMmsIndicator.setVisibility(INVISIBLE);
+ if (shouldShowSimSelector(mConversationDataModel.getData())) {
+ sendWidgetMode = SEND_WIDGET_MODE_SIM_SELECTOR;
+ }
+ }
+ } else {
+ mSelfSendIcon.setImageResourceUri(null);
+ }
+
+ if (mSendWidgetMode != sendWidgetMode || sendWidgetMode == SEND_WIDGET_MODE_SIM_SELECTOR) {
+ setSendButtonAccessibility(sendWidgetMode);
+ mSendWidgetMode = sendWidgetMode;
+ }
+
+ // Update the text hint on the message box depending on the attachment type.
+ final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
+ final int attachmentCount = attachments.size();
+ if (attachmentCount == 0) {
+ final SubscriptionListEntry subscriptionListEntry =
+ mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
+ mBinding.getData().getSelfId(), false /* excludeDefault */);
+ if (subscriptionListEntry == null) {
+ mComposeEditText.setHint(R.string.compose_message_view_hint_text);
+ } else {
+ mComposeEditText.setHint(Html.fromHtml(getResources().getString(
+ R.string.compose_message_view_hint_text_multi_sim,
+ subscriptionListEntry.displayName)));
+ }
+ } else {
+ int type = -1;
+ for (final MessagePartData attachment : attachments) {
+ int newType;
+ if (attachment.isImage()) {
+ newType = ContentType.TYPE_IMAGE;
+ } else if (attachment.isAudio()) {
+ newType = ContentType.TYPE_AUDIO;
+ } else if (attachment.isVideo()) {
+ newType = ContentType.TYPE_VIDEO;
+ } else if (attachment.isVCard()) {
+ newType = ContentType.TYPE_VCARD;
+ } else {
+ newType = ContentType.TYPE_OTHER;
+ }
+
+ if (type == -1) {
+ type = newType;
+ } else if (type != newType || type == ContentType.TYPE_OTHER) {
+ type = ContentType.TYPE_OTHER;
+ break;
+ }
+ }
+
+ switch (type) {
+ case ContentType.TYPE_IMAGE:
+ mComposeEditText.setHint(getResources().getQuantityString(
+ R.plurals.compose_message_view_hint_text_photo, attachmentCount));
+ break;
+
+ case ContentType.TYPE_AUDIO:
+ mComposeEditText.setHint(getResources().getQuantityString(
+ R.plurals.compose_message_view_hint_text_audio, attachmentCount));
+ break;
+
+ case ContentType.TYPE_VIDEO:
+ mComposeEditText.setHint(getResources().getQuantityString(
+ R.plurals.compose_message_view_hint_text_video, attachmentCount));
+ break;
+
+ case ContentType.TYPE_VCARD:
+ mComposeEditText.setHint(getResources().getQuantityString(
+ R.plurals.compose_message_view_hint_text_vcard, attachmentCount));
+ break;
+
+ case ContentType.TYPE_OTHER:
+ mComposeEditText.setHint(getResources().getQuantityString(
+ R.plurals.compose_message_view_hint_text_attachments, attachmentCount));
+ break;
+
+ default:
+ Assert.fail("Unsupported attachment type!");
+ break;
+ }
+ }
+ }
+
+ private void setSendButtonAccessibility(final int sendWidgetMode) {
+ switch (sendWidgetMode) {
+ case SEND_WIDGET_MODE_SELF_AVATAR:
+ // No send button and no SIM selector; the self send button is no longer
+ // important for accessibility.
+ mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mSelfSendIcon.setContentDescription(null);
+ mSendButton.setVisibility(View.GONE);
+ setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SELF_AVATAR);
+ break;
+
+ case SEND_WIDGET_MODE_SIM_SELECTOR:
+ mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mSelfSendIcon.setContentDescription(getSimContentDescription());
+ setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SIM_SELECTOR);
+ break;
+
+ case SEND_WIDGET_MODE_SEND_BUTTON:
+ mMmsIndicator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mMmsIndicator.setContentDescription(null);
+ setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SEND_BUTTON);
+ break;
+ }
+ }
+
+ private String getSimContentDescription() {
+ final SubscriptionListEntry sub = getSelfSubscriptionListEntry();
+ if (sub != null) {
+ return getResources().getString(
+ R.string.sim_selector_button_content_description_with_selection,
+ sub.displayName);
+ } else {
+ return getResources().getString(
+ R.string.sim_selector_button_content_description);
+ }
+ }
+
+ // Set accessibility traversal order of the components in the send widget.
+ private void setSendWidgetAccessibilityTraversalOrder(final int mode) {
+ if (OsUtil.isAtLeastL_MR1()) {
+ mAttachMediaButton.setAccessibilityTraversalBefore(R.id.compose_message_text);
+ switch (mode) {
+ case SEND_WIDGET_MODE_SIM_SELECTOR:
+ mComposeEditText.setAccessibilityTraversalBefore(R.id.self_send_icon);
+ break;
+ case SEND_WIDGET_MODE_SEND_BUTTON:
+ mComposeEditText.setAccessibilityTraversalBefore(R.id.send_message_button);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void afterTextChanged(final Editable editable) {
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count,
+ final int after) {
+ if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
+ hideSimSelector();
+ }
+ }
+
+ private void hideSimSelector() {
+ if (mInputManager.showHideSimSelector(false /* show */, true /* animate */)) {
+ // Now that the sim selector has been hidden, reshow the attachments if they
+ // have been hidden.
+ hideAttachmentsWhenShowingSims(false /*simPickerVisible*/);
+ }
+ }
+
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before,
+ final int count) {
+ final BugleActionBarActivity activity = (mOriginalContext instanceof BugleActionBarActivity)
+ ? (BugleActionBarActivity) mOriginalContext : null;
+ if (activity != null && activity.getIsDestroyed()) {
+ LogUtil.v(LogUtil.BUGLE_TAG, "got onTextChanged after onDestroy");
+
+ // if we get onTextChanged after the activity is destroyed then, ah, wtf
+ // b/18176615
+ // This appears to have occurred as the result of orientation change.
+ return;
+ }
+
+ mBinding.ensureBound();
+ updateVisualsOnDraftChanged();
+ }
+
+ @Override
+ public PlainTextEditText getComposeEditText() {
+ return mComposeEditText;
+ }
+
+ public void displayPhoto(final Uri photoUri, final Rect imageBounds) {
+ mHost.displayPhoto(photoUri, imageBounds, true /* isDraft */);
+ }
+
+ public void updateConversationSelfIdOnExternalChange(final String selfId) {
+ updateConversationSelfId(selfId, true /* notify */);
+ }
+
+ /**
+ * The selfId of the conversation. As soon as the DraftMessageData successfully loads (i.e.
+ * getSelfId() is non-null), the selfId in DraftMessageData is treated as the sole source
+ * of truth for conversation self id since it reflects any pending self id change the user
+ * makes in the UI.
+ */
+ public String getConversationSelfId() {
+ return mBinding.getData().getSelfId();
+ }
+
+ public void selectSim(SubscriptionListEntry subscriptionData) {
+ final String oldSelfId = getConversationSelfId();
+ final String newSelfId = subscriptionData.selfParticipantId;
+ Assert.notNull(newSelfId);
+ // Don't attempt to change self if self hasn't been loaded, or if self hasn't changed.
+ if (oldSelfId == null || TextUtils.equals(oldSelfId, newSelfId)) {
+ return;
+ }
+ updateConversationSelfId(newSelfId, true /* notify */);
+ }
+
+ public void hideAllComposeInputs(final boolean animate) {
+ mInputManager.hideAllInputs(animate);
+ }
+
+ public void saveInputState(final Bundle outState) {
+ mInputManager.onSaveInputState(outState);
+ }
+
+ public void resetMediaPickerState() {
+ mInputManager.resetMediaPickerState();
+ }
+
+ public boolean onBackPressed() {
+ return mInputManager.onBackPressed();
+ }
+
+ public boolean onNavigationUpPressed() {
+ return mInputManager.onNavigationUpPressed();
+ }
+
+ public boolean updateActionBar(final ActionBar actionBar) {
+ return mInputManager != null ? mInputManager.updateActionBar(actionBar) : false;
+ }
+
+ public static boolean shouldShowSimSelector(final ConversationData convData) {
+ return OsUtil.isAtLeastL_MR1() &&
+ convData.getSelfParticipantsCountExcludingDefault(true /* activeOnly */) > 1;
+ }
+
+ public void sendMessageIgnoreMessageSizeLimit() {
+ sendMessageInternal(false /* checkMessageSize */);
+ }
+
+ public void onAttachmentPreviewLongClicked() {
+ mHost.showAttachmentChooser();
+ }
+
+ @Override
+ public void onDraftAttachmentLoadFailed() {
+ mHost.notifyOfAttachmentLoadFailed();
+ }
+
+ private boolean isOverriddenAvatarAGroup() {
+ final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
+ if (overridenSelfUri == null) {
+ return false;
+ }
+ return AvatarUriUtil.TYPE_GROUP_URI.equals(AvatarUriUtil.getAvatarType(overridenSelfUri));
+ }
+
+ @Override
+ public void setAccessibility(boolean enabled) {
+ if (enabled) {
+ mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ setSendButtonAccessibility(mSendWidgetMode);
+ } else {
+ mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivity.java b/src/com/android/messaging/ui/conversation/ConversationActivity.java
new file mode 100644
index 0000000..66310ea
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationActivity.java
@@ -0,0 +1,379 @@
+/*
+ * 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.conversation;
+
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.text.TextUtils;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.contact.ContactPickerFragment;
+import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost;
+import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost;
+import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost;
+import com.android.messaging.ui.conversationlist.ConversationListActivity;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+public class ConversationActivity extends BugleActionBarActivity
+ implements ContactPickerFragmentHost, ConversationFragmentHost,
+ ConversationActivityUiStateHost {
+ public static final int FINISH_RESULT_CODE = 1;
+ private static final String SAVED_INSTANCE_STATE_UI_STATE_KEY = "uistate";
+
+ private ConversationActivityUiState mUiState;
+
+ // Fragment transactions cannot be performed after onSaveInstanceState() has been called since
+ // it will cause state loss. We don't want to call commitAllowingStateLoss() since it's
+ // dangerous. Therefore, we note when instance state is saved and avoid performing UI state
+ // updates concerning fragments past that point.
+ private boolean mInstanceStateSaved;
+
+ // Tracks whether onPause is called.
+ private boolean mIsPaused;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.conversation_activity);
+
+ final Intent intent = getIntent();
+
+ // Do our best to restore UI state from saved instance state.
+ if (savedInstanceState != null) {
+ mUiState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY);
+ } else {
+ if (intent.
+ getBooleanExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, false)) {
+ // See the comment in BugleWidgetService.getViewMoreConversationsView() why this
+ // is unfortunately necessary. The Bugle desktop widget can display a list of
+ // conversations. When there are more conversations that can be displayed in
+ // the widget, the last item is a "More conversations" item. The way widgets
+ // are built, the list items can only go to a single fill-in intent which points
+ // to this ConversationActivity. When the user taps on "More conversations", we
+ // really want to go to the ConversationList. This code makes that possible.
+ finish();
+ final Intent convListIntent = new Intent(this, ConversationListActivity.class);
+ convListIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(convListIntent);
+ return;
+ }
+ }
+
+ // If saved instance state doesn't offer a clue, get the info from the intent.
+ if (mUiState == null) {
+ final String conversationId = intent.getStringExtra(
+ UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+ mUiState = new ConversationActivityUiState(conversationId);
+ }
+ mUiState.setHost(this);
+ mInstanceStateSaved = false;
+
+ // Don't animate UI state change for initial setup.
+ updateUiState(false /* animate */);
+
+ // See if we're getting called from a widget to directly display an image or video
+ final String extraToDisplay =
+ intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI);
+ if (!TextUtils.isEmpty(extraToDisplay)) {
+ final String contentType =
+ intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE);
+ final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(
+ findViewById(R.id.conversation_and_compose_container));
+ if (ContentType.isImageType(contentType)) {
+ final Uri imagesUri = MessagingContentProvider.buildConversationImagesUri(
+ mUiState.getConversationId());
+ UIIntents.get().launchFullScreenPhotoViewer(
+ this, Uri.parse(extraToDisplay), bounds, imagesUri);
+ } else if (ContentType.isVideoType(contentType)) {
+ UIIntents.get().launchFullScreenVideoViewer(this, Uri.parse(extraToDisplay));
+ }
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ // After onSaveInstanceState() is called, future changes to mUiState won't update the UI
+ // anymore, because fragment transactions are not allowed past this point.
+ // For an activity recreation due to orientation change, the saved instance state keeps
+ // using the in-memory copy of the UI state instead of writing it to parcel as an
+ // optimization, so the UI state values may still change in response to, for example,
+ // focus change from the framework, making mUiState and actual UI inconsistent.
+ // Therefore, save an exact "snapshot" (clone) of the UI state object to make sure the
+ // restored UI state ALWAYS matches the actual restored UI components.
+ outState.putParcelable(SAVED_INSTANCE_STATE_UI_STATE_KEY, mUiState.clone());
+ mInstanceStateSaved = true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // we need to reset the mInstanceStateSaved flag since we may have just been restored from
+ // a previous onStop() instead of an onDestroy().
+ mInstanceStateSaved = false;
+ mIsPaused = false;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mIsPaused = true;
+ }
+
+ @Override
+ public void onWindowFocusChanged(final boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ final ConversationFragment conversationFragment = getConversationFragment();
+ // When the screen is turned on, the last used activity gets resumed, but it gets
+ // window focus only after the lock screen is unlocked.
+ if (hasFocus && conversationFragment != null) {
+ conversationFragment.setConversationFocus();
+ }
+ }
+
+ @Override
+ public void onDisplayHeightChanged(final int heightSpecification) {
+ super.onDisplayHeightChanged(heightSpecification);
+ invalidateActionBar();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mUiState != null) {
+ mUiState.setHost(null);
+ }
+ }
+
+ @Override
+ public void updateActionBar(final ActionBar actionBar) {
+ super.updateActionBar(actionBar);
+ final ConversationFragment conversation = getConversationFragment();
+ final ContactPickerFragment contactPicker = getContactPicker();
+ if (contactPicker != null && mUiState.shouldShowContactPickerFragment()) {
+ contactPicker.updateActionBar(actionBar);
+ } else if (conversation != null && mUiState.shouldShowConversationFragment()) {
+ conversation.updateActionBar(actionBar);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ if (super.onOptionsItemSelected(menuItem)) {
+ return true;
+ }
+ if (menuItem.getItemId() == android.R.id.home) {
+ onNavigationUpPressed();
+ return true;
+ }
+ return false;
+ }
+
+ public void onNavigationUpPressed() {
+ // Let the conversation fragment handle the navigation up press.
+ final ConversationFragment conversationFragment = getConversationFragment();
+ if (conversationFragment != null && conversationFragment.onNavigationUpPressed()) {
+ return;
+ }
+ onFinishCurrentConversation();
+ }
+
+ @Override
+ public void onBackPressed() {
+ // If action mode is active dismiss it
+ if (getActionMode() != null) {
+ dismissActionMode();
+ return;
+ }
+
+ // Let the conversation fragment handle the back press.
+ final ConversationFragment conversationFragment = getConversationFragment();
+ if (conversationFragment != null && conversationFragment.onBackPressed()) {
+ return;
+ }
+ super.onBackPressed();
+ }
+
+ private ContactPickerFragment getContactPicker() {
+ return (ContactPickerFragment) getFragmentManager().findFragmentByTag(
+ ContactPickerFragment.FRAGMENT_TAG);
+ }
+
+ private ConversationFragment getConversationFragment() {
+ return (ConversationFragment) getFragmentManager().findFragmentByTag(
+ ConversationFragment.FRAGMENT_TAG);
+ }
+
+ @Override // From ContactPickerFragmentHost
+ public void onGetOrCreateNewConversation(final String conversationId) {
+ Assert.isTrue(conversationId != null);
+ mUiState.onGetOrCreateConversation(conversationId);
+ }
+
+ @Override // From ContactPickerFragmentHost
+ public void onBackButtonPressed() {
+ onBackPressed();
+ }
+
+ @Override // From ContactPickerFragmentHost
+ public void onInitiateAddMoreParticipants() {
+ mUiState.onAddMoreParticipants();
+ }
+
+
+ @Override
+ public void onParticipantCountChanged(final boolean canAddMoreParticipants) {
+ mUiState.onParticipantCountUpdated(canAddMoreParticipants);
+ }
+
+ @Override // From ConversationFragmentHost
+ public void onStartComposeMessage() {
+ mUiState.onStartMessageCompose();
+ }
+
+ @Override // From ConversationFragmentHost
+ public void onConversationMetadataUpdated() {
+ invalidateActionBar();
+ }
+
+ @Override // From ConversationFragmentHost
+ public void onConversationMessagesUpdated(final int numberOfMessages) {
+ }
+
+ @Override // From ConversationFragmentHost
+ public void onConversationParticipantDataLoaded(final int numberOfParticipants) {
+ }
+
+ @Override // From ConversationFragmentHost
+ public boolean isActiveAndFocused() {
+ return !mIsPaused && hasWindowFocus();
+ }
+
+ @Override // From ConversationActivityUiStateListener
+ public void onConversationContactPickerUiStateChanged(final int oldState, final int newState,
+ final boolean animate) {
+ Assert.isTrue(oldState != newState);
+ updateUiState(animate);
+ }
+
+ private void updateUiState(final boolean animate) {
+ if (mInstanceStateSaved || mIsPaused) {
+ return;
+ }
+ Assert.notNull(mUiState);
+ final Intent intent = getIntent();
+ final String conversationId = mUiState.getConversationId();
+
+ final FragmentManager fragmentManager = getFragmentManager();
+ final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+
+ final boolean needConversationFragment = mUiState.shouldShowConversationFragment();
+ final boolean needContactPickerFragment = mUiState.shouldShowContactPickerFragment();
+ ConversationFragment conversationFragment = getConversationFragment();
+
+ // Set up the conversation fragment.
+ if (needConversationFragment) {
+ Assert.notNull(conversationId);
+ if (conversationFragment == null) {
+ conversationFragment = new ConversationFragment();
+ fragmentTransaction.add(R.id.conversation_fragment_container,
+ conversationFragment, ConversationFragment.FRAGMENT_TAG);
+ }
+ final MessageData draftData = intent.getParcelableExtra(
+ UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
+ if (!needContactPickerFragment) {
+ // Once the user has committed the audience,remove the draft data from the
+ // intent to prevent reuse
+ intent.removeExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
+ }
+ conversationFragment.setHost(this);
+ conversationFragment.setConversationInfo(this, conversationId, draftData);
+ } else if (conversationFragment != null) {
+ // Don't save draft to DB when removing conversation fragment and switching to
+ // contact picking mode. The draft is intended for the new group.
+ conversationFragment.suppressWriteDraft();
+ fragmentTransaction.remove(conversationFragment);
+ }
+
+ // Set up the contact picker fragment.
+ ContactPickerFragment contactPickerFragment = getContactPicker();
+ if (needContactPickerFragment) {
+ if (contactPickerFragment == null) {
+ contactPickerFragment = new ContactPickerFragment();
+ fragmentTransaction.add(R.id.contact_picker_fragment_container,
+ contactPickerFragment, ContactPickerFragment.FRAGMENT_TAG);
+ }
+ contactPickerFragment.setHost(this);
+ contactPickerFragment.setContactPickingMode(mUiState.getDesiredContactPickingMode(),
+ animate);
+ } else if (contactPickerFragment != null) {
+ fragmentTransaction.remove(contactPickerFragment);
+ }
+
+ fragmentTransaction.commit();
+ invalidateActionBar();
+ }
+
+ @Override
+ public void onFinishCurrentConversation() {
+ // Simply finish the current activity. The current design is to leave any empty
+ // conversations as is.
+ if (OsUtil.isAtLeastL()) {
+ finishAfterTransition();
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ public boolean shouldResumeComposeMessage() {
+ return mUiState.shouldResumeComposeMessage();
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode,
+ final Intent data) {
+ if (requestCode == ConversationFragment.REQUEST_CHOOSE_ATTACHMENTS &&
+ resultCode == RESULT_OK) {
+ final ConversationFragment conversationFragment = getConversationFragment();
+ if (conversationFragment != null) {
+ conversationFragment.onAttachmentChoosen();
+ } else {
+ LogUtil.e(LogUtil.BUGLE_TAG, "ConversationFragment is missing after launching " +
+ "AttachmentChooserActivity!");
+ }
+ } else if (resultCode == FINISH_RESULT_CODE) {
+ finish();
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java b/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java
new file mode 100644
index 0000000..1469c93
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java
@@ -0,0 +1,306 @@
+/*
+ * 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.conversation;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.messaging.ui.contact.ContactPickerFragment;
+import com.android.messaging.util.Assert;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Keeps track of the different UI states that the ConversationActivity may be in. This acts as
+ * a state machine which, based on different actions (e.g. onAddMoreParticipants), notifies the
+ * ConversationActivity about any state UI change so it can update the visuals. This class
+ * implements Parcelable and it's persisted across activity tear down and relaunch.
+ */
+public class ConversationActivityUiState implements Parcelable, Cloneable {
+ interface ConversationActivityUiStateHost {
+ void onConversationContactPickerUiStateChanged(int oldState, int newState, boolean animate);
+ }
+
+ /*------ Overall UI states (conversation & contact picker) ------*/
+
+ /** Only a full screen conversation is showing. */
+ public static final int STATE_CONVERSATION_ONLY = 1;
+ /** Only a full screen contact picker is showing asking user to pick the initial contact. */
+ public static final int STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT = 2;
+ /**
+ * Only a full screen contact picker is showing asking user to pick more participants. This
+ * happens after the user picked the initial contact, and then decide to go back and add more.
+ */
+ public static final int STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS = 3;
+ /**
+ * Only a full screen contact picker is showing asking user to pick more participants. However
+ * user has reached max number of conversation participants and can add no more.
+ */
+ public static final int STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS = 4;
+ /**
+ * A hybrid mode where the conversation view + contact chips view are showing. This happens
+ * right after the user picked the initial contact for which a 1-1 conversation is fetched or
+ * created.
+ */
+ public static final int STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW = 5;
+
+ // The overall UI state of the ConversationActivity.
+ private int mConversationContactUiState;
+
+ // The currently displayed conversation (if any).
+ private String mConversationId;
+
+ // Indicates whether we should put focus in the compose message view when the
+ // ConversationFragment is attached. This is a transient state that's not persisted as
+ // part of the parcelable.
+ private boolean mPendingResumeComposeMessage = false;
+
+ // The owner ConversationActivity. This is not parceled since the instance always change upon
+ // object reuse.
+ private ConversationActivityUiStateHost mHost;
+
+ // Indicates the owning ConverastionActivity is in the process of updating its UI presentation
+ // to be in sync with the UI states. Outside of the UI updates, the UI states here should
+ // ALWAYS be consistent with the actual states of the activity.
+ private int mUiUpdateCount;
+
+ /**
+ * Create a new instance with an initial conversation id.
+ */
+ ConversationActivityUiState(final String conversationId) {
+ // The conversation activity may be initialized with only one of two states:
+ // Conversation-only (when there's a conversation id) or picking initial contact
+ // (when no conversation id is given).
+ mConversationId = conversationId;
+ mConversationContactUiState = conversationId == null ?
+ STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT : STATE_CONVERSATION_ONLY;
+ }
+
+ public void setHost(final ConversationActivityUiStateHost host) {
+ mHost = host;
+ }
+
+ public boolean shouldShowConversationFragment() {
+ return mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW ||
+ mConversationContactUiState == STATE_CONVERSATION_ONLY;
+ }
+
+ public boolean shouldShowContactPickerFragment() {
+ return mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS ||
+ mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS ||
+ mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT ||
+ mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW;
+ }
+
+ /**
+ * Returns whether there's a pending request to resume message compose (i.e. set focus to
+ * the compose message view and show the soft keyboard). If so, this request will be served
+ * when the conversation fragment get created and resumed. This happens when the user commits
+ * participant selection for a group conversation and goes back to the conversation fragment.
+ * Since conversation fragment creation happens asynchronously, we issue and track this
+ * pending request for it to be eventually fulfilled.
+ */
+ public boolean shouldResumeComposeMessage() {
+ if (mPendingResumeComposeMessage) {
+ // This is a one-shot operation that just keeps track of the pending resume compose
+ // state. This is also a non-critical operation so we don't care about failure case.
+ mPendingResumeComposeMessage = false;
+ return true;
+ }
+ return false;
+ }
+
+ public int getDesiredContactPickingMode() {
+ switch (mConversationContactUiState) {
+ case STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS:
+ return ContactPickerFragment.MODE_PICK_MORE_CONTACTS;
+ case STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS:
+ return ContactPickerFragment.MODE_PICK_MAX_PARTICIPANTS;
+ case STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT:
+ return ContactPickerFragment.MODE_PICK_INITIAL_CONTACT;
+ case STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW:
+ return ContactPickerFragment.MODE_CHIPS_ONLY;
+ default:
+ Assert.fail("Invalid contact picking mode for ConversationActivity!");
+ return ContactPickerFragment.MODE_UNDEFINED;
+ }
+ }
+
+ public String getConversationId() {
+ return mConversationId;
+ }
+
+ /**
+ * Called whenever the contact picker fragment successfully fetched or created a conversation.
+ */
+ public void onGetOrCreateConversation(final String conversationId) {
+ int newState = STATE_CONVERSATION_ONLY;
+ if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) {
+ newState = STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW;
+ } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS ||
+ mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS) {
+ newState = STATE_CONVERSATION_ONLY;
+ } else {
+ // New conversation should only be created when we are in one of the contact picking
+ // modes.
+ Assert.fail("Invalid conversation activity state: can't create conversation!");
+ }
+ mConversationId = conversationId;
+ performUiStateUpdate(newState, true);
+ }
+
+ /**
+ * Called when the user started composing message. If we are in the hybrid chips state, we
+ * should commit to enter the conversation only state.
+ */
+ public void onStartMessageCompose() {
+ // This cannot happen when we are in one of the full-screen contact picking states.
+ Assert.isTrue(mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT &&
+ mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS &&
+ mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS);
+ if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) {
+ performUiStateUpdate(STATE_CONVERSATION_ONLY, true);
+ }
+ }
+
+ /**
+ * Called when the user initiated an action to add more participants in the hybrid state,
+ * namely clicking on the "add more participants" button or entered a new contact chip via
+ * auto-complete.
+ */
+ public void onAddMoreParticipants() {
+ if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) {
+ mPendingResumeComposeMessage = true;
+ performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, true);
+ } else {
+ // This is only possible in the hybrid state.
+ Assert.fail("Invalid conversation activity state: can't add more participants!");
+ }
+ }
+
+ /**
+ * Called each time the number of participants is updated to check against the limit and
+ * update the ui state accordingly.
+ */
+ public void onParticipantCountUpdated(final boolean canAddMoreParticipants) {
+ if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS
+ && !canAddMoreParticipants) {
+ performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS, false);
+ } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS
+ && canAddMoreParticipants) {
+ performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, false);
+ }
+ }
+
+ private void performUiStateUpdate(final int conversationContactState, final boolean animate) {
+ // This starts one UI update cycle, during which we allow the conversation activity's
+ // UI presentation to be temporarily out of sync with the states here.
+ beginUiUpdate();
+
+ if (conversationContactState != mConversationContactUiState) {
+ final int oldState = mConversationContactUiState;
+ mConversationContactUiState = conversationContactState;
+ notifyOnOverallUiStateChanged(oldState, mConversationContactUiState, animate);
+ }
+ endUiUpdate();
+ }
+
+ private void notifyOnOverallUiStateChanged(
+ final int oldState, final int newState, final boolean animate) {
+ // Always verify state validity whenever we have a state change.
+ assertValidState();
+ Assert.isTrue(isUiUpdateInProgress());
+
+ // Only do this if we are still attached to the host. mHost can be null if the host
+ // activity is already destroyed, but due to timing the contained UI components may still
+ // receive events such as focus change and trigger a callback to the Ui state. We'd like
+ // to guard against those cases.
+ if (mHost != null) {
+ mHost.onConversationContactPickerUiStateChanged(oldState, newState, animate);
+ }
+ }
+
+ private void assertValidState() {
+ // Conversation id may be null IF AND ONLY IF the user is picking the initial contact to
+ // start a conversation.
+ Assert.isTrue((mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) ==
+ (mConversationId == null));
+ }
+
+ private void beginUiUpdate() {
+ mUiUpdateCount++;
+ }
+
+ private void endUiUpdate() {
+ if (--mUiUpdateCount < 0) {
+ Assert.fail("Unbalanced Ui updates!");
+ }
+ }
+
+ private boolean isUiUpdateInProgress() {
+ return mUiUpdateCount > 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ dest.writeInt(mConversationContactUiState);
+ dest.writeString(mConversationId);
+ }
+
+ private ConversationActivityUiState(final Parcel in) {
+ mConversationContactUiState = in.readInt();
+ mConversationId = in.readString();
+
+ // Always verify state validity whenever we initialize states.
+ assertValidState();
+ }
+
+ public static final Parcelable.Creator<ConversationActivityUiState> CREATOR
+ = new Parcelable.Creator<ConversationActivityUiState>() {
+ @Override
+ public ConversationActivityUiState createFromParcel(final Parcel in) {
+ return new ConversationActivityUiState(in);
+ }
+
+ @Override
+ public ConversationActivityUiState[] newArray(final int size) {
+ return new ConversationActivityUiState[size];
+ }
+ };
+
+ @Override
+ protected ConversationActivityUiState clone() {
+ try {
+ return (ConversationActivityUiState) super.clone();
+ } catch (CloneNotSupportedException e) {
+ Assert.fail("ConversationActivityUiState: failed to clone(). Is there a mutable " +
+ "reference?");
+ }
+ return null;
+ }
+
+ /**
+ * allows for overridding the internal UI state. Should never be called except by test code.
+ */
+ @VisibleForTesting
+ void testSetUiState(final int uiState) {
+ mConversationContactUiState = uiState;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java b/src/com/android/messaging/ui/conversation/ConversationFastScroller.java
new file mode 100644
index 0000000..b15f05a
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationFastScroller.java
@@ -0,0 +1,489 @@
+/*
+ * 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.conversation;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.StateListDrawable;
+import android.os.Handler;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.AdapterDataObserver;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.StateSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroupOverlay;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Adds a "fast-scroll" bar to the conversation RecyclerView that shows the current position within
+ * the conversation and allows quickly moving to another position by dragging the scrollbar thumb
+ * up or down. As the thumb is dragged, we show a floating bubble alongside it that shows the
+ * date/time of the first visible message at the current position.
+ */
+public class ConversationFastScroller extends RecyclerView.OnScrollListener implements
+ OnLayoutChangeListener, RecyclerView.OnItemTouchListener {
+
+ /**
+ * Creates a {@link ConversationFastScroller} instance, attached to the provided
+ * {@link RecyclerView}.
+ *
+ * @param rv the conversation RecyclerView
+ * @param position where the scrollbar should appear (either {@code POSITION_RIGHT_SIDE} or
+ * {@code POSITION_LEFT_SIDE})
+ * @return a new ConversationFastScroller, or {@code null} if fast-scrolling is not supported
+ * (the feature requires Jellybean MR2 or newer)
+ */
+ public static ConversationFastScroller addTo(RecyclerView rv, int position) {
+ if (OsUtil.isAtLeastJB_MR2()) {
+ return new ConversationFastScroller(rv, position);
+ }
+ return null;
+ }
+
+ public static final int POSITION_RIGHT_SIDE = 0;
+ public static final int POSITION_LEFT_SIDE = 1;
+
+ private static final int MIN_PAGES_TO_ENABLE = 7;
+ private static final int SHOW_ANIMATION_DURATION_MS = 150;
+ private static final int HIDE_ANIMATION_DURATION_MS = 300;
+ private static final int HIDE_DELAY_MS = 1500;
+
+ private final Context mContext;
+ private final RecyclerView mRv;
+ private final ViewGroupOverlay mOverlay;
+ private final ImageView mTrackImageView;
+ private final ImageView mThumbImageView;
+ private final TextView mPreviewTextView;
+
+ private final int mTrackWidth;
+ private final int mThumbHeight;
+ private final int mPreviewHeight;
+ private final int mPreviewMinWidth;
+ private final int mPreviewMarginTop;
+ private final int mPreviewMarginLeftRight;
+ private final int mTouchSlop;
+
+ private final Rect mContainer = new Rect();
+ private final Handler mHandler = new Handler();
+
+ // Whether to render the scrollbar on the right side (otherwise it'll be on the left).
+ private final boolean mPosRight;
+
+ // Whether the scrollbar is currently visible (it may still be animating).
+ private boolean mVisible = false;
+
+ // Whether we are waiting to hide the scrollbar (i.e. scrolling has stopped).
+ private boolean mPendingHide = false;
+
+ // Whether the user is currently dragging the thumb up or down.
+ private boolean mDragging = false;
+
+ // Animations responsible for hiding the scrollbar & preview. May be null.
+ private AnimatorSet mHideAnimation;
+ private ObjectAnimator mHidePreviewAnimation;
+
+ private final Runnable mHideTrackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hide(true /* animate */);
+ mPendingHide = false;
+ }
+ };
+
+ private ConversationFastScroller(RecyclerView rv, int position) {
+ mContext = rv.getContext();
+ mRv = rv;
+ mRv.addOnLayoutChangeListener(this);
+ mRv.addOnScrollListener(this);
+ mRv.addOnItemTouchListener(this);
+ mRv.getAdapter().registerAdapterDataObserver(new AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ updateScrollPos();
+ }
+ });
+ mPosRight = (position == POSITION_RIGHT_SIDE);
+
+ // Cache the dimensions we'll need during layout
+ final Resources res = mContext.getResources();
+ mTrackWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_width);
+ mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height);
+ mPreviewHeight = res.getDimensionPixelSize(R.dimen.fastscroll_preview_height);
+ mPreviewMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_preview_min_width);
+ mPreviewMarginTop = res.getDimensionPixelOffset(R.dimen.fastscroll_preview_margin_top);
+ mPreviewMarginLeftRight = res.getDimensionPixelOffset(
+ R.dimen.fastscroll_preview_margin_left_right);
+ mTouchSlop = res.getDimensionPixelOffset(R.dimen.fastscroll_touch_slop);
+
+ final LayoutInflater inflator = LayoutInflater.from(mContext);
+ mTrackImageView = (ImageView) inflator.inflate(R.layout.fastscroll_track, null);
+ mThumbImageView = (ImageView) inflator.inflate(R.layout.fastscroll_thumb, null);
+ mPreviewTextView = (TextView) inflator.inflate(R.layout.fastscroll_preview, null);
+
+ refreshConversationThemeColor();
+
+ // Add the fast scroll views to the overlay, so they are rendered above the list
+ mOverlay = rv.getOverlay();
+ mOverlay.add(mTrackImageView);
+ mOverlay.add(mThumbImageView);
+ mOverlay.add(mPreviewTextView);
+
+ hide(false /* animate */);
+ mPreviewTextView.setAlpha(0f);
+ }
+
+ public void refreshConversationThemeColor() {
+ mPreviewTextView.setBackground(
+ ConversationDrawables.get().getFastScrollPreviewDrawable(mPosRight));
+ if (OsUtil.isAtLeastL()) {
+ final StateListDrawable drawable = new StateListDrawable();
+ drawable.addState(new int[]{ android.R.attr.state_pressed },
+ ConversationDrawables.get().getFastScrollThumbDrawable(true /* pressed */));
+ drawable.addState(StateSet.WILD_CARD,
+ ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
+ mThumbImageView.setImageDrawable(drawable);
+ } else {
+ // Android pre-L doesn't seem to handle a StateListDrawable containing a tinted
+ // drawable (it's rendered in the filter base color, which is red), so fall back to
+ // just the regular (non-pressed) drawable.
+ mThumbImageView.setImageDrawable(
+ ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(final RecyclerView view, final int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
+ // Only show the scrollbar once the user starts scrolling
+ if (!mVisible && isEnabled()) {
+ show();
+ }
+ cancelAnyPendingHide();
+ } else if (newState == RecyclerView.SCROLL_STATE_IDLE && !mDragging) {
+ // Hide the scrollbar again after scrolling stops
+ hideAfterDelay();
+ }
+ }
+
+ private boolean isEnabled() {
+ final int range = mRv.computeVerticalScrollRange();
+ final int extent = mRv.computeVerticalScrollExtent();
+
+ if (range == 0 || extent == 0) {
+ return false; // Conversation isn't long enough to scroll
+ }
+ // Only enable scrollbars for conversations long enough that they would require several
+ // flings to scroll through.
+ final float pages = (float) range / extent;
+ return (pages > MIN_PAGES_TO_ENABLE);
+ }
+
+ private void show() {
+ if (mHideAnimation != null && mHideAnimation.isRunning()) {
+ mHideAnimation.cancel();
+ }
+ // Slide the scrollbar in from the side
+ ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 0);
+ ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 0);
+ AnimatorSet animation = new AnimatorSet();
+ animation.playTogether(trackSlide, thumbSlide);
+ animation.setDuration(SHOW_ANIMATION_DURATION_MS);
+ animation.start();
+
+ mVisible = true;
+ updateScrollPos();
+ }
+
+ private void hideAfterDelay() {
+ cancelAnyPendingHide();
+ mHandler.postDelayed(mHideTrackRunnable, HIDE_DELAY_MS);
+ mPendingHide = true;
+ }
+
+ private void cancelAnyPendingHide() {
+ if (mPendingHide) {
+ mHandler.removeCallbacks(mHideTrackRunnable);
+ }
+ }
+
+ private void hide(boolean animate) {
+ final int hiddenTranslationX = mPosRight ? mTrackWidth : -mTrackWidth;
+ if (animate) {
+ // Slide the scrollbar off to the side
+ ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X,
+ hiddenTranslationX);
+ ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X,
+ hiddenTranslationX);
+ mHideAnimation = new AnimatorSet();
+ mHideAnimation.playTogether(trackSlide, thumbSlide);
+ mHideAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
+ mHideAnimation.start();
+ } else {
+ mTrackImageView.setTranslationX(hiddenTranslationX);
+ mThumbImageView.setTranslationX(hiddenTranslationX);
+ }
+
+ mVisible = false;
+ }
+
+ private void showPreview() {
+ if (mHidePreviewAnimation != null && mHidePreviewAnimation.isRunning()) {
+ mHidePreviewAnimation.cancel();
+ }
+ mPreviewTextView.setAlpha(1f);
+ }
+
+ private void hidePreview() {
+ mHidePreviewAnimation = ObjectAnimator.ofFloat(mPreviewTextView, View.ALPHA, 0f);
+ mHidePreviewAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
+ mHidePreviewAnimation.start();
+ }
+
+ @Override
+ public void onScrolled(final RecyclerView view, final int dx, final int dy) {
+ updateScrollPos();
+ }
+
+ private void updateScrollPos() {
+ if (!mVisible) {
+ return;
+ }
+ final int verticalScrollLength = mContainer.height() - mThumbHeight;
+ final int verticalScrollStart = mContainer.top + mThumbHeight / 2;
+
+ final float scrollRatio = computeScrollRatio();
+ final int thumbCenterY = verticalScrollStart + (int)(verticalScrollLength * scrollRatio);
+ layoutThumb(thumbCenterY);
+
+ if (mDragging) {
+ updatePreviewText();
+ layoutPreview(thumbCenterY);
+ }
+ }
+
+ /**
+ * Returns the current position in the conversation, as a value between 0 and 1, inclusive.
+ * The top of the conversation is 0, the bottom is 1, the exact middle is 0.5, and so on.
+ */
+ private float computeScrollRatio() {
+ final int range = mRv.computeVerticalScrollRange();
+ final int extent = mRv.computeVerticalScrollExtent();
+ int offset = mRv.computeVerticalScrollOffset();
+
+ if (range == 0 || extent == 0) {
+ // If the conversation doesn't scroll, we're at the bottom.
+ return 1.0f;
+ }
+ final int scrollRange = range - extent;
+ offset = Math.min(offset, scrollRange);
+ return offset / (float) scrollRange;
+ }
+
+ private void updatePreviewText() {
+ final LinearLayoutManager lm = (LinearLayoutManager) mRv.getLayoutManager();
+ final int pos = lm.findFirstVisibleItemPosition();
+ if (pos == RecyclerView.NO_POSITION) {
+ return;
+ }
+ final ViewHolder vh = mRv.findViewHolderForAdapterPosition(pos);
+ if (vh == null) {
+ // This can happen if the messages update while we're dragging the thumb.
+ return;
+ }
+ final ConversationMessageView messageView = (ConversationMessageView) vh.itemView;
+ final ConversationMessageData messageData = messageView.getData();
+ final long timestamp = messageData.getReceivedTimeStamp();
+ final CharSequence timestampText = Dates.getFastScrollPreviewTimeString(timestamp);
+ mPreviewTextView.setText(timestampText);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ if (!mVisible) {
+ return false;
+ }
+ // If the user presses down on the scroll thumb, we'll start intercepting events from the
+ // RecyclerView so we can handle the move events while they're dragging it up/down.
+ final int action = e.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (isInsideThumb(e.getX(), e.getY())) {
+ startDrag();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mDragging) {
+ return true;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (mDragging) {
+ cancelDrag();
+ }
+ return false;
+ }
+ return false;
+ }
+
+ private boolean isInsideThumb(float x, float y) {
+ final int hitTargetLeft = mThumbImageView.getLeft() - mTouchSlop;
+ final int hitTargetRight = mThumbImageView.getRight() + mTouchSlop;
+
+ if (x < hitTargetLeft || x > hitTargetRight) {
+ return false;
+ }
+ if (y < mThumbImageView.getTop() || y > mThumbImageView.getBottom()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ if (!mDragging) {
+ return;
+ }
+ final int action = e.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ handleDragMove(e.getY());
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ cancelDrag();
+ break;
+ }
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+
+ private void startDrag() {
+ mDragging = true;
+ mThumbImageView.setPressed(true);
+ updateScrollPos();
+ showPreview();
+ cancelAnyPendingHide();
+ }
+
+ private void handleDragMove(float y) {
+ final int verticalScrollLength = mContainer.height() - mThumbHeight;
+ final int verticalScrollStart = mContainer.top + (mThumbHeight / 2);
+
+ // Convert the desired position from px to a scroll position in the conversation.
+ float dragScrollRatio = (y - verticalScrollStart) / verticalScrollLength;
+ dragScrollRatio = Math.max(dragScrollRatio, 0.0f);
+ dragScrollRatio = Math.min(dragScrollRatio, 1.0f);
+
+ // Scroll the RecyclerView to a new position.
+ final int itemCount = mRv.getAdapter().getItemCount();
+ final int itemPos = (int)((itemCount - 1) * dragScrollRatio);
+ mRv.scrollToPosition(itemPos);
+ }
+
+ private void cancelDrag() {
+ mDragging = false;
+ mThumbImageView.setPressed(false);
+ hidePreview();
+ hideAfterDelay();
+ }
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (!mVisible) {
+ hide(false /* animate */);
+ }
+ // The container is the size of the RecyclerView that's visible on screen. We have to
+ // exclude the top padding, because it's usually hidden behind the conversation action bar.
+ mContainer.set(left, top + mRv.getPaddingTop(), right, bottom);
+ layoutTrack();
+ updateScrollPos();
+ }
+
+ private void layoutTrack() {
+ int trackHeight = Math.max(0, mContainer.height());
+ int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
+ int heightMeasureSpec = MeasureSpec.makeMeasureSpec(trackHeight, MeasureSpec.EXACTLY);
+ mTrackImageView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
+ int top = mContainer.top;
+ int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
+ int bottom = mContainer.bottom;
+ mTrackImageView.layout(left, top, right, bottom);
+ }
+
+ private void layoutThumb(int centerY) {
+ int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
+ int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mThumbHeight, MeasureSpec.EXACTLY);
+ mThumbImageView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
+ int top = centerY - (mThumbImageView.getHeight() / 2);
+ int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
+ int bottom = top + mThumbHeight;
+ mThumbImageView.layout(left, top, right, bottom);
+ }
+
+ private void layoutPreview(int centerY) {
+ int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mContainer.width(), MeasureSpec.AT_MOST);
+ int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewHeight, MeasureSpec.EXACTLY);
+ mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ // Ensure that the preview bubble is at least as wide as it is tall
+ if (mPreviewTextView.getMeasuredWidth() < mPreviewMinWidth) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewMinWidth, MeasureSpec.EXACTLY);
+ mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
+ }
+ final int previewMinY = mContainer.top + mPreviewMarginTop;
+
+ final int left, right;
+ if (mPosRight) {
+ right = mContainer.right - mTrackWidth - mPreviewMarginLeftRight;
+ left = right - mPreviewTextView.getMeasuredWidth();
+ } else {
+ left = mContainer.left + mTrackWidth + mPreviewMarginLeftRight;
+ right = left + mPreviewTextView.getMeasuredWidth();
+ }
+
+ int bottom = centerY;
+ int top = bottom - mPreviewTextView.getMeasuredHeight();
+ if (top < previewMinY) {
+ top = previewMinY;
+ bottom = top + mPreviewTextView.getMeasuredHeight();
+ }
+ mPreviewTextView.layout(left, top, right, bottom);
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java
new file mode 100644
index 0000000..a6a191a
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationFragment.java
@@ -0,0 +1,1662 @@
+/*
+ * 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.conversation;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DownloadManager;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v4.text.BidiFormatter;
+import android.support.v4.text.TextDirectionHeuristicsCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.text.TextUtils;
+import android.view.ActionMode;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.InsertNewMessageAction;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.binding.ImmutableBindingRef;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.datamodel.data.ConversationParticipantsData;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.ui.AttachmentPreview;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.ui.SnackBar;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.animation.PopupTransitionAnimation;
+import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
+import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost;
+import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost;
+import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
+import com.android.messaging.ui.mediapicker.MediaPicker;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ChangeDefaultSmsAppHelper;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.TextUtil;
+import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.UriUtil;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows a list of messages/parts comprising a conversation.
+ */
+public class ConversationFragment extends Fragment implements ConversationDataListener,
+ IComposeMessageViewHost, ConversationMessageViewHost, ConversationInputHost,
+ DraftMessageDataListener {
+
+ public interface ConversationFragmentHost extends ImeUtil.ImeStateHost {
+ void onStartComposeMessage();
+ void onConversationMetadataUpdated();
+ boolean shouldResumeComposeMessage();
+ void onFinishCurrentConversation();
+ void invalidateActionBar();
+ ActionMode startActionMode(ActionMode.Callback callback);
+ void dismissActionMode();
+ ActionMode getActionMode();
+ void onConversationMessagesUpdated(int numberOfMessages);
+ void onConversationParticipantDataLoaded(int numberOfParticipants);
+ boolean isActiveAndFocused();
+ }
+
+ public static final String FRAGMENT_TAG = "conversation";
+
+ static final int REQUEST_CHOOSE_ATTACHMENTS = 2;
+ private static final int JUMP_SCROLL_THRESHOLD = 15;
+ // We animate the message from draft to message list, if we the message doesn't show up in the
+ // list within this time limit, then we just do a fade in animation instead
+ public static final int MESSAGE_ANIMATION_MAX_WAIT = 500;
+
+ private ComposeMessageView mComposeMessageView;
+ private RecyclerView mRecyclerView;
+ private ConversationMessageAdapter mAdapter;
+ private ConversationFastScroller mFastScroller;
+
+ private View mConversationComposeDivider;
+ private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
+
+ private String mConversationId;
+ // If the fragment receives a draft as part of the invocation this is set
+ private MessageData mIncomingDraft;
+
+ // This binding keeps track of our associated ConversationData instance
+ // A binding should have the lifetime of the owning component,
+ // don't recreate, unbind and bind if you need new data
+ @VisibleForTesting
+ final Binding<ConversationData> mBinding = BindingBase.createBinding(this);
+
+ // Saved Instance State Data - only for temporal data which is nice to maintain but not
+ // critical for correctness.
+ private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY = "conversationViewState";
+ private Parcelable mListState;
+
+ private ConversationFragmentHost mHost;
+
+ protected List<Integer> mFilterResults;
+
+ // The minimum scrolling distance between RecyclerView's scroll change event beyong which
+ // a fling motion is considered fast, in which case we'll delay load image attachments for
+ // perf optimization.
+ private int mFastFlingThreshold;
+
+ // ConversationMessageView that is currently selected
+ private ConversationMessageView mSelectedMessage;
+
+ // Attachment data for the attachment within the selected message that was long pressed
+ private MessagePartData mSelectedAttachment;
+
+ // Normally, as soon as draft message is loaded, we trust the UI state held in
+ // ComposeMessageView to be the only source of truth (incl. the conversation self id). However,
+ // there can be external events that forces the UI state to change, such as SIM state changes
+ // or SIM auto-switching on receiving a message. This receiver is used to receive such
+ // local broadcast messages and reflect the change in the UI.
+ private final BroadcastReceiver mConversationSelfIdChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String conversationId =
+ intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+ final String selfId =
+ intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_SELF_ID);
+ Assert.notNull(conversationId);
+ Assert.notNull(selfId);
+ if (TextUtils.equals(mBinding.getData().getConversationId(), conversationId)) {
+ mComposeMessageView.updateConversationSelfIdOnExternalChange(selfId);
+ }
+ }
+ };
+
+ // Flag to prevent writing draft to DB on pause
+ private boolean mSuppressWriteDraft;
+
+ // Indicates whether local draft should be cleared due to external draft changes that must
+ // be reloaded from db
+ private boolean mClearLocalDraft;
+ private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+
+ private boolean isScrolledToBottom() {
+ if (mRecyclerView.getChildCount() == 0) {
+ return true;
+ }
+ final View lastView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1);
+ int lastVisibleItem = ((LinearLayoutManager) mRecyclerView
+ .getLayoutManager()).findLastVisibleItemPosition();
+ if (lastVisibleItem < 0) {
+ // If the recyclerView height is 0, then the last visible item position is -1
+ // Try to compute the position of the last item, even though it's not visible
+ final long id = mRecyclerView.getChildItemId(lastView);
+ final RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForItemId(id);
+ if (holder != null) {
+ lastVisibleItem = holder.getAdapterPosition();
+ }
+ }
+ final int totalItemCount = mRecyclerView.getAdapter().getItemCount();
+ final boolean isAtBottom = (lastVisibleItem + 1 == totalItemCount);
+ return isAtBottom && lastView.getBottom() <= mRecyclerView.getHeight();
+ }
+
+ private void scrollToBottom(final boolean smoothScroll) {
+ if (mAdapter.getItemCount() > 0) {
+ scrollToPosition(mAdapter.getItemCount() - 1, smoothScroll);
+ }
+ }
+
+ private int mScrollToDismissThreshold;
+ private final RecyclerView.OnScrollListener mListScrollListener =
+ new RecyclerView.OnScrollListener() {
+ // Keeps track of cumulative scroll delta during a scroll event, which we may use to
+ // hide the media picker & co.
+ private int mCumulativeScrollDelta;
+ private boolean mScrollToDismissHandled;
+ private boolean mWasScrolledToBottom = true;
+ private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+
+ @Override
+ public void onScrollStateChanged(final RecyclerView view, final int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ // Reset scroll states.
+ mCumulativeScrollDelta = 0;
+ mScrollToDismissHandled = false;
+ } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
+ mRecyclerView.getItemAnimator().endAnimations();
+ }
+ mScrollState = newState;
+ }
+
+ @Override
+ public void onScrolled(final RecyclerView view, final int dx, final int dy) {
+ if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING &&
+ !mScrollToDismissHandled) {
+ mCumulativeScrollDelta += dy;
+ // Dismiss the keyboard only when the user scroll up (into the past).
+ if (mCumulativeScrollDelta < -mScrollToDismissThreshold) {
+ mComposeMessageView.hideAllComposeInputs(false /* animate */);
+ mScrollToDismissHandled = true;
+ }
+ }
+ if (mWasScrolledToBottom != isScrolledToBottom()) {
+ mConversationComposeDivider.animate().alpha(isScrolledToBottom() ? 0 : 1);
+ mWasScrolledToBottom = isScrolledToBottom();
+ }
+ }
+ };
+
+ private final ActionMode.Callback mMessageActionModeCallback = new ActionMode.Callback() {
+ @Override
+ public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
+ if (mSelectedMessage == null) {
+ return false;
+ }
+ final ConversationMessageData data = mSelectedMessage.getData();
+ final MenuInflater menuInflater = getActivity().getMenuInflater();
+ menuInflater.inflate(R.menu.conversation_fragment_select_menu, menu);
+ menu.findItem(R.id.action_download).setVisible(data.getShowDownloadMessage());
+ menu.findItem(R.id.action_send).setVisible(data.getShowResendMessage());
+
+ // ShareActionProvider does not work with ActionMode. So we use a normal menu item.
+ menu.findItem(R.id.share_message_menu).setVisible(data.getCanForwardMessage());
+ menu.findItem(R.id.save_attachment).setVisible(mSelectedAttachment != null);
+ menu.findItem(R.id.forward_message_menu).setVisible(data.getCanForwardMessage());
+
+ // TODO: We may want to support copying attachments in the future, but it's
+ // unclear which attachment to pick when we make this context menu at the message level
+ // instead of the part level
+ menu.findItem(R.id.copy_text).setVisible(data.getCanCopyMessageToClipboard());
+
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
+ final ConversationMessageData data = mSelectedMessage.getData();
+ final String messageId = data.getMessageId();
+ switch (menuItem.getItemId()) {
+ case R.id.save_attachment:
+ if (OsUtil.hasStoragePermission()) {
+ final SaveAttachmentTask saveAttachmentTask = new SaveAttachmentTask(
+ getActivity());
+ for (final MessagePartData part : data.getAttachments()) {
+ saveAttachmentTask.addAttachmentToSave(part.getContentUri(),
+ part.getContentType());
+ }
+ if (saveAttachmentTask.getAttachmentCount() > 0) {
+ saveAttachmentTask.executeOnThreadPool();
+ mHost.dismissActionMode();
+ }
+ } else {
+ getActivity().requestPermissions(
+ new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
+ }
+ return true;
+ case R.id.action_delete_message:
+ if (mSelectedMessage != null) {
+ deleteMessage(messageId);
+ }
+ return true;
+ case R.id.action_download:
+ if (mSelectedMessage != null) {
+ retryDownload(messageId);
+ mHost.dismissActionMode();
+ }
+ return true;
+ case R.id.action_send:
+ if (mSelectedMessage != null) {
+ retrySend(messageId);
+ mHost.dismissActionMode();
+ }
+ return true;
+ case R.id.copy_text:
+ Assert.isTrue(data.hasText());
+ final ClipboardManager clipboard = (ClipboardManager) getActivity()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(
+ ClipData.newPlainText(null /* label */, data.getText()));
+ mHost.dismissActionMode();
+ return true;
+ case R.id.details_menu:
+ MessageDetailsDialog.show(
+ getActivity(), data, mBinding.getData().getParticipants(),
+ mBinding.getData().getSelfParticipantById(data.getSelfParticipantId()));
+ mHost.dismissActionMode();
+ return true;
+ case R.id.share_message_menu:
+ shareMessage(data);
+ mHost.dismissActionMode();
+ return true;
+ case R.id.forward_message_menu:
+ // TODO: Currently we are forwarding one part at a time, instead of
+ // the entire message. Change this to forwarding the entire message when we
+ // use message-based cursor in conversation.
+ final MessageData message = mBinding.getData().createForwardedMessage(data);
+ UIIntents.get().launchForwardMessageActivity(getActivity(), message);
+ mHost.dismissActionMode();
+ return true;
+ }
+ return false;
+ }
+
+ private void shareMessage(final ConversationMessageData data) {
+ // Figure out what to share.
+ MessagePartData attachmentToShare = mSelectedAttachment;
+ // If the user long-pressed on the background, we will share the text (if any)
+ // or the first attachment.
+ if (mSelectedAttachment == null
+ && TextUtil.isAllWhitespace(data.getText())) {
+ final List<MessagePartData> attachments = data.getAttachments();
+ if (attachments.size() > 0) {
+ attachmentToShare = attachments.get(0);
+ }
+ }
+
+ final Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ if (attachmentToShare == null) {
+ shareIntent.putExtra(Intent.EXTRA_TEXT, data.getText());
+ shareIntent.setType("text/plain");
+ } else {
+ shareIntent.putExtra(
+ Intent.EXTRA_STREAM, attachmentToShare.getContentUri());
+ shareIntent.setType(attachmentToShare.getContentType());
+ }
+ final CharSequence title = getResources().getText(R.string.action_share);
+ startActivity(Intent.createChooser(shareIntent, title));
+ }
+
+ @Override
+ public void onDestroyActionMode(final ActionMode actionMode) {
+ selectMessage(null);
+ }
+ };
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mFastFlingThreshold = getResources().getDimensionPixelOffset(
+ R.dimen.conversation_fast_fling_threshold);
+ mAdapter = new ConversationMessageAdapter(getActivity(), null, this,
+ null,
+ // Sets the item click listener on the Recycler item views.
+ new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ final ConversationMessageView messageView = (ConversationMessageView) v;
+ handleMessageClick(messageView);
+ }
+ },
+ new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View view) {
+ selectMessage((ConversationMessageView) view);
+ return true;
+ }
+ }
+ );
+ }
+
+ /**
+ * setConversationInfo() may be called before or after onCreate(). When a user initiate a
+ * conversation from compose, the ConversationActivity creates this fragment and calls
+ * setConversationInfo(), so it happens before onCreate(). However, when the activity is
+ * restored from saved instance state, the ConversationFragment is created automatically by
+ * the fragment, before ConversationActivity has a chance to call setConversationInfo(). Since
+ * the ability to start loading data depends on both methods being called, we need to start
+ * loading when onActivityCreated() is called, which is guaranteed to happen after both.
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Delay showing the message list until the participant list is loaded.
+ mRecyclerView.setVisibility(View.INVISIBLE);
+ mBinding.ensureBound();
+ mBinding.getData().init(getLoaderManager(), mBinding);
+
+ // Build the input manager with all its required dependencies and pass it along to the
+ // compose message view.
+ final ConversationInputManager inputManager = new ConversationInputManager(
+ getActivity(), this, mComposeMessageView, mHost, getFragmentManagerToUse(),
+ mBinding, mComposeMessageView.getDraftDataModel(), savedInstanceState);
+ mComposeMessageView.setInputManager(inputManager);
+ mComposeMessageView.setConversationDataModel(BindingBase.createBindingReference(mBinding));
+ mHost.invalidateActionBar();
+
+ mDraftMessageDataModel =
+ BindingBase.createBindingReference(mComposeMessageView.getDraftDataModel());
+ mDraftMessageDataModel.getData().addListener(this);
+ }
+
+ public void onAttachmentChoosen() {
+ // Attachment has been choosen in the AttachmentChooserActivity, so clear local draft
+ // and reload draft on resume.
+ mClearLocalDraft = true;
+ }
+
+ private int getScrollToMessagePosition() {
+ final Activity activity = getActivity();
+ if (activity == null) {
+ return -1;
+ }
+
+ final Intent intent = activity.getIntent();
+ if (intent == null) {
+ return -1;
+ }
+
+ return intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1);
+ }
+
+ private void clearScrollToMessagePosition() {
+ final Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ final Intent intent = activity.getIntent();
+ if (intent == null) {
+ return;
+ }
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1);
+ }
+
+ private final Handler mHandler = new Handler();
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
+ mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
+ final LinearLayoutManager manager = new LinearLayoutManager(getActivity());
+ manager.setStackFromEnd(true);
+ manager.setReverseLayout(false);
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setLayoutManager(manager);
+ mRecyclerView.setItemAnimator(new DefaultItemAnimator() {
+ private final List<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
+ private PopupTransitionAnimation mPopupTransitionAnimation;
+
+ @Override
+ public boolean animateAdd(final ViewHolder holder) {
+ final ConversationMessageView view =
+ (ConversationMessageView) holder.itemView;
+ final ConversationMessageData data = view.getData();
+ endAnimation(holder);
+ final long timeSinceSend = System.currentTimeMillis() - data.getReceivedTimeStamp();
+ if (data.getReceivedTimeStamp() ==
+ InsertNewMessageAction.getLastSentMessageTimestamp() &&
+ !data.getIsIncoming() &&
+ timeSinceSend < MESSAGE_ANIMATION_MAX_WAIT) {
+ final ConversationMessageBubbleView messageBubble =
+ (ConversationMessageBubbleView) view
+ .findViewById(R.id.message_content);
+ final Rect startRect = UiUtils.getMeasuredBoundsOnScreen(mComposeMessageView);
+ final View composeBubbleView = mComposeMessageView.findViewById(
+ R.id.compose_message_text);
+ final Rect composeBubbleRect =
+ UiUtils.getMeasuredBoundsOnScreen(composeBubbleView);
+ final AttachmentPreview attachmentView =
+ (AttachmentPreview) mComposeMessageView.findViewById(
+ R.id.attachment_draft_view);
+ final Rect attachmentRect = UiUtils.getMeasuredBoundsOnScreen(attachmentView);
+ if (attachmentView.getVisibility() == View.VISIBLE) {
+ startRect.top = attachmentRect.top;
+ } else {
+ startRect.top = composeBubbleRect.top;
+ }
+ startRect.top -= view.getPaddingTop();
+ startRect.bottom =
+ composeBubbleRect.bottom;
+ startRect.left += view.getPaddingRight();
+
+ view.setAlpha(0);
+ mPopupTransitionAnimation = new PopupTransitionAnimation(startRect, view);
+ mPopupTransitionAnimation.setOnStartCallback(new Runnable() {
+ @Override
+ public void run() {
+ final int startWidth = composeBubbleRect.width();
+ attachmentView.onMessageAnimationStart();
+ messageBubble.kickOffMorphAnimation(startWidth,
+ messageBubble.findViewById(R.id.message_text_and_info)
+ .getMeasuredWidth());
+ }
+ });
+ mPopupTransitionAnimation.setOnStopCallback(new Runnable() {
+ @Override
+ public void run() {
+ view.setAlpha(1);
+ }
+ });
+ mPopupTransitionAnimation.startAfterLayoutComplete();
+ mAddAnimations.add(holder);
+ return true;
+ } else {
+ return super.animateAdd(holder);
+ }
+ }
+
+ @Override
+ public void endAnimation(final ViewHolder holder) {
+ if (mAddAnimations.remove(holder)) {
+ holder.itemView.clearAnimation();
+ }
+ super.endAnimation(holder);
+ }
+
+ @Override
+ public void endAnimations() {
+ for (final ViewHolder holder : mAddAnimations) {
+ holder.itemView.clearAnimation();
+ }
+ mAddAnimations.clear();
+ if (mPopupTransitionAnimation != null) {
+ mPopupTransitionAnimation.cancel();
+ }
+ super.endAnimations();
+ }
+ });
+ mRecyclerView.setAdapter(mAdapter);
+
+ if (savedInstanceState != null) {
+ mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY);
+ }
+
+ mConversationComposeDivider = view.findViewById(R.id.conversation_compose_divider);
+ mScrollToDismissThreshold = ViewConfiguration.get(getActivity()).getScaledTouchSlop();
+ mRecyclerView.addOnScrollListener(mListScrollListener);
+ mFastScroller = ConversationFastScroller.addTo(mRecyclerView,
+ UiUtils.isRtlMode() ? ConversationFastScroller.POSITION_LEFT_SIDE :
+ ConversationFastScroller.POSITION_RIGHT_SIDE);
+
+ mComposeMessageView = (ComposeMessageView)
+ view.findViewById(R.id.message_compose_view_container);
+ // Bind the compose message view to the DraftMessageData
+ mComposeMessageView.bind(DataModel.get().createDraftMessageData(
+ mBinding.getData().getConversationId()), this);
+
+ return view;
+ }
+
+ private void scrollToPosition(final int targetPosition, final boolean smoothScroll) {
+ if (smoothScroll) {
+ final int maxScrollDelta = JUMP_SCROLL_THRESHOLD;
+
+ final LinearLayoutManager layoutManager =
+ (LinearLayoutManager) mRecyclerView.getLayoutManager();
+ final int firstVisibleItemPosition =
+ layoutManager.findFirstVisibleItemPosition();
+ final int delta = targetPosition - firstVisibleItemPosition;
+ final int intermediatePosition;
+
+ if (delta > maxScrollDelta) {
+ intermediatePosition = Math.max(0, targetPosition - maxScrollDelta);
+ } else if (delta < -maxScrollDelta) {
+ final int count = layoutManager.getItemCount();
+ intermediatePosition = Math.min(count - 1, targetPosition + maxScrollDelta);
+ } else {
+ intermediatePosition = -1;
+ }
+ if (intermediatePosition != -1) {
+ mRecyclerView.scrollToPosition(intermediatePosition);
+ }
+ mRecyclerView.smoothScrollToPosition(targetPosition);
+ } else {
+ mRecyclerView.scrollToPosition(targetPosition);
+ }
+ }
+
+ private int getScrollPositionFromBottom() {
+ final LinearLayoutManager layoutManager =
+ (LinearLayoutManager) mRecyclerView.getLayoutManager();
+ final int lastVisibleItem =
+ layoutManager.findLastVisibleItemPosition();
+ return Math.max(mAdapter.getItemCount() - 1 - lastVisibleItem, 0);
+ }
+
+ /**
+ * Display a photo using the Photoviewer component.
+ */
+ @Override
+ public void displayPhoto(final Uri photoUri, final Rect imageBounds, final boolean isDraft) {
+ displayPhoto(photoUri, imageBounds, isDraft, mConversationId, getActivity());
+ }
+
+ public static void displayPhoto(final Uri photoUri, final Rect imageBounds,
+ final boolean isDraft, final String conversationId, final Activity activity) {
+ final Uri imagesUri =
+ isDraft ? MessagingContentProvider.buildDraftImagesUri(conversationId)
+ : MessagingContentProvider.buildConversationImagesUri(conversationId);
+ UIIntents.get().launchFullScreenPhotoViewer(
+ activity, photoUri, imageBounds, imagesUri);
+ }
+
+ private void selectMessage(final ConversationMessageView messageView) {
+ selectMessage(messageView, null /* attachment */);
+ }
+
+ private void selectMessage(final ConversationMessageView messageView,
+ final MessagePartData attachment) {
+ mSelectedMessage = messageView;
+ if (mSelectedMessage == null) {
+ mAdapter.setSelectedMessage(null);
+ mHost.dismissActionMode();
+ mSelectedAttachment = null;
+ return;
+ }
+ mSelectedAttachment = attachment;
+ mAdapter.setSelectedMessage(messageView.getData().getMessageId());
+ mHost.startActionMode(mMessageActionModeCallback);
+ }
+
+ @Override
+ public void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mListState != null) {
+ outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState);
+ }
+ mComposeMessageView.saveInputState(outState);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mIncomingDraft == null) {
+ mComposeMessageView.requestDraftMessage(mClearLocalDraft);
+ } else {
+ mComposeMessageView.setDraftMessage(mIncomingDraft);
+ mIncomingDraft = null;
+ }
+ mClearLocalDraft = false;
+
+ // On resume, check if there's a pending request for resuming message compose. This
+ // may happen when the user commits the contact selection for a group conversation and
+ // goes from compose back to the conversation fragment.
+ if (mHost.shouldResumeComposeMessage()) {
+ mComposeMessageView.resumeComposeMessage();
+ }
+
+ setConversationFocus();
+
+ // On resume, invalidate all message views to show the updated timestamp.
+ mAdapter.notifyDataSetChanged();
+
+ LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
+ mConversationSelfIdChangeReceiver,
+ new IntentFilter(UIIntents.CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION));
+ }
+
+ void setConversationFocus() {
+ if (mHost.isActiveAndFocused()) {
+ mBinding.getData().setFocus();
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ if (mHost.getActionMode() != null) {
+ return;
+ }
+
+ inflater.inflate(R.menu.conversation_menu, menu);
+
+ final ConversationData data = mBinding.getData();
+
+ // Disable the "people & options" item if we haven't loaded participants yet.
+ menu.findItem(R.id.action_people_and_options).setEnabled(data.getParticipantsLoaded());
+
+ // See if we can show add contact action.
+ final ParticipantData participant = data.getOtherParticipant();
+ final boolean addContactActionVisible = (participant != null
+ && TextUtils.isEmpty(participant.getLookupKey()));
+ menu.findItem(R.id.action_add_contact).setVisible(addContactActionVisible);
+
+ // See if we should show archive or unarchive.
+ final boolean isArchived = data.getIsArchived();
+ menu.findItem(R.id.action_archive).setVisible(!isArchived);
+ menu.findItem(R.id.action_unarchive).setVisible(isArchived);
+
+ // Conditionally enable the phone call button.
+ final boolean supportCallAction = (PhoneUtils.getDefault().isVoiceCapable() &&
+ data.getParticipantPhoneNumber() != null);
+ menu.findItem(R.id.action_call).setVisible(supportCallAction);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_people_and_options:
+ Assert.isTrue(mBinding.getData().getParticipantsLoaded());
+ UIIntents.get().launchPeopleAndOptionsActivity(getActivity(), mConversationId);
+ return true;
+
+ case R.id.action_call:
+ final String phoneNumber = mBinding.getData().getParticipantPhoneNumber();
+ Assert.notNull(phoneNumber);
+ final View targetView = getActivity().findViewById(R.id.action_call);
+ Point centerPoint;
+ if (targetView != null) {
+ final int screenLocation[] = new int[2];
+ targetView.getLocationOnScreen(screenLocation);
+ final int centerX = screenLocation[0] + targetView.getWidth() / 2;
+ final int centerY = screenLocation[1] + targetView.getHeight() / 2;
+ centerPoint = new Point(centerX, centerY);
+ } else {
+ // In the overflow menu, just use the center of the screen.
+ final Display display = getActivity().getWindowManager().getDefaultDisplay();
+ centerPoint = new Point(display.getWidth() / 2, display.getHeight() / 2);
+ }
+ UIIntents.get().launchPhoneCallActivity(getActivity(), phoneNumber, centerPoint);
+ return true;
+
+ case R.id.action_archive:
+ mBinding.getData().archiveConversation(mBinding);
+ closeConversation(mConversationId);
+ return true;
+
+ case R.id.action_unarchive:
+ mBinding.getData().unarchiveConversation(mBinding);
+ return true;
+
+ case R.id.action_settings:
+ return true;
+
+ case R.id.action_add_contact:
+ final ParticipantData participant = mBinding.getData().getOtherParticipant();
+ Assert.notNull(participant);
+ final String destination = participant.getNormalizedDestination();
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(participant);
+ (new AddContactsConfirmationDialog(getActivity(), avatarUri, destination)).show();
+ return true;
+
+ case R.id.action_delete:
+ if (isReadyForAction()) {
+ new AlertDialog.Builder(getActivity())
+ .setTitle(getResources().getQuantityString(
+ R.plurals.delete_conversations_confirmation_dialog_title, 1))
+ .setPositiveButton(R.string.delete_conversation_confirmation_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ deleteConversation();
+ }
+ })
+ .setNegativeButton(R.string.delete_conversation_decline_button, null)
+ .show();
+ } else {
+ warnOfMissingActionConditions(false /*sending*/,
+ null /*commandToRunAfterActionConditionResolved*/);
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * {@inheritDoc} from ConversationDataListener
+ */
+ @Override
+ public void onConversationMessagesCursorUpdated(final ConversationData data,
+ final Cursor cursor, final ConversationMessageData newestMessage,
+ final boolean isSync) {
+ mBinding.ensureBound(data);
+
+ // This needs to be determined before swapping cursor, which may change the scroll state.
+ final boolean scrolledToBottom = isScrolledToBottom();
+ final int positionFromBottom = getScrollPositionFromBottom();
+
+ // If participants not loaded, assume 1:1 since that's the 99% case
+ final boolean oneOnOne =
+ !data.getParticipantsLoaded() || data.getOtherParticipant() != null;
+ mAdapter.setOneOnOne(oneOnOne, false /* invalidate */);
+
+ // Ensure that the action bar is updated with the current data.
+ invalidateOptionsMenu();
+ final Cursor oldCursor = mAdapter.swapCursor(cursor);
+
+ if (cursor != null && oldCursor == null) {
+ if (mListState != null) {
+ mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
+ // RecyclerView restores scroll states without triggering scroll change events, so
+ // we need to manually ensure that they are correctly handled.
+ mListScrollListener.onScrolled(mRecyclerView, 0, 0);
+ }
+ }
+
+ if (isSync) {
+ // This is a message sync. Syncing messages changes cursor item count, which would
+ // implicitly change RV's scroll position. We'd like the RV to keep scrolled to the same
+ // relative position from the bottom (because RV is stacked from bottom), so that it
+ // stays relatively put as we sync.
+ final int position = Math.max(mAdapter.getItemCount() - 1 - positionFromBottom, 0);
+ scrollToPosition(position, false /* smoothScroll */);
+ } else if (newestMessage != null) {
+ // Show a snack bar notification if we are not scrolled to the bottom and the new
+ // message is an incoming message.
+ if (!scrolledToBottom && newestMessage.getIsIncoming()) {
+ // If the conversation activity is started but not resumed (if another dialog
+ // activity was in the foregrond), we will show a system notification instead of
+ // the snack bar.
+ if (mBinding.getData().isFocused()) {
+ UiUtils.showSnackBarWithCustomAction(getActivity(),
+ getView().getRootView(),
+ getString(R.string.in_conversation_notify_new_message_text),
+ SnackBar.Action.createCustomAction(new Runnable() {
+ @Override
+ public void run() {
+ scrollToBottom(true /* smoothScroll */);
+ mComposeMessageView.hideAllComposeInputs(false /* animate */);
+ }
+ },
+ getString(R.string.in_conversation_notify_new_message_action)),
+ null /* interactions */,
+ SnackBar.Placement.above(mComposeMessageView));
+ }
+ } else {
+ // We are either already scrolled to the bottom or this is an outgoing message,
+ // scroll to the bottom to reveal it.
+ // Don't smooth scroll if we were already at the bottom; instead, we scroll
+ // immediately so RecyclerView's view animation will take place.
+ scrollToBottom(!scrolledToBottom);
+ }
+ }
+
+ if (cursor != null) {
+ mHost.onConversationMessagesUpdated(cursor.getCount());
+
+ // Are we coming from a widget click where we're told to scroll to a particular item?
+ final int scrollToPos = getScrollToMessagePosition();
+ if (scrollToPos >= 0) {
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(LogUtil.BUGLE_TAG, "onConversationMessagesCursorUpdated " +
+ " scrollToPos: " + scrollToPos +
+ " cursorCount: " + cursor.getCount());
+ }
+ scrollToPosition(scrollToPos, true /*smoothScroll*/);
+ clearScrollToMessagePosition();
+ }
+ }
+
+ mHost.invalidateActionBar();
+ }
+
+ /**
+ * {@inheritDoc} from ConversationDataListener
+ */
+ @Override
+ public void onConversationMetadataUpdated(final ConversationData conversationData) {
+ mBinding.ensureBound(conversationData);
+
+ if (mSelectedMessage != null && mSelectedAttachment != null) {
+ // We may have just sent a message and the temp attachment we selected is now gone.
+ // and it was replaced with some new attachment. Since we don't know which one it
+ // is we shouldn't reselect it (unless there is just one) In the multi-attachment
+ // case we would just deselect the message and allow the user to reselect, otherwise we
+ // may act on old temp data and may crash.
+ final List<MessagePartData> currentAttachments = mSelectedMessage.getData().getAttachments();
+ if (currentAttachments.size() == 1) {
+ mSelectedAttachment = currentAttachments.get(0);
+ } else if (!currentAttachments.contains(mSelectedAttachment)) {
+ selectMessage(null);
+ }
+ }
+ // Ensure that the action bar is updated with the current data.
+ invalidateOptionsMenu();
+ mHost.onConversationMetadataUpdated();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ public void setConversationInfo(final Context context, final String conversationId,
+ final MessageData draftData) {
+ // TODO: Eventually I would like the Factory to implement
+ // Factory.get().bindConversationData(mBinding, getActivity(), this, conversationId));
+ if (!mBinding.isBound()) {
+ mConversationId = conversationId;
+ mIncomingDraft = draftData;
+ mBinding.bind(DataModel.get().createConversationData(context, this, conversationId));
+ } else {
+ Assert.isTrue(TextUtils.equals(mBinding.getData().getConversationId(), conversationId));
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // Unbind all the views that we bound to data
+ if (mComposeMessageView != null) {
+ mComposeMessageView.unbind();
+ }
+
+ // And unbind this fragment from its data
+ mBinding.unbind();
+ mConversationId = null;
+ }
+
+ void suppressWriteDraft() {
+ mSuppressWriteDraft = true;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mComposeMessageView != null && !mSuppressWriteDraft) {
+ mComposeMessageView.writeDraftMessage();
+ }
+ mSuppressWriteDraft = false;
+ mBinding.getData().unsetFocus();
+ mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
+
+ LocalBroadcastManager.getInstance(getActivity())
+ .unregisterReceiver(mConversationSelfIdChangeReceiver);
+ }
+
+ @Override
+ public void onConfigurationChanged(final Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mRecyclerView.getItemAnimator().endAnimations();
+ }
+
+ // TODO: Remove isBound and replace it with ensureBound after b/15704674.
+ public boolean isBound() {
+ return mBinding.isBound();
+ }
+
+ private FragmentManager getFragmentManagerToUse() {
+ return OsUtil.isAtLeastJB_MR1() ? getChildFragmentManager() : getFragmentManager();
+ }
+
+ public MediaPicker getMediaPicker() {
+ return (MediaPicker) getFragmentManagerToUse().findFragmentByTag(
+ MediaPicker.FRAGMENT_TAG);
+ }
+
+ @Override
+ public void sendMessage(final MessageData message) {
+ if (isReadyForAction()) {
+ if (ensureKnownRecipients()) {
+ // Merge the caption text from attachments into the text body of the messages
+ message.consolidateText();
+
+ mBinding.getData().sendMessage(mBinding, message);
+ mComposeMessageView.resetMediaPickerState();
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: conv participants not loaded");
+ }
+ } else {
+ warnOfMissingActionConditions(true /*sending*/,
+ new Runnable() {
+ @Override
+ public void run() {
+ sendMessage(message);
+ }
+ });
+ }
+ }
+
+ public void setHost(final ConversationFragmentHost host) {
+ mHost = host;
+ }
+
+ public String getConversationName() {
+ return mBinding.getData().getConversationName();
+ }
+
+ @Override
+ public void onComposeEditTextFocused() {
+ mHost.onStartComposeMessage();
+ }
+
+ @Override
+ public void onAttachmentsCleared() {
+ // When attachments are removed, reset transient media picker state such as image selection.
+ mComposeMessageView.resetMediaPickerState();
+ }
+
+ /**
+ * Called to check if all conditions are nominal and a "go" for some action, such as deleting
+ * a message, that requires this app to be the default app. This is also a precondition
+ * required for sending a draft.
+ * @return true if all conditions are nominal and we're ready to send a message
+ */
+ @Override
+ public boolean isReadyForAction() {
+ return UiUtils.isReadyForAction();
+ }
+
+ /**
+ * When there's some condition that prevents an operation, such as sending a message,
+ * call warnOfMissingActionConditions to put up a snackbar and allow the user to repair
+ * that condition.
+ * @param sending - true if we're called during a sending operation
+ * @param commandToRunAfterActionConditionResolved - a runnable to run after the user responds
+ * positively to the condition prompt and resolves the condition. If null,
+ * the user will be shown a toast to tap the send button again.
+ */
+ @Override
+ public void warnOfMissingActionConditions(final boolean sending,
+ final Runnable commandToRunAfterActionConditionResolved) {
+ if (mChangeDefaultSmsAppHelper == null) {
+ mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
+ }
+ mChangeDefaultSmsAppHelper.warnOfMissingActionConditions(sending,
+ commandToRunAfterActionConditionResolved, mComposeMessageView,
+ getView().getRootView(),
+ getActivity(), this);
+ }
+
+ private boolean ensureKnownRecipients() {
+ final ConversationData conversationData = mBinding.getData();
+
+ if (!conversationData.getParticipantsLoaded()) {
+ // We can't tell yet whether or not we have an unknown recipient
+ return false;
+ }
+
+ final ConversationParticipantsData participants = conversationData.getParticipants();
+ for (final ParticipantData participant : participants) {
+
+
+ if (participant.isUnknownSender()) {
+ UiUtils.showToast(R.string.unknown_sender);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void retryDownload(final String messageId) {
+ if (isReadyForAction()) {
+ mBinding.getData().downloadMessage(mBinding, messageId);
+ } else {
+ warnOfMissingActionConditions(false /*sending*/,
+ null /*commandToRunAfterActionConditionResolved*/);
+ }
+ }
+
+ public void retrySend(final String messageId) {
+ if (isReadyForAction()) {
+ if (ensureKnownRecipients()) {
+ mBinding.getData().resendMessage(mBinding, messageId);
+ }
+ } else {
+ warnOfMissingActionConditions(true /*sending*/,
+ new Runnable() {
+ @Override
+ public void run() {
+ retrySend(messageId);
+ }
+
+ });
+ }
+ }
+
+ void deleteMessage(final String messageId) {
+ if (isReadyForAction()) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.delete_message_confirmation_dialog_title)
+ .setMessage(R.string.delete_message_confirmation_dialog_text)
+ .setPositiveButton(R.string.delete_message_confirmation_button,
+ new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ mBinding.getData().deleteMessage(mBinding, messageId);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null);
+ if (OsUtil.isAtLeastJB_MR1()) {
+ builder.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(final DialogInterface dialog) {
+ mHost.dismissActionMode();
+ }
+ });
+ } else {
+ builder.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(final DialogInterface dialog) {
+ mHost.dismissActionMode();
+ }
+ });
+ }
+ builder.create().show();
+ } else {
+ warnOfMissingActionConditions(false /*sending*/,
+ null /*commandToRunAfterActionConditionResolved*/);
+ mHost.dismissActionMode();
+ }
+ }
+
+ public void deleteConversation() {
+ if (isReadyForAction()) {
+ final Context context = getActivity();
+ mBinding.getData().deleteConversation(mBinding);
+ closeConversation(mConversationId);
+ } else {
+ warnOfMissingActionConditions(false /*sending*/,
+ null /*commandToRunAfterActionConditionResolved*/);
+ }
+ }
+
+ @Override
+ public void closeConversation(final String conversationId) {
+ if (TextUtils.equals(conversationId, mConversationId)) {
+ mHost.onFinishCurrentConversation();
+ // TODO: Explicitly transition to ConversationList (or just go back)?
+ }
+ }
+
+ @Override
+ public void onConversationParticipantDataLoaded(final ConversationData data) {
+ mBinding.ensureBound(data);
+ if (mBinding.getData().getParticipantsLoaded()) {
+ final boolean oneOnOne = mBinding.getData().getOtherParticipant() != null;
+ mAdapter.setOneOnOne(oneOnOne, true /* invalidate */);
+
+ // refresh the options menu which will enable the "people & options" item.
+ invalidateOptionsMenu();
+
+ mHost.invalidateActionBar();
+
+ mRecyclerView.setVisibility(View.VISIBLE);
+ mHost.onConversationParticipantDataLoaded
+ (mBinding.getData().getNumberOfParticipantsExcludingSelf());
+ }
+ }
+
+ @Override
+ public void onSubscriptionListDataLoaded(final ConversationData data) {
+ mBinding.ensureBound(data);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void promptForSelfPhoneNumber() {
+ if (mComposeMessageView != null) {
+ // Avoid bug in system which puts soft keyboard over dialog after orientation change
+ ImeUtil.hideSoftInput(getActivity(), mComposeMessageView);
+ }
+
+ final FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
+ final EnterSelfPhoneNumberDialog dialog = EnterSelfPhoneNumberDialog
+ .newInstance(getConversationSelfSubId());
+ dialog.setTargetFragment(this, 0/*requestCode*/);
+ dialog.show(ft, null/*tag*/);
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ if (mChangeDefaultSmsAppHelper == null) {
+ mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
+ }
+ mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(requestCode, resultCode, null);
+ }
+
+ public boolean hasMessages() {
+ return mAdapter != null && mAdapter.getItemCount() > 0;
+ }
+
+ public boolean onBackPressed() {
+ if (mComposeMessageView.onBackPressed()) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onNavigationUpPressed() {
+ return mComposeMessageView.onNavigationUpPressed();
+ }
+
+ @Override
+ public boolean onAttachmentClick(final ConversationMessageView messageView,
+ final MessagePartData attachment, final Rect imageBounds, final boolean longPress) {
+ if (longPress) {
+ selectMessage(messageView, attachment);
+ return true;
+ } else if (messageView.getData().getOneClickResendMessage()) {
+ handleMessageClick(messageView);
+ return true;
+ }
+
+ if (attachment.isImage()) {
+ displayPhoto(attachment.getContentUri(), imageBounds, false /* isDraft */);
+ }
+
+ if (attachment.isVCard()) {
+ UIIntents.get().launchVCardDetailActivity(getActivity(), attachment.getContentUri());
+ }
+
+ return false;
+ }
+
+ private void handleMessageClick(final ConversationMessageView messageView) {
+ if (messageView != mSelectedMessage) {
+ final ConversationMessageData data = messageView.getData();
+ final boolean isReadyToSend = isReadyForAction();
+ if (data.getOneClickResendMessage()) {
+ // Directly resend the message on tap if it's failed
+ retrySend(data.getMessageId());
+ selectMessage(null);
+ } else if (data.getShowResendMessage() && isReadyToSend) {
+ // Select the message to show the resend/download/delete options
+ selectMessage(messageView);
+ } else if (data.getShowDownloadMessage() && isReadyToSend) {
+ // Directly download the message on tap
+ retryDownload(data.getMessageId());
+ } else {
+ // Let the toast from warnOfMissingActionConditions show and skip
+ // selecting
+ warnOfMissingActionConditions(false /*sending*/,
+ null /*commandToRunAfterActionConditionResolved*/);
+ selectMessage(null);
+ }
+ } else {
+ selectMessage(null);
+ }
+ }
+
+ private static class AttachmentToSave {
+ public final Uri uri;
+ public final String contentType;
+ public Uri persistedUri;
+
+ AttachmentToSave(final Uri uri, final String contentType) {
+ this.uri = uri;
+ this.contentType = contentType;
+ }
+ }
+
+ public static class SaveAttachmentTask extends SafeAsyncTask<Void, Void, Void> {
+ private final Context mContext;
+ private final List<AttachmentToSave> mAttachmentsToSave = new ArrayList<>();
+
+ public SaveAttachmentTask(final Context context, final Uri contentUri,
+ final String contentType) {
+ mContext = context;
+ addAttachmentToSave(contentUri, contentType);
+ }
+
+ public SaveAttachmentTask(final Context context) {
+ mContext = context;
+ }
+
+ public void addAttachmentToSave(final Uri contentUri, final String contentType) {
+ mAttachmentsToSave.add(new AttachmentToSave(contentUri, contentType));
+ }
+
+ public int getAttachmentCount() {
+ return mAttachmentsToSave.size();
+ }
+
+ @Override
+ protected Void doInBackgroundTimed(final Void... arg) {
+ final File appDir = new File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES),
+ mContext.getResources().getString(R.string.app_name));
+ final File downloadDir = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS);
+ for (final AttachmentToSave attachment : mAttachmentsToSave) {
+ final boolean isImageOrVideo = ContentType.isImageType(attachment.contentType)
+ || ContentType.isVideoType(attachment.contentType);
+ attachment.persistedUri = UriUtil.persistContent(attachment.uri,
+ isImageOrVideo ? appDir : downloadDir, attachment.contentType);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(final Void result) {
+ int failCount = 0;
+ int imageCount = 0;
+ int videoCount = 0;
+ int otherCount = 0;
+ for (final AttachmentToSave attachment : mAttachmentsToSave) {
+ if (attachment.persistedUri == null) {
+ failCount++;
+ continue;
+ }
+
+ // Inform MediaScanner about the new file
+ final Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ scanFileIntent.setData(attachment.persistedUri);
+ mContext.sendBroadcast(scanFileIntent);
+
+ if (ContentType.isImageType(attachment.contentType)) {
+ imageCount++;
+ } else if (ContentType.isVideoType(attachment.contentType)) {
+ videoCount++;
+ } else {
+ otherCount++;
+ // Inform DownloadManager of the file so it will show in the "downloads" app
+ final DownloadManager downloadManager =
+ (DownloadManager) mContext.getSystemService(
+ Context.DOWNLOAD_SERVICE);
+ final String filePath = attachment.persistedUri.getPath();
+ final File file = new File(filePath);
+
+ if (file.exists()) {
+ downloadManager.addCompletedDownload(
+ file.getName() /* title */,
+ mContext.getString(
+ R.string.attachment_file_description) /* description */,
+ true /* isMediaScannerScannable */,
+ attachment.contentType,
+ file.getAbsolutePath(),
+ file.length(),
+ false /* showNotification */);
+ }
+ }
+ }
+
+ String message;
+ if (failCount > 0) {
+ message = mContext.getResources().getQuantityString(
+ R.plurals.attachment_save_error, failCount, failCount);
+ } else {
+ int messageId = R.plurals.attachments_saved;
+ if (otherCount > 0) {
+ if (imageCount + videoCount == 0) {
+ messageId = R.plurals.attachments_saved_to_downloads;
+ }
+ } else {
+ if (videoCount == 0) {
+ messageId = R.plurals.photos_saved_to_album;
+ } else if (imageCount == 0) {
+ messageId = R.plurals.videos_saved_to_album;
+ } else {
+ messageId = R.plurals.attachments_saved_to_album;
+ }
+ }
+ final String appName = mContext.getResources().getString(R.string.app_name);
+ final int count = imageCount + videoCount + otherCount;
+ message = mContext.getResources().getQuantityString(
+ messageId, count, count, appName);
+ }
+ UiUtils.showToastAtBottom(message);
+ }
+ }
+
+ private void invalidateOptionsMenu() {
+ final Activity activity = getActivity();
+ // TODO: Add the supportInvalidateOptionsMenu call to the host activity.
+ if (activity == null || !(activity instanceof BugleActionBarActivity)) {
+ return;
+ }
+ ((BugleActionBarActivity) activity).supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void setOptionsMenuVisibility(final boolean visible) {
+ setHasOptionsMenu(visible);
+ }
+
+ @Override
+ public int getConversationSelfSubId() {
+ final String selfParticipantId = mComposeMessageView.getConversationSelfId();
+ final ParticipantData self = mBinding.getData().getSelfParticipantById(selfParticipantId);
+ // If the self id or the self participant data hasn't been loaded yet, fallback to
+ // the default setting.
+ return self == null ? ParticipantData.DEFAULT_SELF_SUB_ID : self.getSubId();
+ }
+
+ @Override
+ public void invalidateActionBar() {
+ mHost.invalidateActionBar();
+ }
+
+ @Override
+ public void dismissActionMode() {
+ mHost.dismissActionMode();
+ }
+
+ @Override
+ public void selectSim(final SubscriptionListEntry subscriptionData) {
+ mComposeMessageView.selectSim(subscriptionData);
+ mHost.onStartComposeMessage();
+ }
+
+ @Override
+ public void onStartComposeMessage() {
+ mHost.onStartComposeMessage();
+ }
+
+ @Override
+ public SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
+ final String selfParticipantId, final boolean excludeDefault) {
+ // TODO: ConversationMessageView is the only one using this. We should probably
+ // inject this into the view during binding in the ConversationMessageAdapter.
+ return mBinding.getData().getSubscriptionEntryForSelfParticipant(selfParticipantId,
+ excludeDefault);
+ }
+
+ @Override
+ public SimSelectorView getSimSelectorView() {
+ return (SimSelectorView) getView().findViewById(R.id.sim_selector);
+ }
+
+ @Override
+ public MediaPicker createMediaPicker() {
+ return new MediaPicker(getActivity());
+ }
+
+ @Override
+ public void notifyOfAttachmentLoadFailed() {
+ UiUtils.showToastAtBottom(R.string.attachment_load_failed_dialog_message);
+ }
+
+ @Override
+ public void warnOfExceedingMessageLimit(final boolean sending, final boolean tooManyVideos) {
+ warnOfExceedingMessageLimit(sending, mComposeMessageView, mConversationId,
+ getActivity(), tooManyVideos);
+ }
+
+ public static void warnOfExceedingMessageLimit(final boolean sending,
+ final ComposeMessageView composeMessageView, final String conversationId,
+ final Activity activity, final boolean tooManyVideos) {
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(activity)
+ .setTitle(R.string.mms_attachment_limit_reached);
+
+ if (sending) {
+ if (tooManyVideos) {
+ builder.setMessage(R.string.video_attachment_limit_exceeded_when_sending);
+ } else {
+ builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_sending)
+ .setNegativeButton(R.string.attachment_limit_reached_send_anyway,
+ new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int which) {
+ composeMessageView.sendMessageIgnoreMessageSizeLimit();
+ }
+ });
+ }
+ builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ showAttachmentChooser(conversationId, activity);
+ }});
+ } else {
+ builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_composing)
+ .setPositiveButton(android.R.string.ok, null);
+ }
+ builder.show();
+ }
+
+ @Override
+ public void showAttachmentChooser() {
+ showAttachmentChooser(mConversationId, getActivity());
+ }
+
+ public static void showAttachmentChooser(final String conversationId,
+ final Activity activity) {
+ UIIntents.get().launchAttachmentChooserActivity(activity,
+ conversationId, REQUEST_CHOOSE_ATTACHMENTS);
+ }
+
+ private void updateActionAndStatusBarColor(final ActionBar actionBar) {
+ final int themeColor = ConversationDrawables.get().getConversationThemeColor();
+ actionBar.setBackgroundDrawable(new ColorDrawable(themeColor));
+ UiUtils.setStatusBarColor(getActivity(), themeColor);
+ }
+
+ public void updateActionBar(final ActionBar actionBar) {
+ if (mComposeMessageView == null || !mComposeMessageView.updateActionBar(actionBar)) {
+ updateActionAndStatusBarColor(actionBar);
+ // We update this regardless of whether or not the action bar is showing so that we
+ // don't get a race when it reappears.
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ // Reset the back arrow to its default
+ actionBar.setHomeAsUpIndicator(0);
+ View customView = actionBar.getCustomView();
+ if (customView == null || customView.getId() != R.id.conversation_title_container) {
+ final LayoutInflater inflator = (LayoutInflater)
+ getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ customView = inflator.inflate(R.layout.action_bar_conversation_name, null);
+ customView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ onBackPressed();
+ }
+ });
+ actionBar.setCustomView(customView);
+ }
+
+ final TextView conversationNameView =
+ (TextView) customView.findViewById(R.id.conversation_title);
+ final String conversationName = getConversationName();
+ if (!TextUtils.isEmpty(conversationName)) {
+ // RTL : To format conversation title if it happens to be phone numbers.
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final String formattedName = bidiFormatter.unicodeWrap(
+ UiUtils.commaEllipsize(
+ conversationName,
+ conversationNameView.getPaint(),
+ conversationNameView.getWidth(),
+ getString(R.string.plus_one),
+ getString(R.string.plus_n)).toString(),
+ TextDirectionHeuristicsCompat.LTR);
+ conversationNameView.setText(formattedName);
+ // In case phone numbers are mixed in the conversation name, we need to vocalize it.
+ final String vocalizedConversationName =
+ AccessibilityUtil.getVocalizedPhoneNumber(getResources(), conversationName);
+ conversationNameView.setContentDescription(vocalizedConversationName);
+ getActivity().setTitle(conversationName);
+ } else {
+ final String appName = getString(R.string.app_name);
+ conversationNameView.setText(appName);
+ getActivity().setTitle(appName);
+ }
+
+ // When conversation is showing and media picker is not showing, then hide the action
+ // bar only when we are in landscape mode, with IME open.
+ if (mHost.isImeOpen() && UiUtils.isLandscapeMode()) {
+ actionBar.hide();
+ } else {
+ actionBar.show();
+ }
+ }
+ }
+
+ @Override
+ public boolean shouldShowSubjectEditor() {
+ return true;
+ }
+
+ @Override
+ public boolean shouldHideAttachmentsWhenSimSelectorShown() {
+ return false;
+ }
+
+ @Override
+ public void showHideSimSelector(final boolean show) {
+ // no-op for now
+ }
+
+ @Override
+ public int getSimSelectorItemLayoutId() {
+ return R.layout.sim_selector_item_view;
+ }
+
+ @Override
+ public Uri getSelfSendButtonIconUri() {
+ return null; // use default button icon uri
+ }
+
+ @Override
+ public int overrideCounterColor() {
+ return -1; // don't override the color
+ }
+
+ @Override
+ public void onAttachmentsChanged(final boolean haveAttachments) {
+ // no-op for now
+ }
+
+ @Override
+ public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
+ mDraftMessageDataModel.ensureBound(data);
+ // We're specifically only interested in ATTACHMENTS_CHANGED from the widget. Ignore
+ // other changes. When the widget changes an attachment, we need to reload the draft.
+ if (changeFlags ==
+ (DraftMessageData.WIDGET_CHANGED | DraftMessageData.ATTACHMENTS_CHANGED)) {
+ mClearLocalDraft = true; // force a reload of the draft in onResume
+ }
+ }
+
+ @Override
+ public void onDraftAttachmentLimitReached(final DraftMessageData data) {
+ // no-op for now
+ }
+
+ @Override
+ public void onDraftAttachmentLoadFailed() {
+ // no-op for now
+ }
+
+ @Override
+ public int getAttachmentsClearedFlags() {
+ return DraftMessageData.ATTACHMENTS_CHANGED;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationInput.java b/src/com/android/messaging/ui/conversation/ConversationInput.java
new file mode 100644
index 0000000..bf60aa8
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationInput.java
@@ -0,0 +1,103 @@
+/*
+ * 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.conversation;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+
+/**
+ * The base class for a method of user input, e.g. media picker.
+ */
+public abstract class ConversationInput {
+ /**
+ * The host component where all input components are contained. This is typically the
+ * conversation fragment but may be mocked in test code.
+ */
+ public interface ConversationInputBase {
+ boolean showHideInternal(final ConversationInput target, final boolean show,
+ final boolean animate);
+ String getInputStateKey(final ConversationInput input);
+ void beginUpdate();
+ void handleOnShow(final ConversationInput target);
+ void endUpdate();
+ }
+
+ protected boolean mShowing;
+ protected ConversationInputBase mConversationInputBase;
+
+ public abstract boolean show(boolean animate);
+ public abstract boolean hide(boolean animate);
+
+ public ConversationInput(ConversationInputBase baseHost, final boolean isShowing) {
+ mConversationInputBase = baseHost;
+ mShowing = isShowing;
+ }
+
+ public boolean onBackPressed() {
+ if (mShowing) {
+ mConversationInputBase.showHideInternal(this, false /* show */, true /* animate */);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onNavigationUpPressed() {
+ return false;
+ }
+
+ /**
+ * Toggle the visibility of this view.
+ * @param animate
+ * @return true if the view is now shown, false if it now hidden
+ */
+ public boolean toggle(final boolean animate) {
+ mConversationInputBase.showHideInternal(this, !mShowing /* show */, true /* animate */);
+ return mShowing;
+ }
+
+ public void saveState(final Bundle savedState) {
+ savedState.putBoolean(mConversationInputBase.getInputStateKey(this), mShowing);
+ }
+
+ public void restoreState(final Bundle savedState) {
+ // Things are hidden by default, so only handle show.
+ if (savedState.getBoolean(mConversationInputBase.getInputStateKey(this))) {
+ mConversationInputBase.showHideInternal(this, true /* show */, false /* animate */);
+ }
+ }
+
+ public boolean updateActionBar(final ActionBar actionBar) {
+ return false;
+ }
+
+ /**
+ * Update our visibility flag in response to visibility change, both for actions
+ * initiated by this class (through the show/hide methods), and for external changes
+ * tracked by event listeners (e.g. ImeStateObserver, MediaPickerListener). As part of
+ * handling an input showing, we will hide all other inputs to ensure they are mutually
+ * exclusive.
+ */
+ protected void onVisibilityChanged(final boolean visible) {
+ if (mShowing != visible) {
+ mConversationInputBase.beginUpdate();
+ mShowing = visible;
+ if (visible) {
+ mConversationInputBase.handleOnShow(this);
+ }
+ mConversationInputBase.endUpdate();
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationInputManager.java b/src/com/android/messaging/ui/conversation/ConversationInputManager.java
new file mode 100644
index 0000000..e10abe7
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationInputManager.java
@@ -0,0 +1,550 @@
+/*
+ * 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.conversation;
+
+import android.app.FragmentManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.widget.EditText;
+
+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.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.ui.mediapicker.MediaPicker;
+import com.android.messaging.ui.mediapicker.MediaPicker.MediaPickerListener;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.ImeUtil.ImeStateHost;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Collection;
+
+/**
+ * Manages showing/hiding/persisting different mutually exclusive UI components nested in
+ * ConversationFragment that take user inputs, i.e. media picker, SIM selector and
+ * IME keyboard (the IME keyboard is not owned by Bugle, but we try to model it the same way
+ * as the other components).
+ */
+public class ConversationInputManager implements ConversationInput.ConversationInputBase {
+ /**
+ * The host component where all input components are contained. This is typically the
+ * conversation fragment but may be mocked in test code.
+ */
+ public interface ConversationInputHost extends DraftMessageSubscriptionDataProvider {
+ void invalidateActionBar();
+ void setOptionsMenuVisibility(boolean visible);
+ void dismissActionMode();
+ void selectSim(SubscriptionListEntry subscriptionData);
+ void onStartComposeMessage();
+ SimSelectorView getSimSelectorView();
+ MediaPicker createMediaPicker();
+ void showHideSimSelector(boolean show);
+ int getSimSelectorItemLayoutId();
+ }
+
+ /**
+ * The "sink" component where all inputs components will direct the user inputs to. This is
+ * typically the ComposeMessageView but may be mocked in test code.
+ */
+ public interface ConversationInputSink {
+ void onMediaItemsSelected(Collection<MessagePartData> items);
+ void onMediaItemsUnselected(MessagePartData item);
+ void onPendingAttachmentAdded(PendingAttachmentData pendingItem);
+ void resumeComposeMessage();
+ EditText getComposeEditText();
+ void setAccessibility(boolean enabled);
+ }
+
+ private final ConversationInputHost mHost;
+ private final ConversationInputSink mSink;
+
+ /** Dependencies injected from the host during construction */
+ private final FragmentManager mFragmentManager;
+ private final Context mContext;
+ private final ImeStateHost mImeStateHost;
+ private final ImmutableBindingRef<ConversationData> mConversationDataModel;
+ private final ImmutableBindingRef<DraftMessageData> mDraftDataModel;
+
+ private final ConversationInput[] mInputs;
+ private final ConversationMediaPicker mMediaInput;
+ private final ConversationSimSelector mSimInput;
+ private final ConversationImeKeyboard mImeInput;
+ private int mUpdateCount;
+
+ private final ImeUtil.ImeStateObserver mImeStateObserver = new ImeUtil.ImeStateObserver() {
+ @Override
+ public void onImeStateChanged(final boolean imeOpen) {
+ mImeInput.onVisibilityChanged(imeOpen);
+ }
+ };
+
+ private final ConversationDataListener mDataListener = new SimpleConversationDataListener() {
+ @Override
+ public void onConversationParticipantDataLoaded(ConversationData data) {
+ mConversationDataModel.ensureBound(data);
+ }
+
+ @Override
+ public void onSubscriptionListDataLoaded(ConversationData data) {
+ mConversationDataModel.ensureBound(data);
+ mSimInput.onSubscriptionListDataLoaded(data.getSubscriptionListData());
+ }
+ };
+
+ public ConversationInputManager(
+ final Context context,
+ final ConversationInputHost host,
+ final ConversationInputSink sink,
+ final ImeStateHost imeStateHost,
+ final FragmentManager fm,
+ final BindingBase<ConversationData> conversationDataModel,
+ final BindingBase<DraftMessageData> draftDataModel,
+ final Bundle savedState) {
+ mHost = host;
+ mSink = sink;
+ mFragmentManager = fm;
+ mContext = context;
+ mImeStateHost = imeStateHost;
+ mConversationDataModel = BindingBase.createBindingReference(conversationDataModel);
+ mDraftDataModel = BindingBase.createBindingReference(draftDataModel);
+
+ // Register listeners on dependencies.
+ mImeStateHost.registerImeStateObserver(mImeStateObserver);
+ mConversationDataModel.getData().addConversationDataListener(mDataListener);
+
+ // Initialize the inputs
+ mMediaInput = new ConversationMediaPicker(this);
+ mSimInput = new SimSelector(this);
+ mImeInput = new ConversationImeKeyboard(this, mImeStateHost.isImeOpen());
+ mInputs = new ConversationInput[] { mMediaInput, mSimInput, mImeInput };
+
+ if (savedState != null) {
+ for (int i = 0; i < mInputs.length; i++) {
+ mInputs[i].restoreState(savedState);
+ }
+ }
+ updateHostOptionsMenu();
+ }
+
+ public void onDetach() {
+ mImeStateHost.unregisterImeStateObserver(mImeStateObserver);
+ // Don't need to explicitly unregister for data model events. It will unregister all
+ // listeners automagically on unbind.
+ }
+
+ public void onSaveInputState(final Bundle savedState) {
+ for (int i = 0; i < mInputs.length; i++) {
+ mInputs[i].saveState(savedState);
+ }
+ }
+
+ @Override
+ public String getInputStateKey(final ConversationInput input) {
+ return input.getClass().getCanonicalName() + "_savedstate_";
+ }
+
+ public boolean onBackPressed() {
+ for (int i = 0; i < mInputs.length; i++) {
+ if (mInputs[i].onBackPressed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean onNavigationUpPressed() {
+ for (int i = 0; i < mInputs.length; i++) {
+ if (mInputs[i].onNavigationUpPressed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void resetMediaPickerState() {
+ mMediaInput.resetViewHolderState();
+ }
+
+ public void showHideMediaPicker(final boolean show, final boolean animate) {
+ showHideInternal(mMediaInput, show, animate);
+ }
+
+ /**
+ * Show or hide the sim selector
+ * @param show visibility
+ * @param animate whether to animate the change in visibility
+ * @return true if the state of the visibility was changed
+ */
+ public boolean showHideSimSelector(final boolean show, final boolean animate) {
+ return showHideInternal(mSimInput, show, animate);
+ }
+
+ public void showHideImeKeyboard(final boolean show, final boolean animate) {
+ showHideInternal(mImeInput, show, animate);
+ }
+
+ public void hideAllInputs(final boolean animate) {
+ beginUpdate();
+ for (int i = 0; i < mInputs.length; i++) {
+ showHideInternal(mInputs[i], false, animate);
+ }
+ endUpdate();
+ }
+
+ /**
+ * Toggle the visibility of the sim selector.
+ * @param animate
+ * @param subEntry
+ * @return true if the view is now shown, false if it now hidden
+ */
+ public boolean toggleSimSelector(final boolean animate, final SubscriptionListEntry subEntry) {
+ mSimInput.setSelected(subEntry);
+ return mSimInput.toggle(animate);
+ }
+
+ public boolean updateActionBar(final ActionBar actionBar) {
+ for (int i = 0; i < mInputs.length; i++) {
+ if (mInputs[i].mShowing) {
+ return mInputs[i].updateActionBar(actionBar);
+ }
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ boolean isMediaPickerVisible() {
+ return mMediaInput.mShowing;
+ }
+
+ @VisibleForTesting
+ boolean isSimSelectorVisible() {
+ return mSimInput.mShowing;
+ }
+
+ @VisibleForTesting
+ boolean isImeKeyboardVisible() {
+ return mImeInput.mShowing;
+ }
+
+ @VisibleForTesting
+ void testNotifyImeStateChanged(final boolean imeOpen) {
+ mImeStateObserver.onImeStateChanged(imeOpen);
+ }
+
+ /**
+ * returns true if the state of the visibility was actually changed
+ */
+ @Override
+ public boolean showHideInternal(final ConversationInput target, final boolean show,
+ final boolean animate) {
+ if (!mConversationDataModel.isBound()) {
+ return false;
+ }
+
+ if (target.mShowing == show) {
+ return false;
+ }
+ beginUpdate();
+ boolean success;
+ if (!show) {
+ success = target.hide(animate);
+ } else {
+ success = target.show(animate);
+ }
+
+ if (success) {
+ target.onVisibilityChanged(show);
+ }
+ endUpdate();
+ return true;
+ }
+
+ @Override
+ public void handleOnShow(final ConversationInput target) {
+ if (!mConversationDataModel.isBound()) {
+ return;
+ }
+ beginUpdate();
+
+ // All inputs are mutually exclusive. Showing one will hide everything else.
+ // The one exception, is that the keyboard and location media chooser can be open at the
+ // time to enable searching within that chooser
+ for (int i = 0; i < mInputs.length; i++) {
+ final ConversationInput currInput = mInputs[i];
+ if (currInput != target) {
+ // TODO : If there's more exceptions we will want to make this more
+ // generic
+ if (currInput instanceof ConversationMediaPicker &&
+ target instanceof ConversationImeKeyboard &&
+ mMediaInput.getExistingOrCreateMediaPicker() != null &&
+ mMediaInput.getExistingOrCreateMediaPicker().canShowIme()) {
+ // Allow the keyboard and location mediaPicker to be open at the same time,
+ // but ensure the media picker is full screen to allow enough room
+ mMediaInput.getExistingOrCreateMediaPicker().setFullScreen(true);
+ continue;
+ }
+ showHideInternal(currInput, false /* show */, false /* animate */);
+ }
+ }
+ // Always dismiss action mode on show.
+ mHost.dismissActionMode();
+ // Invoking any non-keyboard input UI is treated as starting message compose.
+ if (target != mImeInput) {
+ mHost.onStartComposeMessage();
+ }
+ endUpdate();
+ }
+
+ @Override
+ public void beginUpdate() {
+ mUpdateCount++;
+ }
+
+ @Override
+ public void endUpdate() {
+ Assert.isTrue(mUpdateCount > 0);
+ if (--mUpdateCount == 0) {
+ // Always try to update the host action bar after every update cycle.
+ mHost.invalidateActionBar();
+ }
+ }
+
+ private void updateHostOptionsMenu() {
+ mHost.setOptionsMenuVisibility(!mMediaInput.isOpen());
+ }
+
+ /**
+ * Manages showing/hiding the media picker in conversation.
+ */
+ private class ConversationMediaPicker extends ConversationInput {
+ public ConversationMediaPicker(ConversationInputBase baseHost) {
+ super(baseHost, false);
+ }
+
+ private MediaPicker mMediaPicker;
+
+ @Override
+ public boolean show(boolean animate) {
+ if (mMediaPicker == null) {
+ mMediaPicker = getExistingOrCreateMediaPicker();
+ setConversationThemeColor(ConversationDrawables.get().getConversationThemeColor());
+ mMediaPicker.setSubscriptionDataProvider(mHost);
+ mMediaPicker.setDraftMessageDataModel(mDraftDataModel);
+ mMediaPicker.setListener(new MediaPickerListener() {
+ @Override
+ public void onOpened() {
+ handleStateChange();
+ }
+
+ @Override
+ public void onFullScreenChanged(boolean fullScreen) {
+ // When we're full screen, we want to disable accessibility on the
+ // ComposeMessageView controls (attach button, message input, sim chooser)
+ // that are hiding underneath the action bar.
+ mSink.setAccessibility(!fullScreen /*enabled*/);
+ handleStateChange();
+ }
+
+ @Override
+ public void onDismissed() {
+ // Re-enable accessibility on all controls now that the media picker is
+ // going away.
+ mSink.setAccessibility(true /*enabled*/);
+ handleStateChange();
+ }
+
+ private void handleStateChange() {
+ onVisibilityChanged(isOpen());
+ mHost.invalidateActionBar();
+ updateHostOptionsMenu();
+ }
+
+ @Override
+ public void onItemsSelected(final Collection<MessagePartData> items,
+ final boolean resumeCompose) {
+ mSink.onMediaItemsSelected(items);
+ mHost.invalidateActionBar();
+ if (resumeCompose) {
+ mSink.resumeComposeMessage();
+ }
+ }
+
+ @Override
+ public void onItemUnselected(final MessagePartData item) {
+ mSink.onMediaItemsUnselected(item);
+ mHost.invalidateActionBar();
+ }
+
+ @Override
+ public void onConfirmItemSelection() {
+ mSink.resumeComposeMessage();
+ }
+
+ @Override
+ public void onPendingItemAdded(final PendingAttachmentData pendingItem) {
+ mSink.onPendingAttachmentAdded(pendingItem);
+ }
+
+ @Override
+ public void onChooserSelected(final int chooserIndex) {
+ mHost.invalidateActionBar();
+ mHost.dismissActionMode();
+ }
+ });
+ }
+
+ mMediaPicker.open(MediaPicker.MEDIA_TYPE_DEFAULT, animate);
+
+ return isOpen();
+ }
+
+ @Override
+ public boolean hide(boolean animate) {
+ if (mMediaPicker != null) {
+ mMediaPicker.dismiss(animate);
+ }
+ return !isOpen();
+ }
+
+ public void resetViewHolderState() {
+ if (mMediaPicker != null) {
+ mMediaPicker.resetViewHolderState();
+ }
+ }
+
+ public void setConversationThemeColor(final int themeColor) {
+ if (mMediaPicker != null) {
+ mMediaPicker.setConversationThemeColor(themeColor);
+ }
+ }
+
+ private boolean isOpen() {
+ return (mMediaPicker != null && mMediaPicker.isOpen());
+ }
+
+ private MediaPicker getExistingOrCreateMediaPicker() {
+ if (mMediaPicker != null) {
+ return mMediaPicker;
+ }
+ MediaPicker mediaPicker = (MediaPicker)
+ mFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG);
+ if (mediaPicker == null) {
+ mediaPicker = mHost.createMediaPicker();
+ if (mediaPicker == null) {
+ return null; // this use of ComposeMessageView doesn't support media picking
+ }
+ mFragmentManager.beginTransaction().replace(
+ R.id.mediapicker_container,
+ mediaPicker,
+ MediaPicker.FRAGMENT_TAG).commit();
+ }
+ return mediaPicker;
+ }
+
+ @Override
+ public boolean updateActionBar(ActionBar actionBar) {
+ if (isOpen()) {
+ mMediaPicker.updateActionBar(actionBar);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onNavigationUpPressed() {
+ if (isOpen() && mMediaPicker.isFullScreen()) {
+ return onBackPressed();
+ }
+ return super.onNavigationUpPressed();
+ }
+
+ public boolean onBackPressed() {
+ if (mMediaPicker != null && mMediaPicker.onBackPressed()) {
+ return true;
+ }
+ return super.onBackPressed();
+ }
+ }
+
+ /**
+ * Manages showing/hiding the SIM selector in conversation.
+ */
+ private class SimSelector extends ConversationSimSelector {
+ public SimSelector(ConversationInputBase baseHost) {
+ super(baseHost);
+ }
+
+ @Override
+ protected SimSelectorView getSimSelectorView() {
+ return mHost.getSimSelectorView();
+ }
+
+ @Override
+ public int getSimSelectorItemLayoutId() {
+ return mHost.getSimSelectorItemLayoutId();
+ }
+
+ @Override
+ protected void selectSim(SubscriptionListEntry item) {
+ mHost.selectSim(item);
+ }
+
+ @Override
+ public boolean show(boolean animate) {
+ final boolean result = super.show(animate);
+ mHost.showHideSimSelector(true /*show*/);
+ return result;
+ }
+
+ @Override
+ public boolean hide(boolean animate) {
+ final boolean result = super.hide(animate);
+ mHost.showHideSimSelector(false /*show*/);
+ return result;
+ }
+ }
+
+ /**
+ * Manages showing/hiding the IME keyboard in conversation.
+ */
+ private class ConversationImeKeyboard extends ConversationInput {
+ public ConversationImeKeyboard(ConversationInputBase baseHost, final boolean isShowing) {
+ super(baseHost, isShowing);
+ }
+
+ @Override
+ public boolean show(boolean animate) {
+ ImeUtil.get().showImeKeyboard(mContext, mSink.getComposeEditText());
+ return true;
+ }
+
+ @Override
+ public boolean hide(boolean animate) {
+ ImeUtil.get().hideImeKeyboard(mContext, mSink.getComposeEditText());
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java
new file mode 100644
index 0000000..2748fff
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.AsyncImageView;
+import com.android.messaging.ui.CursorRecyclerAdapter;
+import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
+import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
+import com.android.messaging.util.Assert;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Provides an interface to expose Conversation Message Cursor data to a UI widget like a
+ * RecyclerView.
+ */
+public class ConversationMessageAdapter extends
+ CursorRecyclerAdapter<ConversationMessageAdapter.ConversationMessageViewHolder> {
+
+ private final ConversationMessageViewHost mHost;
+ private final AsyncImageViewDelayLoader mImageViewDelayLoader;
+ private final View.OnClickListener mViewClickListener;
+ private final View.OnLongClickListener mViewLongClickListener;
+ private boolean mOneOnOne;
+ private String mSelectedMessageId;
+
+ public ConversationMessageAdapter(final Context context, final Cursor cursor,
+ final ConversationMessageViewHost host,
+ final AsyncImageViewDelayLoader imageViewDelayLoader,
+ final View.OnClickListener viewClickListener,
+ final View.OnLongClickListener longClickListener) {
+ super(context, cursor, 0);
+ mHost = host;
+ mViewClickListener = viewClickListener;
+ mViewLongClickListener = longClickListener;
+ mImageViewDelayLoader = imageViewDelayLoader;
+ setHasStableIds(true);
+ }
+
+ @Override
+ public void bindViewHolder(final ConversationMessageViewHolder holder,
+ final Context context, final Cursor cursor) {
+ Assert.isTrue(holder.mView instanceof ConversationMessageView);
+ final ConversationMessageView conversationMessageView =
+ (ConversationMessageView) holder.mView;
+ conversationMessageView.bind(cursor, mOneOnOne, mSelectedMessageId);
+ }
+
+ @Override
+ public ConversationMessageViewHolder createViewHolder(final Context context,
+ final ViewGroup parent, final int viewType) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(context);
+ final ConversationMessageView conversationMessageView = (ConversationMessageView)
+ layoutInflater.inflate(R.layout.conversation_message_view, null);
+ conversationMessageView.setHost(mHost);
+ conversationMessageView.setImageViewDelayLoader(mImageViewDelayLoader);
+ return new ConversationMessageViewHolder(conversationMessageView,
+ mViewClickListener, mViewLongClickListener);
+ }
+
+ public void setSelectedMessage(final String messageId) {
+ mSelectedMessageId = messageId;
+ notifyDataSetChanged();
+ }
+
+ public void setOneOnOne(final boolean oneOnOne, final boolean invalidate) {
+ if (mOneOnOne != oneOnOne) {
+ mOneOnOne = oneOnOne;
+ if (invalidate) {
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+ /**
+ * ViewHolder that holds a ConversationMessageView.
+ */
+ public static class ConversationMessageViewHolder extends RecyclerView.ViewHolder {
+ final View mView;
+
+ /**
+ * @param viewClickListener a View.OnClickListener that should define the interaction when
+ * an item in the RecyclerView is clicked.
+ */
+ public ConversationMessageViewHolder(final View itemView,
+ final View.OnClickListener viewClickListener,
+ final View.OnLongClickListener viewLongClickListener) {
+ super(itemView);
+ mView = itemView;
+
+ mView.setOnClickListener(viewClickListener);
+ mView.setOnLongClickListener(viewLongClickListener);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java
new file mode 100644
index 0000000..ef6aeb4
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java
@@ -0,0 +1,132 @@
+/*
+ * 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.conversation;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.annotation.VisibleForAnimation;
+import com.android.messaging.datamodel.data.ConversationMessageBubbleData;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Shows the message bubble for one conversation message. It is able to animate size changes
+ * by morphing when the message content changes size.
+ */
+// TODO: Move functionality from ConversationMessageView into this class as appropriate
+public class ConversationMessageBubbleView extends LinearLayout {
+ private int mIntrinsicWidth;
+ private int mMorphedWidth;
+ private ObjectAnimator mAnimator;
+ private boolean mShouldAnimateWidthChange;
+ private final ConversationMessageBubbleData mData;
+ private int mRunningStartWidth;
+ private ViewGroup mBubbleBackground;
+
+ public ConversationMessageBubbleView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = new ConversationMessageBubbleData();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBubbleBackground = (ViewGroup) findViewById(R.id.message_text_and_info);
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final int newIntrinsicWidth = getMeasuredWidth();
+ if (mIntrinsicWidth == 0 && newIntrinsicWidth != mIntrinsicWidth) {
+ if (mShouldAnimateWidthChange) {
+ kickOffMorphAnimation(mIntrinsicWidth, newIntrinsicWidth);
+ }
+ mIntrinsicWidth = newIntrinsicWidth;
+ }
+
+ if (mMorphedWidth > 0) {
+ mBubbleBackground.getLayoutParams().width = mMorphedWidth;
+ } else {
+ mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
+ }
+ mBubbleBackground.requestLayout();
+ }
+
+ @VisibleForAnimation
+ public void setMorphWidth(final int width) {
+ mMorphedWidth = width;
+ requestLayout();
+ }
+
+ public void bind(final ConversationMessageData data) {
+ final boolean changed = mData.bind(data);
+ // Animate width change only when we are binding to the same message, so that we may
+ // animate view size changes on the same message bubble due to things like status text
+ // change.
+ // Don't animate width change when the bubble contains attachments. Width animation is
+ // only suitable for text-only messages (where the bubble size change due to status or
+ // time stamp changes).
+ mShouldAnimateWidthChange = !changed && !data.hasAttachments();
+ if (mAnimator == null) {
+ mMorphedWidth = 0;
+ }
+ }
+
+ public void kickOffMorphAnimation(final int oldWidth, final int newWidth) {
+ if (mAnimator != null) {
+ mAnimator.setIntValues(mRunningStartWidth, newWidth);
+ return;
+ }
+ mRunningStartWidth = oldWidth;
+ mAnimator = ObjectAnimator.ofInt(this, "morphWidth", oldWidth, newWidth);
+ mAnimator.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
+ mAnimator.addListener(new AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mAnimator = null;
+ mMorphedWidth = 0;
+ // Allow the bubble to resize if, for example, the status text changed during
+ // the animation. This will snap to the bigger size if needed. This is intentional
+ // as animating immediately after looks really bad and switching layout params
+ // during the original animation does not achieve the desired effect.
+ mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
+ mBubbleBackground.requestLayout();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ }
+ });
+ mAnimator.start();
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
new file mode 100644
index 0000000..e22e2c7
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
@@ -0,0 +1,1206 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Formatter;
+import android.text.style.URLSpan;
+import android.text.util.Linkify;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView.ScaleType;
+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.ConversationMessageData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.datamodel.media.ImageRequestDescriptor;
+import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.AsyncImageView;
+import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
+import com.android.messaging.ui.AudioAttachmentView;
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.ui.MultiAttachmentLayout;
+import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
+import com.android.messaging.ui.PersonItemView;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.VideoThumbnailView;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.YouTubeUtil;
+import com.google.common.base.Predicate;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * The view for a single entry in a conversation.
+ */
+public class ConversationMessageView extends FrameLayout implements View.OnClickListener,
+ View.OnLongClickListener, OnAttachmentClickListener {
+ public interface ConversationMessageViewHost {
+ boolean onAttachmentClick(ConversationMessageView view, MessagePartData attachment,
+ Rect imageBounds, boolean longPress);
+ SubscriptionListEntry getSubscriptionEntryForSelfParticipant(String selfParticipantId,
+ boolean excludeDefault);
+ }
+
+ private final ConversationMessageData mData;
+
+ private LinearLayout mMessageAttachmentsView;
+ private MultiAttachmentLayout mMultiAttachmentView;
+ private AsyncImageView mMessageImageView;
+ private TextView mMessageTextView;
+ private boolean mMessageTextHasLinks;
+ private boolean mMessageHasYouTubeLink;
+ private TextView mStatusTextView;
+ private TextView mTitleTextView;
+ private TextView mMmsInfoTextView;
+ private LinearLayout mMessageTitleLayout;
+ private TextView mSenderNameTextView;
+ private ContactIconView mContactIconView;
+ private ConversationMessageBubbleView mMessageBubble;
+ private View mSubjectView;
+ private TextView mSubjectLabel;
+ private TextView mSubjectText;
+ private View mDeliveredBadge;
+ private ViewGroup mMessageMetadataView;
+ private ViewGroup mMessageTextAndInfoView;
+ private TextView mSimNameView;
+
+ private boolean mOneOnOne;
+ private ConversationMessageViewHost mHost;
+
+ public ConversationMessageView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ // TODO: we should switch to using Binding and DataModel factory methods.
+ mData = new ConversationMessageData();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
+ mContactIconView.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View view) {
+ ConversationMessageView.this.performLongClick();
+ return true;
+ }
+ });
+
+ mMessageAttachmentsView = (LinearLayout) findViewById(R.id.message_attachments);
+ mMultiAttachmentView = (MultiAttachmentLayout) findViewById(R.id.multiple_attachments);
+ mMultiAttachmentView.setOnAttachmentClickListener(this);
+
+ mMessageImageView = (AsyncImageView) findViewById(R.id.message_image);
+ mMessageImageView.setOnClickListener(this);
+ mMessageImageView.setOnLongClickListener(this);
+
+ mMessageTextView = (TextView) findViewById(R.id.message_text);
+ mMessageTextView.setOnClickListener(this);
+ IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this);
+
+ mStatusTextView = (TextView) findViewById(R.id.message_status);
+ mTitleTextView = (TextView) findViewById(R.id.message_title);
+ mMmsInfoTextView = (TextView) findViewById(R.id.mms_info);
+ mMessageTitleLayout = (LinearLayout) findViewById(R.id.message_title_layout);
+ mSenderNameTextView = (TextView) findViewById(R.id.message_sender_name);
+ mMessageBubble = (ConversationMessageBubbleView) findViewById(R.id.message_content);
+ mSubjectView = findViewById(R.id.subject_container);
+ mSubjectLabel = (TextView) mSubjectView.findViewById(R.id.subject_label);
+ mSubjectText = (TextView) mSubjectView.findViewById(R.id.subject_text);
+ mDeliveredBadge = findViewById(R.id.smsDeliveredBadge);
+ mMessageMetadataView = (ViewGroup) findViewById(R.id.message_metadata);
+ mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info);
+ mSimNameView = (TextView) findViewById(R.id.sim_name);
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec);
+ final int iconSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
+
+ final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int iconMeasureSpec = MeasureSpec.makeMeasureSpec(iconSize, MeasureSpec.EXACTLY);
+
+ mContactIconView.measure(iconMeasureSpec, iconMeasureSpec);
+
+ final int arrowWidth =
+ getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width);
+
+ // We need to subtract contact icon width twice from the horizontal space to get
+ // the max leftover space because we want the message bubble to extend no further than the
+ // starting position of the message bubble in the opposite direction.
+ final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2
+ - arrowWidth - getPaddingLeft() - getPaddingRight();
+ final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace,
+ MeasureSpec.AT_MOST);
+
+ mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec);
+
+ final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(),
+ mMessageBubble.getMeasuredHeight());
+ setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop());
+ }
+
+ @Override
+ protected void onLayout(final boolean changed, final int left, final int top, final int right,
+ final int bottom) {
+ final boolean isRtl = AccessibilityUtil.isLayoutRtl(this);
+
+ final int iconWidth = mContactIconView.getMeasuredWidth();
+ final int iconHeight = mContactIconView.getMeasuredHeight();
+ final int iconTop = getPaddingTop();
+ final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight();
+ final int contentHeight = mMessageBubble.getMeasuredHeight();
+ final int contentTop = iconTop;
+
+ final int iconLeft;
+ final int contentLeft;
+ if (mData.getIsIncoming()) {
+ if (isRtl) {
+ iconLeft = (right - left) - getPaddingRight() - iconWidth;
+ contentLeft = iconLeft - contentWidth;
+ } else {
+ iconLeft = getPaddingLeft();
+ contentLeft = iconLeft + iconWidth;
+ }
+ } else {
+ if (isRtl) {
+ iconLeft = getPaddingLeft();
+ contentLeft = iconLeft + iconWidth;
+ } else {
+ iconLeft = (right - left) - getPaddingRight() - iconWidth;
+ contentLeft = iconLeft - contentWidth;
+ }
+ }
+
+ mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
+
+ mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth,
+ contentTop + contentHeight);
+ }
+
+ /**
+ * Fills in the data associated with this view.
+ *
+ * @param cursor The cursor from a MessageList that this view is in, pointing to its entry.
+ */
+ public void bind(final Cursor cursor) {
+ bind(cursor, true, null);
+ }
+
+ /**
+ * Fills in the data associated with this view.
+ *
+ * @param cursor The cursor from a MessageList that this view is in, pointing to its entry.
+ * @param oneOnOne Whether this is a 1:1 conversation
+ */
+ public void bind(final Cursor cursor,
+ final boolean oneOnOne, final String selectedMessageId) {
+ mOneOnOne = oneOnOne;
+
+ // Update our UI model
+ mData.bind(cursor);
+ setSelected(TextUtils.equals(mData.getMessageId(), selectedMessageId));
+
+ // Update text and image content for the view.
+ updateViewContent();
+
+ // Update colors and layout parameters for the view.
+ updateViewAppearance();
+
+ updateContentDescription();
+ }
+
+ public void setHost(final ConversationMessageViewHost host) {
+ mHost = host;
+ }
+
+ /**
+ * Sets a delay loader instance to manage loading / resuming of image attachments.
+ */
+ public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
+ Assert.notNull(mMessageImageView);
+ mMessageImageView.setDelayLoader(delayLoader);
+ mMultiAttachmentView.setImageViewDelayLoader(delayLoader);
+ }
+
+ public ConversationMessageData getData() {
+ return mData;
+ }
+
+ /**
+ * Returns whether we should show simplified visual style for the message view (i.e. hide the
+ * avatar and bubble arrow, reduce padding).
+ */
+ private boolean shouldShowSimplifiedVisualStyle() {
+ return mData.getCanClusterWithPreviousMessage();
+ }
+
+ /**
+ * Returns whether we need to show message bubble arrow. We don't show arrow if the message
+ * contains media attachments or if shouldShowSimplifiedVisualStyle() is true.
+ */
+ private boolean shouldShowMessageBubbleArrow() {
+ return !shouldShowSimplifiedVisualStyle()
+ && !(mData.hasAttachments() || mMessageHasYouTubeLink);
+ }
+
+ /**
+ * Returns whether we need to show a message bubble for text content.
+ */
+ private boolean shouldShowMessageTextBubble() {
+ if (mData.hasText()) {
+ return true;
+ }
+ final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
+ mData.getMmsSubject());
+ if (!TextUtils.isEmpty(subjectText)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void updateViewContent() {
+ updateMessageContent();
+ int titleResId = -1;
+ int statusResId = -1;
+ String statusText = null;
+ switch(mData.getStatus()) {
+ case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
+ case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
+ titleResId = R.string.message_title_downloading;
+ statusResId = R.string.message_status_downloading;
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
+ if (!OsUtil.isSecondaryUser()) {
+ titleResId = R.string.message_title_manual_download;
+ if (isSelected()) {
+ statusResId = R.string.message_status_download_action;
+ } else {
+ statusResId = R.string.message_status_download;
+ }
+ }
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
+ if (!OsUtil.isSecondaryUser()) {
+ titleResId = R.string.message_title_download_failed;
+ statusResId = R.string.message_status_download_error;
+ }
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
+ if (!OsUtil.isSecondaryUser()) {
+ titleResId = R.string.message_title_download_failed;
+ if (isSelected()) {
+ statusResId = R.string.message_status_download_action;
+ } else {
+ statusResId = R.string.message_status_download;
+ }
+ }
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
+ case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
+ statusResId = R.string.message_status_sending;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
+ case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
+ statusResId = R.string.message_status_send_retrying;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
+ statusResId = R.string.message_status_send_failed_emergency_number;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
+ // don't show the error state unless we're the default sms app
+ if (PhoneUtils.getDefault().isDefaultSmsApp()) {
+ if (isSelected()) {
+ statusResId = R.string.message_status_resend;
+ } else {
+ statusResId = MmsUtils.mapRawStatusToErrorResourceId(
+ mData.getStatus(), mData.getRawTelephonyStatus());
+ }
+ break;
+ }
+ // FALL THROUGH HERE
+
+ case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
+ case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
+ default:
+ if (!mData.getCanClusterWithNextMessage()) {
+ statusText = mData.getFormattedReceivedTimeStamp();
+ }
+ break;
+ }
+
+ final boolean titleVisible = (titleResId >= 0);
+ if (titleVisible) {
+ final String titleText = getResources().getString(titleResId);
+ mTitleTextView.setText(titleText);
+
+ final String mmsInfoText = getResources().getString(
+ R.string.mms_info,
+ Formatter.formatFileSize(getContext(), mData.getSmsMessageSize()),
+ DateUtils.formatDateTime(
+ getContext(),
+ mData.getMmsExpiry(),
+ DateUtils.FORMAT_SHOW_DATE |
+ DateUtils.FORMAT_SHOW_TIME |
+ DateUtils.FORMAT_NUMERIC_DATE |
+ DateUtils.FORMAT_NO_YEAR));
+ mMmsInfoTextView.setText(mmsInfoText);
+ mMessageTitleLayout.setVisibility(View.VISIBLE);
+ } else {
+ mMessageTitleLayout.setVisibility(View.GONE);
+ }
+
+ final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
+ mData.getMmsSubject());
+ final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
+
+ final boolean senderNameVisible = !mOneOnOne && !mData.getCanClusterWithNextMessage()
+ && mData.getIsIncoming();
+ if (senderNameVisible) {
+ mSenderNameTextView.setText(mData.getSenderDisplayName());
+ mSenderNameTextView.setVisibility(View.VISIBLE);
+ } else {
+ mSenderNameTextView.setVisibility(View.GONE);
+ }
+
+ if (statusResId >= 0) {
+ statusText = getResources().getString(statusResId);
+ }
+
+ // We set the text even if the view will be GONE for accessibility
+ mStatusTextView.setText(statusText);
+ final boolean statusVisible = !TextUtils.isEmpty(statusText);
+ if (statusVisible) {
+ mStatusTextView.setVisibility(View.VISIBLE);
+ } else {
+ mStatusTextView.setVisibility(View.GONE);
+ }
+
+ final boolean deliveredBadgeVisible =
+ mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_DELIVERED;
+ mDeliveredBadge.setVisibility(deliveredBadgeVisible ? View.VISIBLE : View.GONE);
+
+ // Update the sim indicator.
+ final boolean showSimIconAsIncoming = mData.getIsIncoming() &&
+ (!mData.hasAttachments() || shouldShowMessageTextBubble());
+ final SubscriptionListEntry subscriptionEntry =
+ mHost.getSubscriptionEntryForSelfParticipant(mData.getSelfParticipantId(),
+ true /* excludeDefault */);
+ final boolean simNameVisible = subscriptionEntry != null &&
+ !TextUtils.isEmpty(subscriptionEntry.displayName) &&
+ !mData.getCanClusterWithNextMessage();
+ if (simNameVisible) {
+ final String simNameText = mData.getIsIncoming() ? getResources().getString(
+ R.string.incoming_sim_name_text, subscriptionEntry.displayName) :
+ subscriptionEntry.displayName;
+ mSimNameView.setText(simNameText);
+ mSimNameView.setTextColor(showSimIconAsIncoming ? getResources().getColor(
+ R.color.timestamp_text_incoming) : subscriptionEntry.displayColor);
+ mSimNameView.setVisibility(VISIBLE);
+ } else {
+ mSimNameView.setText(null);
+ mSimNameView.setVisibility(GONE);
+ }
+
+ final boolean metadataVisible = senderNameVisible || statusVisible
+ || deliveredBadgeVisible || simNameVisible;
+ mMessageMetadataView.setVisibility(metadataVisible ? View.VISIBLE : View.GONE);
+
+ final boolean messageTextAndOrInfoVisible = titleVisible || subjectVisible
+ || mData.hasText() || metadataVisible;
+ mMessageTextAndInfoView.setVisibility(
+ messageTextAndOrInfoVisible ? View.VISIBLE : View.GONE);
+
+ if (shouldShowSimplifiedVisualStyle()) {
+ mContactIconView.setVisibility(View.GONE);
+ mContactIconView.setImageResourceUri(null);
+ } else {
+ mContactIconView.setVisibility(View.VISIBLE);
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ mData.getSenderProfilePhotoUri(),
+ mData.getSenderFullName(),
+ mData.getSenderNormalizedDestination(),
+ mData.getSenderContactLookupKey());
+ mContactIconView.setImageResourceUri(avatarUri, mData.getSenderContactId(),
+ mData.getSenderContactLookupKey(), mData.getSenderNormalizedDestination());
+ }
+ }
+
+ private void updateMessageContent() {
+ // We must update the text before the attachments since we search the text to see if we
+ // should make a preview youtube image in the attachments
+ updateMessageText();
+ updateMessageAttachments();
+ updateMessageSubject();
+ mMessageBubble.bind(mData);
+ }
+
+ private void updateMessageAttachments() {
+ // Bind video, audio, and VCard attachments. If there are multiple, they stack vertically.
+ bindAttachmentsOfSameType(sVideoFilter,
+ R.layout.message_video_attachment, mVideoViewBinder, VideoThumbnailView.class);
+ bindAttachmentsOfSameType(sAudioFilter,
+ R.layout.message_audio_attachment, mAudioViewBinder, AudioAttachmentView.class);
+ bindAttachmentsOfSameType(sVCardFilter,
+ R.layout.message_vcard_attachment, mVCardViewBinder, PersonItemView.class);
+
+ // Bind image attachments. If there are multiple, they are shown in a collage view.
+ final List<MessagePartData> imageParts = mData.getAttachments(sImageFilter);
+ if (imageParts.size() > 1) {
+ Collections.sort(imageParts, sImageComparator);
+ mMultiAttachmentView.bindAttachments(imageParts, null, imageParts.size());
+ mMultiAttachmentView.setVisibility(View.VISIBLE);
+ } else {
+ mMultiAttachmentView.setVisibility(View.GONE);
+ }
+
+ // In the case that we have no image attachments and exactly one youtube link in a message
+ // then we will show a preview.
+ String youtubeThumbnailUrl = null;
+ String originalYoutubeLink = null;
+ if (mMessageTextHasLinks && imageParts.size() == 0) {
+ CharSequence messageTextWithSpans = mMessageTextView.getText();
+ final URLSpan[] spans = ((Spanned) messageTextWithSpans).getSpans(0,
+ messageTextWithSpans.length(), URLSpan.class);
+ for (URLSpan span : spans) {
+ String url = span.getURL();
+ String youtubeLinkForUrl = YouTubeUtil.getYoutubePreviewImageLink(url);
+ if (!TextUtils.isEmpty(youtubeLinkForUrl)) {
+ if (TextUtils.isEmpty(youtubeThumbnailUrl)) {
+ // Save the youtube link if we don't already have one
+ youtubeThumbnailUrl = youtubeLinkForUrl;
+ originalYoutubeLink = url;
+ } else {
+ // We already have a youtube link. This means we have two youtube links so
+ // we shall show none.
+ youtubeThumbnailUrl = null;
+ originalYoutubeLink = null;
+ break;
+ }
+ }
+ }
+ }
+ // We need to keep track if we have a youtube link in the message so that we will not show
+ // the arrow
+ mMessageHasYouTubeLink = !TextUtils.isEmpty(youtubeThumbnailUrl);
+
+ // We will show the message image view if there is one attachment or one youtube link
+ if (imageParts.size() == 1 || mMessageHasYouTubeLink) {
+ // Get the display metrics for a hint for how large to pull the image data into
+ final WindowManager windowManager = (WindowManager) getContext().
+ getSystemService(Context.WINDOW_SERVICE);
+ final DisplayMetrics displayMetrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+
+ final int iconSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
+ final int desiredWidth = displayMetrics.widthPixels - iconSize - iconSize;
+
+ if (imageParts.size() == 1) {
+ final MessagePartData imagePart = imageParts.get(0);
+ // If the image is big, we want to scale it down to save memory since we're going to
+ // scale it down to fit into the bubble width. We don't constrain the height.
+ final ImageRequestDescriptor imageRequest =
+ new MessagePartImageRequestDescriptor(imagePart,
+ desiredWidth,
+ MessagePartData.UNSPECIFIED_SIZE,
+ false);
+ adjustImageViewBounds(imagePart);
+ mMessageImageView.setImageResourceId(imageRequest);
+ mMessageImageView.setTag(imagePart);
+ } else {
+ // Youtube Thumbnail image
+ final ImageRequestDescriptor imageRequest =
+ new UriImageRequestDescriptor(Uri.parse(youtubeThumbnailUrl), desiredWidth,
+ MessagePartData.UNSPECIFIED_SIZE, true /* allowCompression */,
+ true /* isStatic */, false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ mMessageImageView.setImageResourceId(imageRequest);
+ mMessageImageView.setTag(originalYoutubeLink);
+ }
+ mMessageImageView.setVisibility(View.VISIBLE);
+ } else {
+ mMessageImageView.setImageResourceId(null);
+ mMessageImageView.setVisibility(View.GONE);
+ }
+
+ // Show the message attachments container if any of its children are visible
+ boolean attachmentsVisible = false;
+ for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
+ final View attachmentView = mMessageAttachmentsView.getChildAt(i);
+ if (attachmentView.getVisibility() == View.VISIBLE) {
+ attachmentsVisible = true;
+ break;
+ }
+ }
+ mMessageAttachmentsView.setVisibility(attachmentsVisible ? View.VISIBLE : View.GONE);
+ }
+
+ private void bindAttachmentsOfSameType(final Predicate<MessagePartData> attachmentTypeFilter,
+ final int attachmentViewLayoutRes, final AttachmentViewBinder viewBinder,
+ final Class<?> attachmentViewClass) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+
+ // Iterate through all attachments of a particular type (video, audio, etc).
+ // Find the first attachment index that matches the given type if possible.
+ int attachmentViewIndex = -1;
+ View existingAttachmentView;
+ do {
+ existingAttachmentView = mMessageAttachmentsView.getChildAt(++attachmentViewIndex);
+ } while (existingAttachmentView != null &&
+ !(attachmentViewClass.isInstance(existingAttachmentView)));
+
+ for (final MessagePartData attachment : mData.getAttachments(attachmentTypeFilter)) {
+ View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex);
+ if (!attachmentViewClass.isInstance(attachmentView)) {
+ attachmentView = layoutInflater.inflate(attachmentViewLayoutRes,
+ mMessageAttachmentsView, false /* attachToRoot */);
+ attachmentView.setOnClickListener(this);
+ attachmentView.setOnLongClickListener(this);
+ mMessageAttachmentsView.addView(attachmentView, attachmentViewIndex);
+ }
+ viewBinder.bindView(attachmentView, attachment);
+ attachmentView.setTag(attachment);
+ attachmentView.setVisibility(View.VISIBLE);
+ attachmentViewIndex++;
+ }
+ // If there are unused views left over, unbind or remove them.
+ while (attachmentViewIndex < mMessageAttachmentsView.getChildCount()) {
+ final View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex);
+ if (attachmentViewClass.isInstance(attachmentView)) {
+ mMessageAttachmentsView.removeViewAt(attachmentViewIndex);
+ } else {
+ // No more views of this type; we're done.
+ break;
+ }
+ }
+ }
+
+ private void updateMessageSubject() {
+ final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
+ mData.getMmsSubject());
+ final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
+
+ if (subjectVisible) {
+ mSubjectText.setText(subjectText);
+ mSubjectView.setVisibility(View.VISIBLE);
+ } else {
+ mSubjectView.setVisibility(View.GONE);
+ }
+ }
+
+ private void updateMessageText() {
+ final String text = mData.getText();
+ if (!TextUtils.isEmpty(text)) {
+ mMessageTextView.setText(text);
+ // Linkify phone numbers, web urls, emails, and map addresses to allow users to
+ // click on them and take the default intent.
+ mMessageTextHasLinks = Linkify.addLinks(mMessageTextView, Linkify.ALL);
+ mMessageTextView.setVisibility(View.VISIBLE);
+ } else {
+ mMessageTextView.setVisibility(View.GONE);
+ mMessageTextHasLinks = false;
+ }
+ }
+
+ private void updateViewAppearance() {
+ final Resources res = getResources();
+ final ConversationDrawables drawableProvider = ConversationDrawables.get();
+ final boolean incoming = mData.getIsIncoming();
+ final boolean outgoing = !incoming;
+ final boolean showArrow = shouldShowMessageBubbleArrow();
+
+ final int messageTopPaddingClustered =
+ res.getDimensionPixelSize(R.dimen.message_padding_same_author);
+ final int messageTopPaddingDefault =
+ res.getDimensionPixelSize(R.dimen.message_padding_default);
+ final int arrowWidth = res.getDimensionPixelOffset(R.dimen.message_bubble_arrow_width);
+ final int messageTextMinHeightDefault = res.getDimensionPixelSize(
+ R.dimen.conversation_message_contact_icon_size);
+ final int messageTextLeftRightPadding = res.getDimensionPixelOffset(
+ R.dimen.message_text_left_right_padding);
+ final int textTopPaddingDefault = res.getDimensionPixelOffset(
+ R.dimen.message_text_top_padding);
+ final int textBottomPaddingDefault = res.getDimensionPixelOffset(
+ R.dimen.message_text_bottom_padding);
+
+ // These values depend on whether the message has text, attachments, or both.
+ // We intentionally don't set defaults, so the compiler will tell us if we forget
+ // to set one of them, or if we set one more than once.
+ final int contentLeftPadding, contentRightPadding;
+ final Drawable textBackground;
+ final int textMinHeight;
+ final int textTopMargin;
+ final int textTopPadding, textBottomPadding;
+ final int textLeftPadding, textRightPadding;
+
+ if (mData.hasAttachments()) {
+ if (shouldShowMessageTextBubble()) {
+ // Text and attachment(s)
+ contentLeftPadding = incoming ? arrowWidth : 0;
+ contentRightPadding = outgoing ? arrowWidth : 0;
+ textBackground = drawableProvider.getBubbleDrawable(
+ isSelected(),
+ incoming,
+ false /* needArrow */,
+ mData.hasIncomingErrorStatus());
+ textMinHeight = messageTextMinHeightDefault;
+ textTopMargin = messageTopPaddingClustered;
+ textTopPadding = textTopPaddingDefault;
+ textBottomPadding = textBottomPaddingDefault;
+ textLeftPadding = messageTextLeftRightPadding;
+ textRightPadding = messageTextLeftRightPadding;
+ } else {
+ // Attachment(s) only
+ contentLeftPadding = incoming ? arrowWidth : 0;
+ contentRightPadding = outgoing ? arrowWidth : 0;
+ textBackground = null;
+ textMinHeight = 0;
+ textTopMargin = 0;
+ textTopPadding = 0;
+ textBottomPadding = 0;
+ textLeftPadding = 0;
+ textRightPadding = 0;
+ }
+ } else {
+ // Text only
+ contentLeftPadding = (!showArrow && incoming) ? arrowWidth : 0;
+ contentRightPadding = (!showArrow && outgoing) ? arrowWidth : 0;
+ textBackground = drawableProvider.getBubbleDrawable(
+ isSelected(),
+ incoming,
+ shouldShowMessageBubbleArrow(),
+ mData.hasIncomingErrorStatus());
+ textMinHeight = messageTextMinHeightDefault;
+ textTopMargin = 0;
+ textTopPadding = textTopPaddingDefault;
+ textBottomPadding = textBottomPaddingDefault;
+ if (showArrow && incoming) {
+ textLeftPadding = messageTextLeftRightPadding + arrowWidth;
+ } else {
+ textLeftPadding = messageTextLeftRightPadding;
+ }
+ if (showArrow && outgoing) {
+ textRightPadding = messageTextLeftRightPadding + arrowWidth;
+ } else {
+ textRightPadding = messageTextLeftRightPadding;
+ }
+ }
+
+ // These values do not depend on whether the message includes attachments
+ final int gravity = incoming ? (Gravity.START | Gravity.CENTER_VERTICAL) :
+ (Gravity.END | Gravity.CENTER_VERTICAL);
+ final int messageTopPadding = shouldShowSimplifiedVisualStyle() ?
+ messageTopPaddingClustered : messageTopPaddingDefault;
+ final int metadataTopPadding = res.getDimensionPixelOffset(
+ R.dimen.message_metadata_top_padding);
+
+ // Update the message text/info views
+ ImageUtils.setBackgroundDrawableOnView(mMessageTextAndInfoView, textBackground);
+ mMessageTextAndInfoView.setMinimumHeight(textMinHeight);
+ final LinearLayout.LayoutParams textAndInfoLayoutParams =
+ (LinearLayout.LayoutParams) mMessageTextAndInfoView.getLayoutParams();
+ textAndInfoLayoutParams.topMargin = textTopMargin;
+
+ if (UiUtils.isRtlMode()) {
+ // Need to switch right and left padding in RtL mode
+ mMessageTextAndInfoView.setPadding(textRightPadding, textTopPadding, textLeftPadding,
+ textBottomPadding);
+ mMessageBubble.setPadding(contentRightPadding, 0, contentLeftPadding, 0);
+ } else {
+ mMessageTextAndInfoView.setPadding(textLeftPadding, textTopPadding, textRightPadding,
+ textBottomPadding);
+ mMessageBubble.setPadding(contentLeftPadding, 0, contentRightPadding, 0);
+ }
+
+ // Update the message row and message bubble views
+ setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0);
+ mMessageBubble.setGravity(gravity);
+ updateMessageAttachmentsAppearance(gravity);
+
+ mMessageMetadataView.setPadding(0, metadataTopPadding, 0, 0);
+
+ updateTextAppearance();
+
+ requestLayout();
+ }
+
+ private void updateContentDescription() {
+ StringBuilder description = new StringBuilder();
+
+ Resources res = getResources();
+ String separator = res.getString(R.string.enumeration_comma);
+
+ // Sender information
+ boolean hasPlainTextMessage = !(TextUtils.isEmpty(mData.getText()) ||
+ mMessageTextHasLinks);
+ if (mData.getIsIncoming()) {
+ int senderResId = hasPlainTextMessage
+ ? R.string.incoming_text_sender_content_description
+ : R.string.incoming_sender_content_description;
+ description.append(res.getString(senderResId, mData.getSenderDisplayName()));
+ } else {
+ int senderResId = hasPlainTextMessage
+ ? R.string.outgoing_text_sender_content_description
+ : R.string.outgoing_sender_content_description;
+ description.append(res.getString(senderResId));
+ }
+
+ if (mSubjectView.getVisibility() == View.VISIBLE) {
+ description.append(separator);
+ description.append(mSubjectText.getText());
+ }
+
+ if (mMessageTextView.getVisibility() == View.VISIBLE) {
+ // If the message has hyperlinks, we will let the user navigate to the text message so
+ // that the hyperlink can be clicked. Otherwise, the text message does not need to
+ // be reachable.
+ if (mMessageTextHasLinks) {
+ mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ } else {
+ mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ description.append(separator);
+ description.append(mMessageTextView.getText());
+ }
+ }
+
+ if (mMessageTitleLayout.getVisibility() == View.VISIBLE) {
+ description.append(separator);
+ description.append(mTitleTextView.getText());
+
+ description.append(separator);
+ description.append(mMmsInfoTextView.getText());
+ }
+
+ if (mStatusTextView.getVisibility() == View.VISIBLE) {
+ description.append(separator);
+ description.append(mStatusTextView.getText());
+ }
+
+ if (mSimNameView.getVisibility() == View.VISIBLE) {
+ description.append(separator);
+ description.append(mSimNameView.getText());
+ }
+
+ if (mDeliveredBadge.getVisibility() == View.VISIBLE) {
+ description.append(separator);
+ description.append(res.getString(R.string.delivered_status_content_description));
+ }
+
+ setContentDescription(description);
+ }
+
+ private void updateMessageAttachmentsAppearance(final int gravity) {
+ mMessageAttachmentsView.setGravity(gravity);
+
+ // Tint image/video attachments when selected
+ final int selectedImageTint = getResources().getColor(R.color.message_image_selected_tint);
+ if (mMessageImageView.getVisibility() == View.VISIBLE) {
+ if (isSelected()) {
+ mMessageImageView.setColorFilter(selectedImageTint);
+ } else {
+ mMessageImageView.clearColorFilter();
+ }
+ }
+ if (mMultiAttachmentView.getVisibility() == View.VISIBLE) {
+ if (isSelected()) {
+ mMultiAttachmentView.setColorFilter(selectedImageTint);
+ } else {
+ mMultiAttachmentView.clearColorFilter();
+ }
+ }
+ for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
+ final View attachmentView = mMessageAttachmentsView.getChildAt(i);
+ if (attachmentView instanceof VideoThumbnailView
+ && attachmentView.getVisibility() == View.VISIBLE) {
+ final VideoThumbnailView videoView = (VideoThumbnailView) attachmentView;
+ if (isSelected()) {
+ videoView.setColorFilter(selectedImageTint);
+ } else {
+ videoView.clearColorFilter();
+ }
+ }
+ }
+
+ // If there are multiple attachment bubbles in a single message, add some separation.
+ final int multipleAttachmentPadding =
+ getResources().getDimensionPixelSize(R.dimen.message_padding_same_author);
+
+ boolean previousVisibleView = false;
+ for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
+ final View attachmentView = mMessageAttachmentsView.getChildAt(i);
+ if (attachmentView.getVisibility() == View.VISIBLE) {
+ final int margin = previousVisibleView ? multipleAttachmentPadding : 0;
+ ((LinearLayout.LayoutParams) attachmentView.getLayoutParams()).topMargin = margin;
+ // updateViewAppearance calls requestLayout() at the end, so we don't need to here
+ previousVisibleView = true;
+ }
+ }
+ }
+
+ private void updateTextAppearance() {
+ int messageColorResId;
+ int statusColorResId = -1;
+ int infoColorResId = -1;
+ int timestampColorResId;
+ int subjectLabelColorResId;
+ if (isSelected()) {
+ messageColorResId = R.color.message_text_color_incoming;
+ statusColorResId = R.color.message_action_status_text;
+ infoColorResId = R.color.message_action_info_text;
+ if (shouldShowMessageTextBubble()) {
+ timestampColorResId = R.color.message_action_timestamp_text;
+ subjectLabelColorResId = R.color.message_action_timestamp_text;
+ } else {
+ // If there's no text, the timestamp will be shown below the attachments,
+ // against the conversation view background.
+ timestampColorResId = R.color.timestamp_text_outgoing;
+ subjectLabelColorResId = R.color.timestamp_text_outgoing;
+ }
+ } else {
+ messageColorResId = (mData.getIsIncoming() ?
+ R.color.message_text_color_incoming : R.color.message_text_color_outgoing);
+ statusColorResId = messageColorResId;
+ infoColorResId = R.color.timestamp_text_incoming;
+ switch(mData.getStatus()) {
+
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
+ timestampColorResId = R.color.message_failed_timestamp_text;
+ subjectLabelColorResId = R.color.timestamp_text_outgoing;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
+ case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
+ case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
+ case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
+ case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
+ case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED:
+ timestampColorResId = R.color.timestamp_text_outgoing;
+ subjectLabelColorResId = R.color.timestamp_text_outgoing;
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
+ case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
+ messageColorResId = R.color.message_text_color_incoming_download_failed;
+ timestampColorResId = R.color.message_download_failed_timestamp_text;
+ subjectLabelColorResId = R.color.message_text_color_incoming_download_failed;
+ statusColorResId = R.color.message_download_failed_status_text;
+ infoColorResId = R.color.message_info_text_incoming_download_failed;
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
+ case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
+ case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
+ timestampColorResId = R.color.message_text_color_incoming;
+ subjectLabelColorResId = R.color.message_text_color_incoming;
+ infoColorResId = R.color.timestamp_text_incoming;
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
+ default:
+ timestampColorResId = R.color.timestamp_text_incoming;
+ subjectLabelColorResId = R.color.timestamp_text_incoming;
+ infoColorResId = -1; // Not used
+ break;
+ }
+ }
+ final int messageColor = getResources().getColor(messageColorResId);
+ mMessageTextView.setTextColor(messageColor);
+ mMessageTextView.setLinkTextColor(messageColor);
+ mSubjectText.setTextColor(messageColor);
+ if (statusColorResId >= 0) {
+ mTitleTextView.setTextColor(getResources().getColor(statusColorResId));
+ }
+ if (infoColorResId >= 0) {
+ mMmsInfoTextView.setTextColor(getResources().getColor(infoColorResId));
+ }
+ if (timestampColorResId == R.color.timestamp_text_incoming &&
+ mData.hasAttachments() && !shouldShowMessageTextBubble()) {
+ timestampColorResId = R.color.timestamp_text_outgoing;
+ }
+ mStatusTextView.setTextColor(getResources().getColor(timestampColorResId));
+
+ mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId));
+ mSenderNameTextView.setTextColor(getResources().getColor(timestampColorResId));
+ }
+
+ /**
+ * If we don't know the size of the image, we want to show it in a fixed-sized frame to
+ * avoid janks when the image is loaded and resized. Otherwise, we can set the imageview to
+ * take on normal layout params.
+ */
+ private void adjustImageViewBounds(final MessagePartData imageAttachment) {
+ Assert.isTrue(ContentType.isImageType(imageAttachment.getContentType()));
+ final ViewGroup.LayoutParams layoutParams = mMessageImageView.getLayoutParams();
+ if (imageAttachment.getWidth() == MessagePartData.UNSPECIFIED_SIZE ||
+ imageAttachment.getHeight() == MessagePartData.UNSPECIFIED_SIZE) {
+ // We don't know the size of the image attachment, enable letterboxing on the image
+ // and show a fixed sized attachment. This should happen at most once per image since
+ // after the image is loaded we then save the image dimensions to the db so that the
+ // next time we can display the full size.
+ layoutParams.width = getResources()
+ .getDimensionPixelSize(R.dimen.image_attachment_fallback_width);
+ layoutParams.height = getResources()
+ .getDimensionPixelSize(R.dimen.image_attachment_fallback_height);
+ mMessageImageView.setScaleType(ScaleType.CENTER_CROP);
+ } else {
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ // ScaleType.CENTER_INSIDE and FIT_CENTER behave similarly for most images. However,
+ // FIT_CENTER works better for small images as it enlarges the image such that the
+ // minimum size ("android:minWidth" etc) is honored.
+ mMessageImageView.setScaleType(ScaleType.FIT_CENTER);
+ }
+ }
+
+ @Override
+ public void onClick(final View view) {
+ final Object tag = view.getTag();
+ if (tag instanceof MessagePartData) {
+ final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
+ onAttachmentClick((MessagePartData) tag, bounds, false /* longPress */);
+ } else if (tag instanceof String) {
+ // Currently the only object that would make a tag of a string is a youtube preview
+ // image
+ UIIntents.get().launchBrowserForUrl(getContext(), (String) tag);
+ }
+ }
+
+ @Override
+ public boolean onLongClick(final View view) {
+ if (view == mMessageTextView) {
+ // Preemptively handle the long click event on message text so it's not handled by
+ // the link spans.
+ return performLongClick();
+ }
+
+ final Object tag = view.getTag();
+ if (tag instanceof MessagePartData) {
+ final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
+ return onAttachmentClick((MessagePartData) tag, bounds, true /* longPress */);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onAttachmentClick(final MessagePartData attachment,
+ final Rect viewBoundsOnScreen, final boolean longPress) {
+ return mHost.onAttachmentClick(this, attachment, viewBoundsOnScreen, longPress);
+ }
+
+ public ContactIconView getContactIconView() {
+ return mContactIconView;
+ }
+
+ // Sort photos in MultiAttachLayout in the same order as the ConversationImagePartsView
+ static final Comparator<MessagePartData> sImageComparator = new Comparator<MessagePartData>(){
+ @Override
+ public int compare(final MessagePartData x, final MessagePartData y) {
+ return x.getPartId().compareTo(y.getPartId());
+ }
+ };
+
+ static final Predicate<MessagePartData> sVideoFilter = new Predicate<MessagePartData>() {
+ @Override
+ public boolean apply(final MessagePartData part) {
+ return part.isVideo();
+ }
+ };
+
+ static final Predicate<MessagePartData> sAudioFilter = new Predicate<MessagePartData>() {
+ @Override
+ public boolean apply(final MessagePartData part) {
+ return part.isAudio();
+ }
+ };
+
+ static final Predicate<MessagePartData> sVCardFilter = new Predicate<MessagePartData>() {
+ @Override
+ public boolean apply(final MessagePartData part) {
+ return part.isVCard();
+ }
+ };
+
+ static final Predicate<MessagePartData> sImageFilter = new Predicate<MessagePartData>() {
+ @Override
+ public boolean apply(final MessagePartData part) {
+ return part.isImage();
+ }
+ };
+
+ interface AttachmentViewBinder {
+ void bindView(View view, MessagePartData attachment);
+ void unbind(View view);
+ }
+
+ final AttachmentViewBinder mVideoViewBinder = new AttachmentViewBinder() {
+ @Override
+ public void bindView(final View view, final MessagePartData attachment) {
+ ((VideoThumbnailView) view).setSource(attachment, mData.getIsIncoming());
+ }
+
+ @Override
+ public void unbind(final View view) {
+ ((VideoThumbnailView) view).setSource((Uri) null, mData.getIsIncoming());
+ }
+ };
+
+ final AttachmentViewBinder mAudioViewBinder = new AttachmentViewBinder() {
+ @Override
+ public void bindView(final View view, final MessagePartData attachment) {
+ final AudioAttachmentView audioView = (AudioAttachmentView) view;
+ audioView.bindMessagePartData(attachment, isSelected() || mData.getIsIncoming());
+ audioView.setBackground(ConversationDrawables.get().getBubbleDrawable(
+ isSelected(), mData.getIsIncoming(), false /* needArrow */,
+ mData.hasIncomingErrorStatus()));
+ }
+
+ @Override
+ public void unbind(final View view) {
+ ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming());
+ }
+ };
+
+ final AttachmentViewBinder mVCardViewBinder = new AttachmentViewBinder() {
+ @Override
+ public void bindView(final View view, final MessagePartData attachment) {
+ final PersonItemView personView = (PersonItemView) view;
+ personView.bind(DataModel.get().createVCardContactItemData(getContext(),
+ attachment));
+ personView.setBackground(ConversationDrawables.get().getBubbleDrawable(
+ isSelected(), mData.getIsIncoming(), false /* needArrow */,
+ mData.hasIncomingErrorStatus()));
+ final int nameTextColorRes;
+ final int detailsTextColorRes;
+ if (isSelected()) {
+ nameTextColorRes = R.color.message_text_color_incoming;
+ detailsTextColorRes = R.color.message_text_color_incoming;
+ } else {
+ nameTextColorRes = mData.getIsIncoming() ? R.color.message_text_color_incoming
+ : R.color.message_text_color_outgoing;
+ detailsTextColorRes = mData.getIsIncoming() ? R.color.timestamp_text_incoming
+ : R.color.timestamp_text_outgoing;
+ }
+ personView.setNameTextColor(getResources().getColor(nameTextColorRes));
+ personView.setDetailsTextColor(getResources().getColor(detailsTextColorRes));
+ }
+
+ @Override
+ public void unbind(final View view) {
+ ((PersonItemView) view).bind(null);
+ }
+ };
+
+ /**
+ * A helper class that allows us to handle long clicks on linkified message text view (i.e. to
+ * select the message) so it's not handled by the link spans to launch apps for the links.
+ */
+ private static class IgnoreLinkLongClickHelper implements OnLongClickListener, OnTouchListener {
+ private boolean mIsLongClick;
+ private final OnLongClickListener mDelegateLongClickListener;
+
+ /**
+ * Ignore long clicks on linkified texts for a given text view.
+ * @param textView the TextView to ignore long clicks on
+ * @param longClickListener a delegate OnLongClickListener to be called when the view is
+ * long clicked.
+ */
+ public static void ignoreLinkLongClick(final TextView textView,
+ @Nullable final OnLongClickListener longClickListener) {
+ final IgnoreLinkLongClickHelper helper =
+ new IgnoreLinkLongClickHelper(longClickListener);
+ textView.setOnLongClickListener(helper);
+ textView.setOnTouchListener(helper);
+ }
+
+ private IgnoreLinkLongClickHelper(@Nullable final OnLongClickListener longClickListener) {
+ mDelegateLongClickListener = longClickListener;
+ }
+
+ @Override
+ public boolean onLongClick(final View v) {
+ // Record that this click is a long click.
+ mIsLongClick = true;
+ if (mDelegateLongClickListener != null) {
+ return mDelegateLongClickListener.onLongClick(v);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(final View v, final MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP && mIsLongClick) {
+ // This touch event is a long click, preemptively handle this touch event so that
+ // the link span won't get a onClicked() callback.
+ mIsLongClick = false;
+ return true;
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mIsLongClick = false;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java b/src/com/android/messaging/ui/conversation/ConversationSimSelector.java
new file mode 100644
index 0000000..fc43a46
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/ConversationSimSelector.java
@@ -0,0 +1,128 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.support.v4.util.Pair;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.SubscriptionListData;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.ui.conversation.SimSelectorView.SimSelectorViewListener;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.ThreadUtil;
+
+/**
+ * Manages showing/hiding the SIM selector in conversation.
+ */
+abstract class ConversationSimSelector extends ConversationInput {
+ private SimSelectorView mSimSelectorView;
+ private Pair<Boolean /* show */, Boolean /* animate */> mPendingShow;
+ private boolean mDataReady;
+ private String mSelectedSimText;
+
+ public ConversationSimSelector(ConversationInputBase baseHost) {
+ super(baseHost, false);
+ }
+
+ public void onSubscriptionListDataLoaded(final SubscriptionListData subscriptionListData) {
+ ensureSimSelectorView();
+ mSimSelectorView.bind(subscriptionListData);
+ mDataReady = subscriptionListData != null && subscriptionListData.hasData();
+ if (mPendingShow != null && mDataReady) {
+ Assert.isTrue(OsUtil.isAtLeastL_MR1());
+ final boolean show = mPendingShow.first;
+ final boolean animate = mPendingShow.second;
+ ThreadUtil.getMainThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ // This will No-Op if we are no longer attached to the host.
+ mConversationInputBase.showHideInternal(ConversationSimSelector.this,
+ show, animate);
+ }
+ });
+ mPendingShow = null;
+ }
+ }
+
+ private void announcedSelectedSim() {
+ final Context context = Factory.get().getApplicationContext();
+ if (AccessibilityUtil.isTouchExplorationEnabled(context) &&
+ !TextUtils.isEmpty(mSelectedSimText)) {
+ AccessibilityUtil.announceForAccessibilityCompat(
+ mSimSelectorView, null,
+ context.getString(R.string.selected_sim_content_message, mSelectedSimText));
+ }
+ }
+
+ public void setSelected(final SubscriptionListEntry subEntry) {
+ mSelectedSimText = subEntry == null ? null : subEntry.displayName;
+ }
+
+ @Override
+ public boolean show(boolean animate) {
+ announcedSelectedSim();
+ return showHide(true, animate);
+ }
+
+ @Override
+ public boolean hide(boolean animate) {
+ return showHide(false, animate);
+ }
+
+ private boolean showHide(final boolean show, final boolean animate) {
+ if (!OsUtil.isAtLeastL_MR1()) {
+ return false;
+ }
+
+ if (mDataReady) {
+ mSimSelectorView.showOrHide(show, animate);
+ return mSimSelectorView.isOpen() == show;
+ } else {
+ mPendingShow = Pair.create(show, animate);
+ return false;
+ }
+ }
+
+ private void ensureSimSelectorView() {
+ if (mSimSelectorView == null) {
+ // Grab the SIM selector view from the host. This class assumes ownership of it.
+ mSimSelectorView = getSimSelectorView();
+ mSimSelectorView.setItemLayoutId(getSimSelectorItemLayoutId());
+ mSimSelectorView.setListener(new SimSelectorViewListener() {
+
+ @Override
+ public void onSimSelectorVisibilityChanged(boolean visible) {
+ onVisibilityChanged(visible);
+ }
+
+ @Override
+ public void onSimItemClicked(SubscriptionListEntry item) {
+ selectSim(item);
+ }
+ });
+ }
+ }
+
+ protected abstract SimSelectorView getSimSelectorView();
+ protected abstract void selectSim(final SubscriptionListEntry item);
+ protected abstract int getSimSelectorItemLayoutId();
+
+}
diff --git a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java b/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java
new file mode 100644
index 0000000..e3ad601
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java
@@ -0,0 +1,92 @@
+/*
+ * 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.conversation;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.widget.EditText;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.ParticipantRefresh;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * The dialog for the user to enter the phone number of their sim.
+ */
+public class EnterSelfPhoneNumberDialog extends DialogFragment {
+ private EditText mEditText;
+ private int mSubId;
+
+ public static EnterSelfPhoneNumberDialog newInstance(final int subId) {
+ final EnterSelfPhoneNumberDialog dialog = new EnterSelfPhoneNumberDialog();
+ dialog.mSubId = subId;
+ return dialog;
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Context context = getActivity();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ mEditText = (EditText) inflater.inflate(R.layout.enter_phone_number_view, null, false);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.enter_phone_number_title)
+ .setMessage(R.string.enter_phone_number_text)
+ .setView(mEditText)
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ dismiss();
+ }
+ })
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ final String newNumber = mEditText.getText().toString();
+ dismiss();
+ if (!TextUtils.isEmpty(newNumber)) {
+ savePhoneNumberInPrefs(newNumber);
+ // TODO: Remove this toast and just auto-send
+ // the message instead
+ UiUtils.showToast(
+ R.string
+ .toast_after_setting_default_sms_app_for_message_send);
+ }
+ }
+ });
+ return builder.create();
+ }
+
+ private void savePhoneNumberInPrefs(final String newPhoneNumber) {
+ final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
+ subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
+ newPhoneNumber);
+ // Update the self participants so the new phone number will be reflected
+ // everywhere in the UI.
+ ParticipantRefresh.refreshSelfParticipants();
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
new file mode 100644
index 0000000..8af9f75
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
@@ -0,0 +1,134 @@
+/*
+ * 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.conversation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.LaunchConversationData;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+import com.android.messaging.util.UriUtil;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+/**
+ * Launches ConversationActivity for sending a message to, or viewing messages from, a specific
+ * recipient.
+ * <p>
+ * (This activity should be marked noHistory="true" in AndroidManifest.xml)
+ */
+public class LaunchConversationActivity extends Activity implements
+ LaunchConversationData.LaunchConversationDataListener {
+ static final String SMS_BODY = "sms_body";
+ static final String ADDRESS = "address";
+ final Binding<LaunchConversationData> mBinding = BindingBase.createBinding(this);
+ String mSmsBody;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
+ return;
+ }
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+ if (Intent.ACTION_SENDTO.equals(action) || Intent.ACTION_VIEW.equals(action)) {
+ String[] recipients = UriUtil.parseRecipientsFromSmsMmsUri(intent.getData());
+ final boolean haveAddress = !TextUtils.isEmpty(intent.getStringExtra(ADDRESS));
+ final boolean haveEmail = !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL));
+ if (recipients == null && (haveAddress || haveEmail)) {
+ if (haveAddress) {
+ recipients = new String[] { intent.getStringExtra(ADDRESS) };
+ } else {
+ recipients = new String[] { intent.getStringExtra(Intent.EXTRA_EMAIL) };
+ }
+ }
+ mSmsBody = intent.getStringExtra(SMS_BODY);
+ if (TextUtils.isEmpty(mSmsBody)) {
+ // Used by intents sent from the web YouTube (and perhaps others).
+ mSmsBody = getBody(intent.getData());
+ if (TextUtils.isEmpty(mSmsBody)) {
+ // If that fails, try yet another method apps use to share text
+ if (ContentType.TEXT_PLAIN.equals(intent.getType())) {
+ mSmsBody = intent.getStringExtra(Intent.EXTRA_TEXT);
+ }
+ }
+ }
+ if (recipients != null) {
+ mBinding.bind(DataModel.get().createLaunchConversationData(this));
+ mBinding.getData().getOrCreateConversation(mBinding, recipients);
+ } else {
+ // No recipients were specified in the intent.
+ // Start a new conversation with contact picker. The new conversation will be
+ // primed with the (optional) message in mSmsBody.
+ onGetOrCreateNewConversation(null);
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Unsupported conversation intent action : " + action);
+ }
+ // As of M, activities without a visible window must finish before onResume completes.
+ finish();
+ }
+
+ private String getBody(final Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ String urlStr = uri.getSchemeSpecificPart();
+ if (!urlStr.contains("?")) {
+ return null;
+ }
+ urlStr = urlStr.substring(urlStr.indexOf('?') + 1);
+ final String[] params = urlStr.split("&");
+ for (final String p : params) {
+ if (p.startsWith("body=")) {
+ try {
+ return URLDecoder.decode(p.substring(5), "UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ // Invalid URL, ignore
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onGetOrCreateNewConversation(final String conversationId) {
+ final Context context = Factory.get().getApplicationContext();
+ UIIntents.get().launchConversationActivityWithParentStack(context, conversationId,
+ mSmsBody);
+ }
+
+ @Override
+ public void onGetOrCreateNewConversationFailed() {
+ UiUtils.showToastAtBottom(R.string.conversation_creation_failure);
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java b/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java
new file mode 100644
index 0000000..4c22970
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java
@@ -0,0 +1,47 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import com.android.messaging.R;
+
+public class MessageBubbleBackground extends LinearLayout {
+ private final int mSnapWidthPixels;
+
+ public MessageBubbleBackground(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mSnapWidthPixels = context.getResources().getDimensionPixelSize(
+ R.dimen.conversation_bubble_width_snap);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int widthPadding = getPaddingLeft() + getPaddingRight();
+ int bubbleWidth = getMeasuredWidth() - widthPadding;
+ final int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - widthPadding;
+ // Round up to next snapWidthPixels
+ bubbleWidth = Math.min(maxWidth,
+ (int) (Math.ceil(bubbleWidth / (float) mSnapWidthPixels) * mSnapWidthPixels));
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(bubbleWidth + widthPadding, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java b/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java
new file mode 100644
index 0000000..89b9148
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java
@@ -0,0 +1,381 @@
+/*
+ * 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.conversation;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.text.format.Formatter;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.datamodel.data.ConversationParticipantsData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.pdu.PduHeaders;
+import com.android.messaging.sms.DatabaseMessages.MmsMessage;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.SafeAsyncTask;
+
+import java.util.List;
+
+public class MessageDetailsDialog {
+ private static final String RECIPIENT_SEPARATOR = ", ";
+
+ // All methods are static, no creating this class
+ private MessageDetailsDialog() {
+ }
+
+ public static void show(final Context context, final ConversationMessageData data,
+ final ConversationParticipantsData participants, final ParticipantData self) {
+ if (DebugUtils.isDebugEnabled()) {
+ new SafeAsyncTask<Void, Void, String>() {
+ @Override
+ protected String doInBackgroundTimed(Void... params) {
+ return getMessageDetails(context, data, participants, self);
+ }
+
+ @Override
+ protected void onPostExecute(String messageDetails) {
+ showDialog(context, messageDetails);
+ }
+ }.executeOnThreadPool(null, null, null);
+ } else {
+ String messageDetails = getMessageDetails(context, data, participants, self);
+ showDialog(context, messageDetails);
+ }
+ }
+
+ private static String getMessageDetails(final Context context,
+ final ConversationMessageData data,
+ final ConversationParticipantsData participants, final ParticipantData self) {
+ String messageDetails = null;
+ if (data.getIsSms()) {
+ messageDetails = getSmsMessageDetails(data, participants, self);
+ } else {
+ // TODO: Handle SMS_TYPE_MMS_PUSH_NOTIFICATION type differently?
+ messageDetails = getMmsMessageDetails(context, data, participants, self);
+ }
+
+ return messageDetails;
+ }
+
+ private static void showDialog(final Context context, String messageDetails) {
+ if (!TextUtils.isEmpty(messageDetails)) {
+ new AlertDialog.Builder(context)
+ .setTitle(R.string.message_details_title)
+ .setMessage(messageDetails)
+ .setCancelable(true)
+ .show();
+ }
+ }
+
+ /**
+ * Return a string, separated by newlines, that contains a number of labels and values
+ * for this sms message. The string will be displayed in a modal dialog.
+ * @return string list of various message properties
+ */
+ private static String getSmsMessageDetails(final ConversationMessageData data,
+ final ConversationParticipantsData participants, final ParticipantData self) {
+ final Resources res = Factory.get().getApplicationContext().getResources();
+ final StringBuilder details = new StringBuilder();
+
+ // Type: Text message
+ details.append(res.getString(R.string.message_type_label));
+ details.append(res.getString(R.string.text_message));
+
+ // From: +1425xxxxxxx
+ // or To: +1425xxxxxxx
+ final String rawSender = data.getSenderNormalizedDestination();
+ if (!TextUtils.isEmpty(rawSender)) {
+ details.append('\n');
+ details.append(res.getString(R.string.from_label));
+ details.append(rawSender);
+ }
+ final String rawRecipients = getRecipientParticipantString(participants,
+ data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
+ if (!TextUtils.isEmpty(rawRecipients)) {
+ details.append('\n');
+ details.append(res.getString(R.string.to_address_label));
+ details.append(rawRecipients);
+ }
+
+ // Sent: Mon 11:42AM
+ if (data.getIsIncoming()) {
+ if (data.getSentTimeStamp() != MmsUtils.INVALID_TIMESTAMP) {
+ details.append('\n');
+ details.append(res.getString(R.string.sent_label));
+ details.append(
+ Dates.getMessageDetailsTimeString(data.getSentTimeStamp()).toString());
+ }
+ }
+
+ // Sent: Mon 11:43AM
+ // or Received: Mon 11:43AM
+ appendSentOrReceivedTimestamp(res, details, data);
+
+ appendSimInfo(res, self, details);
+
+ if (DebugUtils.isDebugEnabled()) {
+ appendDebugInfo(details, data);
+ }
+
+ return details.toString();
+ }
+
+ /**
+ * Return a string, separated by newlines, that contains a number of labels and values
+ * for this mms message. The string will be displayed in a modal dialog.
+ * @return string list of various message properties
+ */
+ private static String getMmsMessageDetails(Context context, final ConversationMessageData data,
+ final ConversationParticipantsData participants, final ParticipantData self) {
+ final Resources res = Factory.get().getApplicationContext().getResources();
+ // TODO: when we support non-auto-download of mms messages, we'll have to handle
+ // the case when the message is a PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND and display
+ // something different. See the Messaging app's MessageUtils.getNotificationIndDetails()
+
+ final StringBuilder details = new StringBuilder();
+
+ // Type: Multimedia message.
+ details.append(res.getString(R.string.message_type_label));
+ details.append(res.getString(R.string.multimedia_message));
+
+ // From: +1425xxxxxxx
+ final String rawSender = data.getSenderNormalizedDestination();
+ details.append('\n');
+ details.append(res.getString(R.string.from_label));
+ details.append(!TextUtils.isEmpty(rawSender) ? rawSender :
+ res.getString(R.string.hidden_sender_address));
+
+ // To: +1425xxxxxxx
+ final String rawRecipients = getRecipientParticipantString(participants,
+ data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
+ if (!TextUtils.isEmpty(rawRecipients)) {
+ details.append('\n');
+ details.append(res.getString(R.string.to_address_label));
+ details.append(rawRecipients);
+ }
+
+ // Sent: Tue 3:05PM
+ // or Received: Tue 3:05PM
+ appendSentOrReceivedTimestamp(res, details, data);
+
+ // Subject: You're awesome
+ details.append('\n');
+ details.append(res.getString(R.string.subject_label));
+ if (!TextUtils.isEmpty(MmsUtils.cleanseMmsSubject(res, data.getMmsSubject()))) {
+ details.append(data.getMmsSubject());
+ }
+
+ // Priority: High/Normal/Low
+ details.append('\n');
+ details.append(res.getString(R.string.priority_label));
+ details.append(getPriorityDescription(res, data.getSmsPriority()));
+
+ // Message size: 30 KB
+ if (data.getSmsMessageSize() > 0) {
+ details.append('\n');
+ details.append(res.getString(R.string.message_size_label));
+ details.append(Formatter.formatFileSize(context, data.getSmsMessageSize()));
+ }
+
+ appendSimInfo(res, self, details);
+
+ if (DebugUtils.isDebugEnabled()) {
+ appendDebugInfo(details, data);
+ }
+
+ return details.toString();
+ }
+
+ private static void appendSentOrReceivedTimestamp(Resources res, StringBuilder details,
+ ConversationMessageData data) {
+ int labelId = -1;
+ if (data.getIsIncoming()) {
+ labelId = R.string.received_label;
+ } else if (data.getIsSendComplete()) {
+ labelId = R.string.sent_label;
+ }
+ if (labelId >= 0) {
+ details.append('\n');
+ details.append(res.getString(labelId));
+ details.append(
+ Dates.getMessageDetailsTimeString(data.getReceivedTimeStamp()).toString());
+ }
+ }
+
+ @DoesNotRunOnMainThread
+ private static void appendDebugInfo(StringBuilder details, ConversationMessageData data) {
+ // We grab the thread id from the database, so this needs to run in the background
+ Assert.isNotMainThread();
+ details.append("\n\n");
+ details.append("DEBUG");
+
+ details.append('\n');
+ details.append("Message id: ");
+ details.append(data.getMessageId());
+
+ final String telephonyUri = data.getSmsMessageUri();
+ details.append('\n');
+ details.append("Telephony uri: ");
+ details.append(telephonyUri);
+
+ final String conversationId = data.getConversationId();
+
+ if (conversationId == null) {
+ return;
+ }
+
+ details.append('\n');
+ details.append("Conversation id: ");
+ details.append(conversationId);
+
+ final long threadId = BugleDatabaseOperations.getThreadId(DataModel.get().getDatabase(),
+ conversationId);
+
+ details.append('\n');
+ details.append("Conversation telephony thread id: ");
+ details.append(threadId);
+
+ MmsMessage mms = null;
+
+ if (data.getIsMms()) {
+ if (telephonyUri == null) {
+ return;
+ }
+ mms = MmsUtils.loadMms(Uri.parse(telephonyUri));
+ if (mms == null) {
+ return;
+ }
+
+ // We log the thread id again to check that they are internally consistent
+ final long mmsThreadId = mms.mThreadId;
+ details.append('\n');
+ details.append("Telephony thread id: ");
+ details.append(mmsThreadId);
+
+ // Log the MMS content location
+ final String mmsContentLocation = mms.mContentLocation;
+ details.append('\n');
+ details.append("Content location URL: ");
+ details.append(mmsContentLocation);
+ }
+
+ final String recipientsString = MmsUtils.getRawRecipientIdsForThread(threadId);
+ if (recipientsString != null) {
+ details.append('\n');
+ details.append("Thread recipient ids: ");
+ details.append(recipientsString);
+ }
+
+ final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
+ if (recipients != null) {
+ details.append('\n');
+ details.append("Thread recipients: ");
+ details.append(recipients.toString());
+
+ if (mms != null) {
+ final String from = MmsUtils.getMmsSender(recipients, mms.getUri());
+ details.append('\n');
+ details.append("Sender: ");
+ details.append(from);
+ }
+ }
+ }
+
+ private static String getRecipientParticipantString(
+ final ConversationParticipantsData participants, final String senderId,
+ final boolean addSelf, final String selfId) {
+ final StringBuilder recipients = new StringBuilder();
+ for (final ParticipantData participant : participants) {
+ if (TextUtils.equals(participant.getId(), senderId)) {
+ // Don't add sender
+ continue;
+ }
+ if (participant.isSelf() &&
+ (!participant.getId().equals(selfId) || !addSelf)) {
+ // For self participants, don't add the one that's not relevant to this message
+ // or if we are asked not to add self
+ continue;
+ }
+ final String phoneNumber = participant.getNormalizedDestination();
+ // Don't add empty number. This should not happen. But if that happens
+ // we should not add it.
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ if (recipients.length() > 0) {
+ recipients.append(RECIPIENT_SEPARATOR);
+ }
+ recipients.append(phoneNumber);
+ }
+ }
+ return recipients.toString();
+ }
+
+ /**
+ * Convert the numeric mms priority into a human-readable string
+ * @param res
+ * @param priorityValue coded PduHeader priority
+ * @return string representation of the priority
+ */
+ private static String getPriorityDescription(final Resources res, final int priorityValue) {
+ switch(priorityValue) {
+ case PduHeaders.PRIORITY_HIGH:
+ return res.getString(R.string.priority_high);
+ case PduHeaders.PRIORITY_LOW:
+ return res.getString(R.string.priority_low);
+ case PduHeaders.PRIORITY_NORMAL:
+ default:
+ return res.getString(R.string.priority_normal);
+ }
+ }
+
+ private static void appendSimInfo(final Resources res,
+ final ParticipantData self, final StringBuilder outString) {
+ if (!OsUtil.isAtLeastL_MR1()
+ || self == null
+ || PhoneUtils.getDefault().getActiveSubscriptionCount() < 2) {
+ return;
+ }
+ // The appended SIM info would look like:
+ // SIM: SUB 01
+ // or SIM: SIM 1
+ // or SIM: Unknown
+ Assert.isTrue(self.isSelf());
+ outString.append('\n');
+ outString.append(res.getString(R.string.sim_label));
+ if (self.isActiveSubscription() && !self.isDefaultSelf()) {
+ final String subscriptionName = self.getSubscriptionName();
+ if (TextUtils.isEmpty(subscriptionName)) {
+ outString.append(res.getString(R.string.sim_slot_identifier,
+ self.getDisplaySlotId()));
+ } else {
+ outString.append(subscriptionName);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/SimIconView.java b/src/com/android/messaging/ui/conversation/SimIconView.java
new file mode 100644
index 0000000..e2e446c
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/SimIconView.java
@@ -0,0 +1,51 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.graphics.Outline;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Shows SIM avatar icon in the SIM switcher / Self-send button.
+ */
+public class SimIconView extends ContactIconView {
+ public SimIconView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ if (OsUtil.isAtLeastL()) {
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View v, Outline outline) {
+ outline.setOval(0, 0, v.getWidth(), v.getHeight());
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void maybeInitializeOnClickListener() {
+ // TODO: SIM icon view shouldn't consume or handle clicks, but it should if
+ // this is the send button for the only SIM in the device or if MSIM is not supported.
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java b/src/com/android/messaging/ui/conversation/SimSelectorItemView.java
new file mode 100644
index 0000000..3058d31
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/SimSelectorItemView.java
@@ -0,0 +1,90 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.util.Assert;
+
+/**
+ * Shows a view for a SIM in the SIM selector.
+ */
+public class SimSelectorItemView extends LinearLayout {
+ public interface HostInterface {
+ void onSimItemClicked(SubscriptionListEntry item);
+ }
+
+ private SubscriptionListEntry mData;
+ private TextView mNameTextView;
+ private TextView mDetailsTextView;
+ private SimIconView mSimIconView;
+ private HostInterface mHost;
+
+ public SimSelectorItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mNameTextView = (TextView) findViewById(R.id.name);
+ mDetailsTextView = (TextView) findViewById(R.id.details);
+ mSimIconView = (SimIconView) findViewById(R.id.sim_icon);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mHost.onSimItemClicked(mData);
+ }
+ });
+ }
+
+ public void bind(final SubscriptionListEntry simEntry) {
+ Assert.notNull(simEntry);
+ mData = simEntry;
+ updateViewAppearance();
+ }
+
+ public void setHostInterface(final HostInterface host) {
+ mHost = host;
+ }
+
+ private void updateViewAppearance() {
+ Assert.notNull(mData);
+ final String displayName = mData.displayName;
+ if (TextUtils.isEmpty(displayName)) {
+ mNameTextView.setVisibility(GONE);
+ } else {
+ mNameTextView.setVisibility(VISIBLE);
+ mNameTextView.setText(displayName);
+ }
+
+ final String details = mData.displayDestination;
+ if (TextUtils.isEmpty(details)) {
+ mDetailsTextView.setVisibility(GONE);
+ } else {
+ mDetailsTextView.setVisibility(VISIBLE);
+ mDetailsTextView.setText(details);
+ }
+
+ mSimIconView.setImageResourceUri(mData.iconUri);
+ }
+}
diff --git a/src/com/android/messaging/ui/conversation/SimSelectorView.java b/src/com/android/messaging/ui/conversation/SimSelectorView.java
new file mode 100644
index 0000000..b07ff19
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/SimSelectorView.java
@@ -0,0 +1,169 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.SubscriptionListData;
+import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Displays a SIM selector above the compose message view and overlays the message list.
+ */
+public class SimSelectorView extends FrameLayout implements SimSelectorItemView.HostInterface {
+ public interface SimSelectorViewListener {
+ void onSimItemClicked(SubscriptionListEntry item);
+ void onSimSelectorVisibilityChanged(boolean visible);
+ }
+
+ private ListView mSimListView;
+ private final SimSelectorAdapter mAdapter;
+ private boolean mShow;
+ private SimSelectorViewListener mListener;
+ private int mItemLayoutId;
+
+ public SimSelectorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAdapter = new SimSelectorAdapter(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSimListView = (ListView) findViewById(R.id.sim_list);
+ mSimListView.setAdapter(mAdapter);
+
+ // Clicking anywhere outside the switcher list should dismiss.
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showOrHide(false, true);
+ }
+ });
+ }
+
+ public void bind(final SubscriptionListData data) {
+ mAdapter.bindData(data.getActiveSubscriptionEntriesExcludingDefault());
+ }
+
+ public void setItemLayoutId(final int layoutId) {
+ mItemLayoutId = layoutId;
+ }
+
+ public void setListener(final SimSelectorViewListener listener) {
+ mListener = listener;
+ }
+
+ public void toggleVisibility() {
+ showOrHide(!mShow, true);
+ }
+
+ public void showOrHide(final boolean show, final boolean animate) {
+ final boolean oldShow = mShow;
+ mShow = show && mAdapter.getCount() > 1;
+ if (oldShow != mShow) {
+ if (mListener != null) {
+ mListener.onSimSelectorVisibilityChanged(mShow);
+ }
+
+ if (animate) {
+ // Fade in the background pane.
+ setVisibility(VISIBLE);
+ setAlpha(mShow ? 0.0f : 1.0f);
+ animate().alpha(mShow ? 1.0f : 0.0f)
+ .setDuration(UiUtils.REVEAL_ANIMATION_DURATION)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ setAlpha(1.0f);
+ setVisibility(mShow ? VISIBLE : GONE);
+ }
+ });
+ } else {
+ setVisibility(mShow ? VISIBLE : GONE);
+ }
+
+ // Slide in the SIM selector list via a translate animation.
+ mSimListView.setVisibility(mShow ? VISIBLE : GONE);
+ if (animate) {
+ mSimListView.clearAnimation();
+ final TranslateAnimation translateAnimation = new TranslateAnimation(
+ Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
+ Animation.RELATIVE_TO_SELF, mShow ? 1.0f : 0.0f,
+ Animation.RELATIVE_TO_SELF, mShow ? 0.0f : 1.0f);
+ translateAnimation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR);
+ translateAnimation.setDuration(UiUtils.REVEAL_ANIMATION_DURATION);
+ mSimListView.startAnimation(translateAnimation);
+ }
+ }
+ }
+
+ /**
+ * An adapter that takes a list of SubscriptionListEntry and displays them as a list of
+ * available SIMs in the SIM selector.
+ */
+ private class SimSelectorAdapter extends ArrayAdapter<SubscriptionListEntry> {
+ public SimSelectorAdapter(final Context context) {
+ super(context, R.layout.sim_selector_item_view, new ArrayList<SubscriptionListEntry>());
+ }
+
+ public void bindData(final List<SubscriptionListEntry> newList) {
+ clear();
+ addAll(newList);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ SimSelectorItemView itemView;
+ if (convertView != null && convertView instanceof SimSelectorItemView) {
+ itemView = (SimSelectorItemView) convertView;
+ } else {
+ final LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ itemView = (SimSelectorItemView) inflater.inflate(mItemLayoutId,
+ parent, false);
+ itemView.setHostInterface(SimSelectorView.this);
+ }
+ itemView.bind(getItem(position));
+ return itemView;
+ }
+ }
+
+ @Override
+ public void onSimItemClicked(SubscriptionListEntry item) {
+ mListener.onSimItemClicked(item);
+ showOrHide(false, true);
+ }
+
+ public boolean isOpen() {
+ return mShow;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java
new file mode 100644
index 0000000..dbbbb15
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java
@@ -0,0 +1,339 @@
+/*
+ * 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.conversationlist;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.view.View;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.action.DeleteConversationAction;
+import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
+import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
+import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.SnackBar;
+import com.android.messaging.ui.SnackBarInteraction;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
+import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
+import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.PhoneUtils;
+import com.android.messaging.util.Trace;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Base class for many Conversation List activities. This will handle the common actions of multi
+ * select and common launching of intents.
+ */
+public abstract class AbstractConversationListActivity extends BugleActionBarActivity
+ implements ConversationListFragmentHost, MultiSelectActionModeCallback.Listener {
+
+ private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
+
+ protected ConversationListFragment mConversationListFragment;
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ Trace.beginSection("AbstractConversationListActivity.onAttachFragment");
+ // Fragment could be debug dialog
+ if (fragment instanceof ConversationListFragment) {
+ mConversationListFragment = (ConversationListFragment) fragment;
+ mConversationListFragment.setHost(this);
+ }
+ Trace.endSection();
+ }
+
+ @Override
+ public void onBackPressed() {
+ // If action mode is active dismiss it
+ if (getActionMode() != null) {
+ dismissActionMode();
+ return;
+ }
+ super.onBackPressed();
+ }
+
+ protected void startMultiSelectActionMode() {
+ startActionMode(new MultiSelectActionModeCallback(this));
+ }
+
+ protected void exitMultiSelectState() {
+ mConversationListFragment.showFab();
+ dismissActionMode();
+ mConversationListFragment.updateUi();
+ }
+
+ protected boolean isInConversationListSelectMode() {
+ return getActionModeCallback() instanceof MultiSelectActionModeCallback;
+ }
+
+ @Override
+ public boolean isSelectionMode() {
+ return isInConversationListSelectMode();
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ }
+
+ @Override
+ public void onActionBarDelete(final Collection<SelectedConversation> conversations) {
+ if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
+ // TODO: figure out a good way to combine this with the implementation in
+ // ConversationFragment doing similar things
+ final Activity activity = this;
+ UiUtils.showSnackBarWithCustomAction(this,
+ getWindow().getDecorView().getRootView(),
+ getString(R.string.requires_default_sms_app),
+ SnackBar.Action.createCustomAction(new Runnable() {
+ @Override
+ public void run() {
+ final Intent intent =
+ UIIntents.get().getChangeDefaultSmsAppIntent(activity);
+ startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
+ }
+ },
+ getString(R.string.requires_default_sms_change_button)),
+ null /* interactions */,
+ null /* placement */);
+ return;
+ }
+
+ new AlertDialog.Builder(this)
+ .setTitle(getResources().getQuantityString(
+ R.plurals.delete_conversations_confirmation_dialog_title,
+ conversations.size()))
+ .setPositiveButton(R.string.delete_conversation_confirmation_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ for (final SelectedConversation conversation : conversations) {
+ DeleteConversationAction.deleteConversation(
+ conversation.conversationId,
+ conversation.timestamp);
+ }
+ exitMultiSelectState();
+ }
+ })
+ .setNegativeButton(R.string.delete_conversation_decline_button, null)
+ .show();
+ }
+
+ @Override
+ public void onActionBarArchive(final Iterable<SelectedConversation> conversations,
+ final boolean isToArchive) {
+ final ArrayList<String> conversationIds = new ArrayList<String>();
+ for (final SelectedConversation conversation : conversations) {
+ final String conversationId = conversation.conversationId;
+ conversationIds.add(conversationId);
+ if (isToArchive) {
+ UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
+ } else {
+ UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
+ }
+ }
+
+ final Runnable undoRunnable = new Runnable() {
+ @Override
+ public void run() {
+ for (final String conversationId : conversationIds) {
+ if (isToArchive) {
+ UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
+ } else {
+ UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
+ }
+ }
+ }
+ };
+
+ final int textId =
+ isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message;
+ final String message = getResources().getString(textId, conversationIds.size());
+ UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable,
+ SnackBar.Action.SNACK_BAR_UNDO,
+ mConversationListFragment.getSnackBarInteractions());
+ exitMultiSelectState();
+ }
+
+ @Override
+ public void onActionBarNotification(final Iterable<SelectedConversation> conversations,
+ final boolean isNotificationOn) {
+ for (final SelectedConversation conversation : conversations) {
+ UpdateConversationOptionsAction.enableConversationNotifications(
+ conversation.conversationId, isNotificationOn);
+ }
+
+ final int textId = isNotificationOn ?
+ R.string.notification_on_toast_message : R.string.notification_off_toast_message;
+ final String message = getResources().getString(textId, 1);
+ UiUtils.showSnackBar(this, findViewById(android.R.id.list), message,
+ null /* undoRunnable */,
+ SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions());
+ exitMultiSelectState();
+ }
+
+ @Override
+ public void onActionBarAddContact(final SelectedConversation conversation) {
+ final Uri avatarUri;
+ if (conversation.icon != null) {
+ avatarUri = Uri.parse(conversation.icon);
+ } else {
+ avatarUri = null;
+ }
+ final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
+ this, avatarUri, conversation.otherParticipantNormalizedDestination);
+ dialog.show();
+ exitMultiSelectState();
+ }
+
+ @Override
+ public void onActionBarBlock(final SelectedConversation conversation) {
+ final Resources res = getResources();
+ new AlertDialog.Builder(this)
+ .setTitle(res.getString(R.string.block_confirmation_title,
+ conversation.otherParticipantNormalizedDestination))
+ .setMessage(res.getString(R.string.block_confirmation_message))
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface arg0, final int arg1) {
+ final Context context = AbstractConversationListActivity.this;
+ final View listView = findViewById(android.R.id.list);
+ final List<SnackBarInteraction> interactions =
+ mConversationListFragment.getSnackBarInteractions();
+ final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
+ undoListener =
+ new UpdateDestinationBlockedActionSnackBar(
+ context, listView, null /* undoRunnable */,
+ interactions);
+ final Runnable undoRunnable = new Runnable() {
+ @Override
+ public void run() {
+ UpdateDestinationBlockedAction.updateDestinationBlocked(
+ conversation.otherParticipantNormalizedDestination, false,
+ conversation.conversationId,
+ undoListener);
+ }
+ };
+ final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
+ listener = new UpdateDestinationBlockedActionSnackBar(
+ context, listView, undoRunnable, interactions);
+ UpdateDestinationBlockedAction.updateDestinationBlocked(
+ conversation.otherParticipantNormalizedDestination, true,
+ conversation.conversationId,
+ listener);
+ exitMultiSelectState();
+ }
+ })
+ .create()
+ .show();
+ }
+
+ @Override
+ public void onConversationClick(final ConversationListData listData,
+ final ConversationListItemData conversationListItemData,
+ final boolean isLongClick,
+ final ConversationListItemView conversationView) {
+ if (isLongClick && !isInConversationListSelectMode()) {
+ startMultiSelectActionMode();
+ }
+
+ if (isInConversationListSelectMode()) {
+ final MultiSelectActionModeCallback multiSelectActionMode =
+ (MultiSelectActionModeCallback) getActionModeCallback();
+ multiSelectActionMode.toggleSelect(listData, conversationListItemData);
+ mConversationListFragment.updateUi();
+ } else {
+ final String conversationId = conversationListItemData.getConversationId();
+ Bundle sceneTransitionAnimationOptions = null;
+ boolean hasCustomTransitions = false;
+
+ UIIntents.get().launchConversationActivity(
+ this, conversationId, null,
+ sceneTransitionAnimationOptions,
+ hasCustomTransitions);
+ }
+ }
+
+ @Override
+ public void onCreateConversationClick() {
+ UIIntents.get().launchCreateNewConversationActivity(this, null);
+ }
+
+
+ @Override
+ public boolean isConversationSelected(final String conversationId) {
+ return isInConversationListSelectMode() &&
+ ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected(
+ conversationId);
+ }
+
+ public void onActionBarDebug() {
+ DebugUtils.showDebugOptions(this);
+ }
+
+ private static class UpdateDestinationBlockedActionSnackBar
+ implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
+ private final Context mContext;
+ private final View mParentView;
+ private final Runnable mUndoRunnable;
+ private final List<SnackBarInteraction> mInteractions;
+
+ UpdateDestinationBlockedActionSnackBar(final Context context,
+ @NonNull final View parentView, @Nullable final Runnable undoRunnable,
+ @Nullable List<SnackBarInteraction> interactions) {
+ mContext = context;
+ mParentView = parentView;
+ mUndoRunnable = undoRunnable;
+ mInteractions = interactions;
+ }
+
+ @Override
+ public void onUpdateDestinationBlockedAction(
+ final UpdateDestinationBlockedAction action,
+ final boolean success, final boolean block,
+ final String destination) {
+ if (success) {
+ final int messageId = block ? R.string.blocked_toast_message
+ : R.string.unblocked_toast_message;
+ final String message = mContext.getResources().getString(messageId, 1);
+ UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable,
+ SnackBar.Action.SNACK_BAR_UNDO, mInteractions);
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java
new file mode 100644
index 0000000..366c7d3
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java
@@ -0,0 +1,96 @@
+/*
+ * 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.conversationlist;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.util.DebugUtils;
+
+public class ArchivedConversationListActivity extends AbstractConversationListActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final ConversationListFragment fragment =
+ ConversationListFragment.createArchivedConversationListFragment();
+ getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
+ invalidateActionBar();
+ }
+
+ protected void updateActionBar(ActionBar actionBar) {
+ actionBar.setTitle(getString(R.string.archived_activity_title));
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(
+ R.color.archived_conversation_action_bar_background_color_dark)));
+ actionBar.show();
+ super.updateActionBar(actionBar);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (isInConversationListSelectMode()) {
+ exitMultiSelectState();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (super.onCreateOptionsMenu(menu)) {
+ return true;
+ }
+ getMenuInflater().inflate(R.menu.archived_conversation_list_menu, menu);
+ final MenuItem item = menu.findItem(R.id.action_debug_options);
+ if (item != null) {
+ final boolean enableDebugItems = DebugUtils.isDebugEnabled();
+ item.setVisible(enableDebugItems).setEnabled(enableDebugItems);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ switch(menuItem.getItemId()) {
+ case R.id.action_debug_options:
+ onActionBarDebug();
+ return true;
+ case android.R.id.home:
+ onActionBarHome();
+ return true;
+ default:
+ return super.onOptionsItemSelected(menuItem);
+ }
+ }
+
+ @Override
+ public void onActionBarHome() {
+ onBackPressed();
+ }
+
+ @Override
+ public boolean isSwipeAnimatable() {
+ return false;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java
new file mode 100644
index 0000000..f8abe81
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java
@@ -0,0 +1,144 @@
+/*
+ * 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.conversationlist;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.Trace;
+
+public class ConversationListActivity extends AbstractConversationListActivity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ Trace.beginSection("ConversationListActivity.onCreate");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.conversation_list_activity);
+ Trace.endSection();
+ invalidateActionBar();
+ }
+
+ @Override
+ protected void updateActionBar(final ActionBar actionBar) {
+ actionBar.setTitle(getString(R.string.app_name));
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+ actionBar.setBackgroundDrawable(new ColorDrawable(
+ getResources().getColor(R.color.action_bar_background_color)));
+ actionBar.show();
+ super.updateActionBar(actionBar);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ // Invalidate the menu as items that are based on settings may have changed
+ // while not in the app (e.g. Talkback enabled/disable affects new conversation
+ // button)
+ supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (isInConversationListSelectMode()) {
+ exitMultiSelectState();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ if (super.onCreateOptionsMenu(menu)) {
+ return true;
+ }
+ getMenuInflater().inflate(R.menu.conversation_list_fragment_menu, menu);
+ final MenuItem item = menu.findItem(R.id.action_debug_options);
+ if (item != null) {
+ final boolean enableDebugItems = DebugUtils.isDebugEnabled();
+ item.setVisible(enableDebugItems).setEnabled(enableDebugItems);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ switch(menuItem.getItemId()) {
+ case R.id.action_start_new_conversation:
+ onActionBarStartNewConversation();
+ return true;
+ case R.id.action_settings:
+ onActionBarSettings();
+ return true;
+ case R.id.action_debug_options:
+ onActionBarDebug();
+ return true;
+ case R.id.action_show_archived:
+ onActionBarArchived();
+ return true;
+ case R.id.action_show_blocked_contacts:
+ onActionBarBlockedParticipants();
+ return true;
+ }
+ return super.onOptionsItemSelected(menuItem);
+ }
+
+ @Override
+ public void onActionBarHome() {
+ exitMultiSelectState();
+ }
+
+ public void onActionBarStartNewConversation() {
+ UIIntents.get().launchCreateNewConversationActivity(this, null);
+ }
+
+ public void onActionBarSettings() {
+ UIIntents.get().launchSettingsActivity(this);
+ }
+
+ public void onActionBarBlockedParticipants() {
+ UIIntents.get().launchBlockedParticipantsActivity(this);
+ }
+
+ public void onActionBarArchived() {
+ UIIntents.get().launchArchivedConversationsActivity(this);
+ }
+
+ @Override
+ public boolean isSwipeAnimatable() {
+ return !isInConversationListSelectMode();
+ }
+
+ @Override
+ public void onWindowFocusChanged(final boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ final ConversationListFragment conversationListFragment =
+ (ConversationListFragment) getFragmentManager().findFragmentById(
+ R.id.conversation_list_fragment);
+ // When the screen is turned on, the last used activity gets resumed, but it gets
+ // window focus only after the lock screen is unlocked.
+ if (hasFocus && conversationListFragment != null) {
+ conversationListFragment.setScrolledToNewestConversationIfNeeded();
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java b/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java
new file mode 100644
index 0000000..629c4ae
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java
@@ -0,0 +1,77 @@
+/*
+ * 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.conversationlist;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.CursorRecyclerAdapter;
+import com.android.messaging.ui.conversationlist.ConversationListItemView.HostInterface;
+
+/**
+ * Provides an interface to expose Conversation List Cursor data to a UI widget like a ListView.
+ */
+public class ConversationListAdapter
+ extends CursorRecyclerAdapter<ConversationListAdapter.ConversationListViewHolder> {
+
+ private final ConversationListItemView.HostInterface mClivHostInterface;
+
+ public ConversationListAdapter(final Context context, final Cursor cursor,
+ final ConversationListItemView.HostInterface clivHostInterface) {
+ super(context, cursor, 0);
+ mClivHostInterface = clivHostInterface;
+ setHasStableIds(true);
+ }
+
+ /**
+ * @see com.android.messaging.ui.CursorRecyclerAdapter#bindViewHolder(
+ * android.support.v7.widget.RecyclerView.ViewHolder, android.content.Context,
+ * android.database.Cursor)
+ */
+ @Override
+ public void bindViewHolder(final ConversationListViewHolder holder, final Context context,
+ final Cursor cursor) {
+ final ConversationListItemView conversationListItemView = holder.mView;
+ conversationListItemView.bind(cursor, mClivHostInterface);
+ }
+
+ @Override
+ public ConversationListViewHolder createViewHolder(final Context context,
+ final ViewGroup parent, final int viewType) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(context);
+ final ConversationListItemView itemView =
+ (ConversationListItemView) layoutInflater.inflate(
+ R.layout.conversation_list_item_view, null);
+ return new ConversationListViewHolder(itemView);
+ }
+
+ /**
+ * ViewHolder that holds a ConversationListItemView.
+ */
+ public static class ConversationListViewHolder extends RecyclerView.ViewHolder {
+ final ConversationListItemView mView;
+
+ public ConversationListViewHolder(final ConversationListItemView itemView) {
+ super(itemView);
+ mView = itemView;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java b/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java
new file mode 100644
index 0000000..2f868d4
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java
@@ -0,0 +1,446 @@
+/*
+ * 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.conversationlist;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewGroupCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewPropertyAnimator;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.AbsListView;
+import android.widget.ImageView;
+
+import com.android.messaging.R;
+import com.android.messaging.annotation.VisibleForAnimation;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.ui.BugleAnimationTags;
+import com.android.messaging.ui.ListEmptyView;
+import com.android.messaging.ui.SnackBarInteraction;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows a list of conversations.
+ */
+public class ConversationListFragment extends Fragment implements ConversationListDataListener,
+ ConversationListItemView.HostInterface {
+ private static final String BUNDLE_ARCHIVED_MODE = "archived_mode";
+ private static final String BUNDLE_FORWARD_MESSAGE_MODE = "forward_message_mode";
+ private static final boolean VERBOSE = false;
+
+ private MenuItem mShowBlockedMenuItem;
+ private boolean mArchiveMode;
+ private boolean mBlockedAvailable;
+ private boolean mForwardMessageMode;
+
+ public interface ConversationListFragmentHost {
+ public void onConversationClick(final ConversationListData listData,
+ final ConversationListItemData conversationListItemData,
+ final boolean isLongClick,
+ final ConversationListItemView conversationView);
+ public void onCreateConversationClick();
+ public boolean isConversationSelected(final String conversationId);
+ public boolean isSwipeAnimatable();
+ public boolean isSelectionMode();
+ public boolean hasWindowFocus();
+ }
+
+ private ConversationListFragmentHost mHost;
+ private RecyclerView mRecyclerView;
+ private ImageView mStartNewConversationButton;
+ private ListEmptyView mEmptyListMessageView;
+ private ConversationListAdapter mAdapter;
+
+ // Saved Instance State Data - only for temporal data which is nice to maintain but not
+ // critical for correctness.
+ private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY =
+ "conversationListViewState";
+ private Parcelable mListState;
+
+ @VisibleForTesting
+ final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
+
+ public static ConversationListFragment createArchivedConversationListFragment() {
+ return createConversationListFragment(BUNDLE_ARCHIVED_MODE);
+ }
+
+ public static ConversationListFragment createForwardMessageConversationListFragment() {
+ return createConversationListFragment(BUNDLE_FORWARD_MESSAGE_MODE);
+ }
+
+ public static ConversationListFragment createConversationListFragment(String modeKeyName) {
+ final ConversationListFragment fragment = new ConversationListFragment();
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(modeKeyName, true);
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public void onCreate(final Bundle bundle) {
+ super.onCreate(bundle);
+ mListBinding.getData().init(getLoaderManager(), mListBinding);
+ mAdapter = new ConversationListAdapter(getActivity(), null, this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ Assert.notNull(mHost);
+ setScrolledToNewestConversationIfNeeded();
+
+ updateUi();
+ }
+
+ public void setScrolledToNewestConversationIfNeeded() {
+ if (!mArchiveMode
+ && !mForwardMessageMode
+ && isScrolledToFirstConversation()
+ && mHost.hasWindowFocus()) {
+ mListBinding.getData().setScrolledToNewestConversation(true);
+ }
+ }
+
+ private boolean isScrolledToFirstConversation() {
+ int firstItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
+ .findFirstCompletelyVisibleItemPosition();
+ return firstItemPosition == 0;
+ }
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mListBinding.unbind();
+ mHost = null;
+ }
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.conversation_list_fragment,
+ container, false);
+ mRecyclerView = (RecyclerView) rootView.findViewById(android.R.id.list);
+ mEmptyListMessageView = (ListEmptyView) rootView.findViewById(R.id.no_conversations_view);
+ mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
+ // The default behavior for default layout param generation by LinearLayoutManager is to
+ // provide width and height of WRAP_CONTENT, but this is not desirable for
+ // ConversationListFragment; the view in each row should be a width of MATCH_PARENT so that
+ // the entire row is tappable.
+ final Activity activity = getActivity();
+ final LinearLayoutManager manager = new LinearLayoutManager(activity) {
+ @Override
+ public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+ return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ };
+ mRecyclerView.setLayoutManager(manager);
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setAdapter(mAdapter);
+ mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
+ int mCurrentState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
+
+ @Override
+ public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
+ if (mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
+ || mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+ ImeUtil.get().hideImeKeyboard(getActivity(), mRecyclerView);
+ }
+
+ if (isScrolledToFirstConversation()) {
+ setScrolledToNewestConversationIfNeeded();
+ } else {
+ mListBinding.getData().setScrolledToNewestConversation(false);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
+ mCurrentState = newState;
+ }
+ });
+ mRecyclerView.addOnItemTouchListener(new ConversationListSwipeHelper(mRecyclerView));
+
+ if (savedInstanceState != null) {
+ mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY);
+ }
+
+ mStartNewConversationButton = (ImageView) rootView.findViewById(
+ R.id.start_new_conversation_button);
+ if (mArchiveMode) {
+ mStartNewConversationButton.setVisibility(View.GONE);
+ } else {
+ mStartNewConversationButton.setVisibility(View.VISIBLE);
+ mStartNewConversationButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View clickView) {
+ mHost.onCreateConversationClick();
+ }
+ });
+ }
+ ViewCompat.setTransitionName(mStartNewConversationButton, BugleAnimationTags.TAG_FABICON);
+
+ // The root view has a non-null background, which by default is deemed by the framework
+ // to be a "transition group," where all child views are animated together during an
+ // activity transition. However, we want each individual items in the recycler view to
+ // show explode animation themselves, so we explicitly tag the root view to be a non-group.
+ ViewGroupCompat.setTransitionGroup(rootView, false);
+
+ setHasOptionsMenu(true);
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(final Activity activity) {
+ super.onAttach(activity);
+ if (VERBOSE) {
+ LogUtil.v(LogUtil.BUGLE_TAG, "Attaching List");
+ }
+ final Bundle arguments = getArguments();
+ if (arguments != null) {
+ mArchiveMode = arguments.getBoolean(BUNDLE_ARCHIVED_MODE, false);
+ mForwardMessageMode = arguments.getBoolean(BUNDLE_FORWARD_MESSAGE_MODE, false);
+ }
+ mListBinding.bind(DataModel.get().createConversationListData(activity, this, mArchiveMode));
+ }
+
+
+ @Override
+ public void onSaveInstanceState(final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mListState != null) {
+ outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
+ mListBinding.getData().setScrolledToNewestConversation(false);
+ }
+
+ /**
+ * Call this immediately after attaching the fragment
+ */
+ public void setHost(final ConversationListFragmentHost host) {
+ Assert.isNull(mHost);
+ mHost = host;
+ }
+
+ @Override
+ public void onConversationListCursorUpdated(final ConversationListData data,
+ final Cursor cursor) {
+ mListBinding.ensureBound(data);
+ final Cursor oldCursor = mAdapter.swapCursor(cursor);
+ updateEmptyListUi(cursor == null || cursor.getCount() == 0);
+ if (mListState != null && cursor != null && oldCursor == null) {
+ mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
+ }
+ }
+
+ @Override
+ public void setBlockedParticipantsAvailable(final boolean blockedAvailable) {
+ mBlockedAvailable = blockedAvailable;
+ if (mShowBlockedMenuItem != null) {
+ mShowBlockedMenuItem.setVisible(blockedAvailable);
+ }
+ }
+
+ public void updateUi() {
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(final Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ final MenuItem startNewConversationMenuItem =
+ menu.findItem(R.id.action_start_new_conversation);
+ if (startNewConversationMenuItem != null) {
+ // It is recommended for the Floating Action button functionality to be duplicated as a
+ // menu
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getActivity().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ startNewConversationMenuItem.setVisible(accessibilityManager
+ .isTouchExplorationEnabled());
+ }
+
+ final MenuItem archive = menu.findItem(R.id.action_show_archived);
+ if (archive != null) {
+ archive.setVisible(true);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ if (!isAdded()) {
+ // Guard against being called before we're added to the activity
+ return;
+ }
+
+ mShowBlockedMenuItem = menu.findItem(R.id.action_show_blocked_contacts);
+ if (mShowBlockedMenuItem != null) {
+ mShowBlockedMenuItem.setVisible(mBlockedAvailable);
+ }
+ }
+
+ /**
+ * {@inheritDoc} from ConversationListItemView.HostInterface
+ */
+ @Override
+ public void onConversationClicked(final ConversationListItemData conversationListItemData,
+ final boolean isLongClick, final ConversationListItemView conversationView) {
+ final ConversationListData listData = mListBinding.getData();
+ mHost.onConversationClick(listData, conversationListItemData, isLongClick,
+ conversationView);
+ }
+
+ /**
+ * {@inheritDoc} from ConversationListItemView.HostInterface
+ */
+ @Override
+ public boolean isConversationSelected(final String conversationId) {
+ return mHost.isConversationSelected(conversationId);
+ }
+
+ @Override
+ public boolean isSwipeAnimatable() {
+ return mHost.isSwipeAnimatable();
+ }
+
+ // Show and hide empty list UI as needed with appropriate text based on view specifics
+ private void updateEmptyListUi(final boolean isEmpty) {
+ if (isEmpty) {
+ int emptyListText;
+ if (!mListBinding.getData().getHasFirstSyncCompleted()) {
+ emptyListText = R.string.conversation_list_first_sync_text;
+ } else if (mArchiveMode) {
+ emptyListText = R.string.archived_conversation_list_empty_text;
+ } else {
+ emptyListText = R.string.conversation_list_empty_text;
+ }
+ mEmptyListMessageView.setTextHint(emptyListText);
+ mEmptyListMessageView.setVisibility(View.VISIBLE);
+ mEmptyListMessageView.setIsImageVisible(true);
+ mEmptyListMessageView.setIsVerticallyCentered(true);
+ } else {
+ mEmptyListMessageView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public List<SnackBarInteraction> getSnackBarInteractions() {
+ final List<SnackBarInteraction> interactions = new ArrayList<SnackBarInteraction>(1);
+ final SnackBarInteraction fabInteraction =
+ new SnackBarInteraction.BasicSnackBarInteraction(mStartNewConversationButton);
+ interactions.add(fabInteraction);
+ return interactions;
+ }
+
+ private ViewPropertyAnimator getNormalizedFabAnimator() {
+ return mStartNewConversationButton.animate()
+ .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
+ .setDuration(getActivity().getResources().getInteger(
+ R.integer.fab_animation_duration_ms));
+ }
+
+ public ViewPropertyAnimator dismissFab() {
+ // To prevent clicking while animating.
+ mStartNewConversationButton.setEnabled(false);
+ final MarginLayoutParams lp =
+ (MarginLayoutParams) mStartNewConversationButton.getLayoutParams();
+ final float fabWidthWithLeftRightMargin = mStartNewConversationButton.getWidth()
+ + lp.leftMargin + lp.rightMargin;
+ final int direction = AccessibilityUtil.isLayoutRtl(mStartNewConversationButton) ? -1 : 1;
+ return getNormalizedFabAnimator().translationX(direction * fabWidthWithLeftRightMargin);
+ }
+
+ public ViewPropertyAnimator showFab() {
+ return getNormalizedFabAnimator().translationX(0).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ // Re-enable clicks after the animation.
+ mStartNewConversationButton.setEnabled(true);
+ }
+ });
+ }
+
+ public View getHeroElementForTransition() {
+ return mArchiveMode ? null : mStartNewConversationButton;
+ }
+
+ @VisibleForAnimation
+ public RecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ @Override
+ public void startFullScreenPhotoViewer(
+ final Uri initialPhoto, final Rect initialPhotoBounds, final Uri photosUri) {
+ UIIntents.get().launchFullScreenPhotoViewer(
+ getActivity(), initialPhoto, initialPhotoBounds, photosUri);
+ }
+
+ @Override
+ public void startFullScreenVideoViewer(final Uri videoUri) {
+ UIIntents.get().launchFullScreenVideoViewer(getActivity(), videoUri);
+ }
+
+ @Override
+ public boolean isSelectionMode() {
+ return mHost != null && mHost.isSelectionMode();
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
new file mode 100644
index 0000000..7525182
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
@@ -0,0 +1,643 @@
+/*
+ * 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.conversationlist;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.support.v4.text.BidiFormatter;
+import android.support.v4.text.TextDirectionHeuristicsCompat;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.annotation.VisibleForAnimation;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.AsyncImageView;
+import com.android.messaging.ui.AudioAttachmentView;
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.ui.SnackBar;
+import com.android.messaging.ui.SnackBarInteraction;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.OsUtil;
+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 java.util.List;
+
+/**
+ * The view for a single entry in a conversation list.
+ */
+public class ConversationListItemView extends FrameLayout implements OnClickListener,
+ OnLongClickListener, OnLayoutChangeListener {
+ static final int UNREAD_SNIPPET_LINE_COUNT = 3;
+ static final int NO_UNREAD_SNIPPET_LINE_COUNT = 1;
+ private int mListItemReadColor;
+ private int mListItemUnreadColor;
+ private Typeface mListItemReadTypeface;
+ private Typeface mListItemUnreadTypeface;
+ private static String sPlusOneString;
+ private static String sPlusNString;
+
+ public interface HostInterface {
+ boolean isConversationSelected(final String conversationId);
+ void onConversationClicked(final ConversationListItemData conversationListItemData,
+ boolean isLongClick, final ConversationListItemView conversationView);
+ boolean isSwipeAnimatable();
+ List<SnackBarInteraction> getSnackBarInteractions();
+ void startFullScreenPhotoViewer(final Uri initialPhoto, final Rect initialPhotoBounds,
+ final Uri photosUri);
+ void startFullScreenVideoViewer(final Uri videoUri);
+ boolean isSelectionMode();
+ }
+
+ private final OnClickListener fullScreenPreviewClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ final String previewType = mData.getShowDraft() ?
+ mData.getDraftPreviewContentType() : mData.getPreviewContentType();
+ Assert.isTrue(ContentType.isImageType(previewType) ||
+ ContentType.isVideoType(previewType));
+
+ final Uri previewUri = mData.getShowDraft() ?
+ mData.getDraftPreviewUri() : mData.getPreviewUri();
+ if (ContentType.isImageType(previewType)) {
+ final Uri imagesUri = mData.getShowDraft() ?
+ MessagingContentProvider.buildDraftImagesUri(mData.getConversationId()) :
+ MessagingContentProvider
+ .buildConversationImagesUri(mData.getConversationId());
+ final Rect previewImageBounds = UiUtils.getMeasuredBoundsOnScreen(v);
+ mHostInterface.startFullScreenPhotoViewer(
+ previewUri, previewImageBounds, imagesUri);
+ } else {
+ mHostInterface.startFullScreenVideoViewer(previewUri);
+ }
+ }
+ };
+
+ private final ConversationListItemData mData;
+
+ private int mAnimatingCount;
+ private ViewGroup mSwipeableContainer;
+ private ViewGroup mCrossSwipeBackground;
+ private ViewGroup mSwipeableContent;
+ private TextView mConversationNameView;
+ private TextView mSnippetTextView;
+ private TextView mSubjectTextView;
+ private TextView mTimestampTextView;
+ private ContactIconView mContactIconView;
+ private ImageView mContactCheckmarkView;
+ private ImageView mNotificationBellView;
+ private ImageView mFailedStatusIconView;
+ private ImageView mCrossSwipeArchiveLeftImageView;
+ private ImageView mCrossSwipeArchiveRightImageView;
+ private AsyncImageView mImagePreviewView;
+ private AudioAttachmentView mAudioAttachmentView;
+ private HostInterface mHostInterface;
+
+ public ConversationListItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = new ConversationListItemData();
+ final Resources res = context.getResources();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mSwipeableContainer = (ViewGroup) findViewById(R.id.swipeableContainer);
+ mCrossSwipeBackground = (ViewGroup) findViewById(R.id.crossSwipeBackground);
+ mSwipeableContent = (ViewGroup) findViewById(R.id.swipeableContent);
+ mConversationNameView = (TextView) findViewById(R.id.conversation_name);
+ mSnippetTextView = (TextView) findViewById(R.id.conversation_snippet);
+ mSubjectTextView = (TextView) findViewById(R.id.conversation_subject);
+ mTimestampTextView = (TextView) findViewById(R.id.conversation_timestamp);
+ mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
+ mContactCheckmarkView = (ImageView) findViewById(R.id.conversation_checkmark);
+ mNotificationBellView = (ImageView) findViewById(R.id.conversation_notification_bell);
+ mFailedStatusIconView = (ImageView) findViewById(R.id.conversation_failed_status_icon);
+ mCrossSwipeArchiveLeftImageView = (ImageView) findViewById(R.id.crossSwipeArchiveIconLeft);
+ mCrossSwipeArchiveRightImageView =
+ (ImageView) findViewById(R.id.crossSwipeArchiveIconRight);
+ mImagePreviewView = (AsyncImageView) findViewById(R.id.conversation_image_preview);
+ mAudioAttachmentView = (AudioAttachmentView) findViewById(R.id.audio_attachment_view);
+ mConversationNameView.addOnLayoutChangeListener(this);
+ mSnippetTextView.addOnLayoutChangeListener(this);
+
+ final Resources resources = getContext().getResources();
+ mListItemReadColor = resources.getColor(R.color.conversation_list_item_read);
+ mListItemUnreadColor = resources.getColor(R.color.conversation_list_item_unread);
+
+ mListItemReadTypeface = Typefaces.getRobotoNormal();
+ mListItemUnreadTypeface = Typefaces.getRobotoBold();
+
+ if (OsUtil.isAtLeastL()) {
+ setTransitionGroup(true);
+ }
+ }
+
+ @Override
+ public void onLayoutChange(final View v, final int left, final int top, final int right,
+ final int bottom, final int oldLeft, final int oldTop, final int oldRight,
+ final int oldBottom) {
+ if (v == mConversationNameView) {
+ setConversationName();
+ } else if (v == mSnippetTextView) {
+ setSnippet();
+ } else if (v == mSubjectTextView) {
+ setSubject();
+ }
+ }
+
+ private void setConversationName() {
+ if (mData.getIsRead() || mData.getShowDraft()) {
+ mConversationNameView.setTextColor(mListItemReadColor);
+ mConversationNameView.setTypeface(mListItemReadTypeface);
+ } else {
+ mConversationNameView.setTextColor(mListItemUnreadColor);
+ mConversationNameView.setTypeface(mListItemUnreadTypeface);
+ }
+
+ final String conversationName = mData.getName();
+
+ // For group conversations, ellipsize the group members that do not fit
+ final CharSequence ellipsizedName = UiUtils.commaEllipsize(
+ conversationName,
+ mConversationNameView.getPaint(),
+ mConversationNameView.getMeasuredWidth(),
+ getPlusOneString(),
+ getPlusNString());
+ // RTL : To format conversation name if it happens to be phone number.
+ final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ final String bidiFormattedName = bidiFormatter.unicodeWrap(
+ ellipsizedName.toString(),
+ TextDirectionHeuristicsCompat.LTR);
+
+ mConversationNameView.setText(bidiFormattedName);
+ }
+
+ private static String getPlusOneString() {
+ if (sPlusOneString == null) {
+ sPlusOneString = Factory.get().getApplicationContext().getResources()
+ .getString(R.string.plus_one);
+ }
+ return sPlusOneString;
+ }
+
+ private static String getPlusNString() {
+ if (sPlusNString == null) {
+ sPlusNString = Factory.get().getApplicationContext().getResources()
+ .getString(R.string.plus_n);
+ }
+ return sPlusNString;
+ }
+
+ private void setSubject() {
+ final String subjectText = mData.getShowDraft() ?
+ mData.getDraftSubject() :
+ MmsUtils.cleanseMmsSubject(getContext().getResources(), mData.getSubject());
+ if (!TextUtils.isEmpty(subjectText)) {
+ final String subjectPrepend = getResources().getString(R.string.subject_label);
+ mSubjectTextView.setText(TextUtils.concat(subjectPrepend, subjectText));
+ mSubjectTextView.setVisibility(VISIBLE);
+ } else {
+ mSubjectTextView.setVisibility(GONE);
+ }
+ }
+
+ private void setSnippet() {
+ mSnippetTextView.setText(getSnippetText());
+ }
+
+ // Resource Ids of content descriptions prefixes for different message status.
+ private static final int [][][] sPrimaryContentDescriptions = {
+ // 1:1 conversation
+ {
+ // Incoming message
+ {
+ R.string.one_on_one_incoming_failed_message_prefix,
+ R.string.one_on_one_incoming_successful_message_prefix
+ },
+ // Outgoing message
+ {
+ R.string.one_on_one_outgoing_failed_message_prefix,
+ R.string.one_on_one_outgoing_successful_message_prefix,
+ R.string.one_on_one_outgoing_draft_message_prefix,
+ R.string.one_on_one_outgoing_sending_message_prefix,
+ }
+ },
+
+ // Group conversation
+ {
+ // Incoming message
+ {
+ R.string.group_incoming_failed_message_prefix,
+ R.string.group_incoming_successful_message_prefix,
+ },
+ // Outgoing message
+ {
+ R.string.group_outgoing_failed_message_prefix,
+ R.string.group_outgoing_successful_message_prefix,
+ R.string.group_outgoing_draft_message_prefix,
+ R.string.group_outgoing_sending_message_prefix,
+ }
+ }
+ };
+
+ // Resource Id of the secondary part of the content description for an edge case of a message
+ // which is in both draft status and failed status.
+ private static final int sSecondaryContentDescription =
+ R.string.failed_message_content_description;
+
+ // 1:1 versus group
+ private static final int CONV_TYPE_ONE_ON_ONE_INDEX = 0;
+ private static final int CONV_TYPE_ONE_GROUP_INDEX = 1;
+ // Direction
+ private static final int DIRECTION_INCOMING_INDEX = 0;
+ private static final int DIRECTION_OUTGOING_INDEX = 1;
+ // Message status
+ private static final int MESSAGE_STATUS_FAILED_INDEX = 0;
+ private static final int MESSAGE_STATUS_SUCCESSFUL_INDEX = 1;
+ private static final int MESSAGE_STATUS_DRAFT_INDEX = 2;
+ private static final int MESSAGE_STATUS_SENDING_INDEX = 3;
+
+ private static final int WIDTH_FOR_ACCESSIBLE_CONVERSATION_NAME = 600;
+
+ public static String buildContentDescription(final Resources resources,
+ final ConversationListItemData data, final TextPaint conversationNameViewPaint) {
+ int messageStatusIndex;
+ boolean outgoingSnippet = data.getIsMessageTypeOutgoing() || data.getShowDraft();
+ if (outgoingSnippet) {
+ if (data.getShowDraft()) {
+ messageStatusIndex = MESSAGE_STATUS_DRAFT_INDEX;
+ } else if (data.getIsSendRequested()) {
+ messageStatusIndex = MESSAGE_STATUS_SENDING_INDEX;
+ } else {
+ messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
+ : MESSAGE_STATUS_SUCCESSFUL_INDEX;
+ }
+ } else {
+ messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
+ : MESSAGE_STATUS_SUCCESSFUL_INDEX;
+ }
+
+ int resId = sPrimaryContentDescriptions
+ [data.getIsGroup() ? CONV_TYPE_ONE_GROUP_INDEX : CONV_TYPE_ONE_ON_ONE_INDEX]
+ [outgoingSnippet ? DIRECTION_OUTGOING_INDEX : DIRECTION_INCOMING_INDEX]
+ [messageStatusIndex];
+
+ final String snippetText = data.getShowDraft() ?
+ data.getDraftSnippetText() : data.getSnippetText();
+
+ final String conversationName = data.getName();
+ String senderOrConvName = outgoingSnippet ? conversationName : data.getSnippetSenderName();
+
+ String primaryContentDescription = resources.getString(resId, senderOrConvName,
+ snippetText == null ? "" : snippetText,
+ data.getFormattedTimestamp(),
+ // This is used only for incoming group messages
+ conversationName);
+ String contentDescription = primaryContentDescription;
+
+ // An edge case : for an outgoing message, it might be in both draft status and
+ // failed status.
+ if (outgoingSnippet && data.getShowDraft() && data.getIsFailedStatus()) {
+ StringBuilder contentDescriptionBuilder = new StringBuilder();
+ contentDescriptionBuilder.append(primaryContentDescription);
+
+ String secondaryContentDescription =
+ resources.getString(sSecondaryContentDescription);
+ contentDescriptionBuilder.append(" ");
+ contentDescriptionBuilder.append(secondaryContentDescription);
+ contentDescription = contentDescriptionBuilder.toString();
+ }
+ return contentDescription;
+ }
+
+ /**
+ * Fills in the data associated with this view.
+ *
+ * @param cursor The cursor from a ConversationList that this view is in, pointing to its
+ * entry.
+ */
+ public void bind(final Cursor cursor, final HostInterface hostInterface) {
+ // Update our UI model
+ mHostInterface = hostInterface;
+ mData.bind(cursor);
+
+ resetAnimatingState();
+
+ mSwipeableContainer.setOnClickListener(this);
+ mSwipeableContainer.setOnLongClickListener(this);
+
+ final Resources resources = getContext().getResources();
+
+ int color;
+ final int maxLines;
+ final Typeface typeface;
+ final int typefaceStyle = mData.getShowDraft() ? Typeface.ITALIC : Typeface.NORMAL;
+ final String snippetText = getSnippetText();
+
+ if (mData.getIsRead() || mData.getShowDraft()) {
+ maxLines = TextUtils.isEmpty(snippetText) ? 0 : NO_UNREAD_SNIPPET_LINE_COUNT;
+ color = mListItemReadColor;
+ typeface = mListItemReadTypeface;
+ } else {
+ maxLines = TextUtils.isEmpty(snippetText) ? 0 : UNREAD_SNIPPET_LINE_COUNT;
+ color = mListItemUnreadColor;
+ typeface = mListItemUnreadTypeface;
+ }
+
+ mSnippetTextView.setMaxLines(maxLines);
+ mSnippetTextView.setTextColor(color);
+ mSnippetTextView.setTypeface(typeface, typefaceStyle);
+ mSubjectTextView.setTextColor(color);
+ mSubjectTextView.setTypeface(typeface, typefaceStyle);
+
+ setSnippet();
+ setConversationName();
+ setSubject();
+ setContentDescription(buildContentDescription(resources, mData,
+ mConversationNameView.getPaint()));
+
+ final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
+ // don't show the error state unless we're the default sms app
+ if (mData.getIsFailedStatus() && isDefaultSmsApp) {
+ mTimestampTextView.setTextColor(resources.getColor(R.color.conversation_list_error));
+ mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
+ int failureMessageId = R.string.message_status_download_failed;
+ if (mData.getIsMessageTypeOutgoing()) {
+ failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(mData.getMessageStatus(),
+ mData.getMessageRawTelephonyStatus());
+ }
+ mTimestampTextView.setText(resources.getString(failureMessageId));
+ } else if (mData.getShowDraft()
+ || mData.getMessageStatus() == MessageData.BUGLE_STATUS_OUTGOING_DRAFT
+ // also check for unknown status which we get because sometimes the conversation
+ // row is left with a latest_message_id of a no longer existing message and
+ // therefore the join values come back as null (or in this case zero).
+ || mData.getMessageStatus() == MessageData.BUGLE_STATUS_UNKNOWN) {
+ mTimestampTextView.setTextColor(mListItemReadColor);
+ mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
+ mTimestampTextView.setText(resources.getString(
+ R.string.conversation_list_item_view_draft_message));
+ } else {
+ mTimestampTextView.setTextColor(mListItemReadColor);
+ mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
+ final String formattedTimestamp = mData.getFormattedTimestamp();
+ if (mData.getIsSendRequested()) {
+ mTimestampTextView.setText(R.string.message_status_sending);
+ } else {
+ mTimestampTextView.setText(formattedTimestamp);
+ }
+ }
+
+ final boolean isSelected = mHostInterface.isConversationSelected(mData.getConversationId());
+ setSelected(isSelected);
+ Uri iconUri = null;
+ int contactIconVisibility = GONE;
+ int checkmarkVisiblity = GONE;
+ int failStatusVisiblity = GONE;
+ if (isSelected) {
+ checkmarkVisiblity = VISIBLE;
+ } else {
+ contactIconVisibility = VISIBLE;
+ // Only show the fail icon if it is not a group conversation.
+ // And also require that we be the default sms app.
+ if (mData.getIsFailedStatus() && !mData.getIsGroup() && isDefaultSmsApp) {
+ failStatusVisiblity = VISIBLE;
+ }
+ }
+ if (mData.getIcon() != null) {
+ iconUri = Uri.parse(mData.getIcon());
+ }
+ mContactIconView.setImageResourceUri(iconUri, mData.getParticipantContactId(),
+ mData.getParticipantLookupKey(), mData.getOtherParticipantNormalizedDestination());
+ mContactIconView.setVisibility(contactIconVisibility);
+ mContactIconView.setOnLongClickListener(this);
+ mContactIconView.setClickable(!mHostInterface.isSelectionMode());
+ mContactIconView.setLongClickable(!mHostInterface.isSelectionMode());
+
+ mContactCheckmarkView.setVisibility(checkmarkVisiblity);
+ mFailedStatusIconView.setVisibility(failStatusVisiblity);
+
+ final Uri previewUri = mData.getShowDraft() ?
+ mData.getDraftPreviewUri() : mData.getPreviewUri();
+ final String previewContentType = mData.getShowDraft() ?
+ mData.getDraftPreviewContentType() : mData.getPreviewContentType();
+ OnClickListener previewClickListener = null;
+ Uri previewImageUri = null;
+ int previewImageVisibility = GONE;
+ int audioPreviewVisiblity = GONE;
+ if (previewUri != null && !TextUtils.isEmpty(previewContentType)) {
+ if (ContentType.isAudioType(previewContentType)) {
+ mAudioAttachmentView.bind(previewUri, false);
+ audioPreviewVisiblity = VISIBLE;
+ } else if (ContentType.isVideoType(previewContentType)) {
+ previewImageUri = UriUtil.getUriForResourceId(
+ getContext(), R.drawable.ic_preview_play);
+ previewClickListener = fullScreenPreviewClickListener;
+ previewImageVisibility = VISIBLE;
+ } else if (ContentType.isImageType(previewContentType)) {
+ previewImageUri = previewUri;
+ previewClickListener = fullScreenPreviewClickListener;
+ previewImageVisibility = VISIBLE;
+ }
+ }
+
+ final int imageSize = resources.getDimensionPixelSize(
+ R.dimen.conversation_list_image_preview_size);
+ mImagePreviewView.setImageResourceId(
+ new UriImageRequestDescriptor(previewImageUri, imageSize, imageSize,
+ true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */));
+ mImagePreviewView.setOnLongClickListener(this);
+ mImagePreviewView.setVisibility(previewImageVisibility);
+ mImagePreviewView.setOnClickListener(previewClickListener);
+ mAudioAttachmentView.setOnLongClickListener(this);
+ mAudioAttachmentView.setVisibility(audioPreviewVisiblity);
+
+ final int notificationBellVisiblity = mData.getNotificationEnabled() ? GONE : VISIBLE;
+ mNotificationBellView.setVisibility(notificationBellVisiblity);
+ }
+
+ public boolean isSwipeAnimatable() {
+ return mHostInterface.isSwipeAnimatable();
+ }
+
+ @VisibleForAnimation
+ public float getSwipeTranslationX() {
+ return mSwipeableContainer.getTranslationX();
+ }
+
+ @VisibleForAnimation
+ public void setSwipeTranslationX(final float translationX) {
+ mSwipeableContainer.setTranslationX(translationX);
+ if (translationX == 0) {
+ mCrossSwipeBackground.setVisibility(View.GONE);
+ mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
+ mCrossSwipeArchiveRightImageView.setVisibility(GONE);
+
+ mSwipeableContainer.setBackgroundColor(Color.TRANSPARENT);
+ } else {
+ mCrossSwipeBackground.setVisibility(View.VISIBLE);
+ if (translationX > 0) {
+ mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE);
+ mCrossSwipeArchiveRightImageView.setVisibility(GONE);
+ } else {
+ mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
+ mCrossSwipeArchiveRightImageView.setVisibility(VISIBLE);
+ }
+ mSwipeableContainer.setBackgroundResource(R.drawable.swipe_shadow_drag);
+ }
+ }
+
+ public void onSwipeComplete() {
+ final String conversationId = mData.getConversationId();
+ UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
+
+ final Runnable undoRunnable = new Runnable() {
+ @Override
+ public void run() {
+ UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
+ }
+ };
+ final String message = getResources().getString(R.string.archived_toast_message, 1);
+ UiUtils.showSnackBar(getContext(), getRootView(), message, undoRunnable,
+ SnackBar.Action.SNACK_BAR_UNDO,
+ mHostInterface.getSnackBarInteractions());
+ }
+
+ private void setShortAndLongClickable(final boolean clickable) {
+ setClickable(clickable);
+ setLongClickable(clickable);
+ }
+
+ private void resetAnimatingState() {
+ mAnimatingCount = 0;
+ setShortAndLongClickable(true);
+ setSwipeTranslationX(0);
+ }
+
+ /**
+ * Notifies this view that it is undergoing animation. This view should disable its click
+ * targets.
+ *
+ * The animating counter is used to reset the swipe controller when the counter becomes 0. A
+ * positive counter also makes the view not clickable.
+ */
+ public final void setAnimating(final boolean animating) {
+ final int oldAnimatingCount = mAnimatingCount;
+ if (animating) {
+ mAnimatingCount++;
+ } else {
+ mAnimatingCount--;
+ if (mAnimatingCount < 0) {
+ mAnimatingCount = 0;
+ }
+ }
+
+ if (mAnimatingCount == 0) {
+ // New count is 0. All animations ended.
+ setShortAndLongClickable(true);
+ } else if (oldAnimatingCount == 0) {
+ // New count is > 0. Waiting for some animations to end.
+ setShortAndLongClickable(false);
+ }
+ }
+
+ public boolean isAnimating() {
+ return mAnimatingCount > 0;
+ }
+
+ /**
+ * {@inheritDoc} from OnClickListener
+ */
+ @Override
+ public void onClick(final View v) {
+ processClick(v, false);
+ }
+
+ /**
+ * {@inheritDoc} from OnLongClickListener
+ */
+ @Override
+ public boolean onLongClick(final View v) {
+ return processClick(v, true);
+ }
+
+ private boolean processClick(final View v, final boolean isLongClick) {
+ Assert.isTrue(v == mSwipeableContainer || v == mContactIconView || v == mImagePreviewView);
+ Assert.notNull(mData.getName());
+
+ if (mHostInterface != null) {
+ mHostInterface.onConversationClicked(mData, isLongClick, this);
+ return true;
+ }
+ return false;
+ }
+
+ public View getSwipeableContent() {
+ return mSwipeableContent;
+ }
+
+ public View getContactIconView() {
+ return mContactIconView;
+ }
+
+ private String getSnippetText() {
+ String snippetText = mData.getShowDraft() ?
+ mData.getDraftSnippetText() : mData.getSnippetText();
+ final String previewContentType = mData.getShowDraft() ?
+ mData.getDraftPreviewContentType() : mData.getPreviewContentType();
+ if (TextUtils.isEmpty(snippetText)) {
+ Resources resources = getResources();
+ // Use the attachment type as a snippet so the preview doesn't look odd
+ if (ContentType.isAudioType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_audio_clip);
+ } else if (ContentType.isImageType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_picture);
+ } else if (ContentType.isVideoType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_video);
+ } else if (ContentType.isVCardType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
+ }
+ }
+ return snippetText;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
new file mode 100644
index 0000000..4988259
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
@@ -0,0 +1,462 @@
+/*
+ * 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.conversationlist;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.messaging.R;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Animation and touch helper class for Conversation List swipe.
+ */
+public class ConversationListSwipeHelper implements OnItemTouchListener {
+ private static final int UNIT_SECONDS = 1000;
+ private static final boolean ANIMATING = true;
+
+ private static final float ERROR_FACTOR_MULTIPLIER = 1.2f;
+ 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;
+
+ private final RecyclerView mRecyclerView;
+ private final long mDefaultRestoreAnimationDuration;
+ private final long mDefaultDismissAnimationDuration;
+ private final long mMaxTranslationAnimationDuration;
+ private final int mTouchSlop;
+ private final int mMinimumFlingVelocity;
+ private final int mMaximumFlingVelocity;
+
+ /* Valid throughout a single gesture. */
+ private VelocityTracker mVelocityTracker;
+ private float mInitialX;
+ private float mInitialY;
+ private boolean mIsSwiping;
+ private ConversationListItemView mListItemView;
+
+ public ConversationListSwipeHelper(final RecyclerView recyclerView) {
+ mRecyclerView = recyclerView;
+
+ final Context context = mRecyclerView.getContext();
+ final Resources res = context.getResources();
+ mDefaultRestoreAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
+ mDefaultDismissAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
+ mMaxTranslationAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
+
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
+ mTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
+ mMaximumFlingVelocity = Math.min(
+ viewConfiguration.getScaledMaximumFlingVelocity(),
+ res.getInteger(R.integer.swipe_max_fling_velocity_px_per_s));
+ mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(final RecyclerView recyclerView, final MotionEvent event) {
+ if (event.getPointerCount() > 1) {
+ // Ignore subsequent pointers.
+ return false;
+ }
+
+ // We are not yet tracking a swipe gesture. Begin detection by spying on
+ // touch events bubbling down to our children.
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (!hasGestureSwipeTarget()) {
+ onGestureStart();
+
+ mVelocityTracker.addMovement(event);
+ mInitialX = event.getX();
+ mInitialY = event.getY();
+
+ final View viewAtPoint = mRecyclerView.findChildViewUnder(mInitialX, mInitialY);
+ final ConversationListItemView child = (ConversationListItemView) viewAtPoint;
+ if (viewAtPoint instanceof ConversationListItemView &&
+ child != null && child.isSwipeAnimatable()) {
+ // Begin detecting swipe on the target for the rest of the gesture.
+ mListItemView = child;
+ if (mListItemView.isAnimating()) {
+ mListItemView = null;
+ }
+ } else {
+ mListItemView = null;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (hasValidGestureSwipeTarget()) {
+ mVelocityTracker.addMovement(event);
+
+ final int historicalCount = event.getHistorySize();
+ // First consume the historical events, then consume the current ones.
+ for (int i = 0; i < historicalCount + 1; i++) {
+ float currX;
+ float currY;
+ if (i < historicalCount) {
+ currX = event.getHistoricalX(i);
+ currY = event.getHistoricalY(i);
+ } else {
+ currX = event.getX();
+ currY = event.getY();
+ }
+ final float deltaX = currX - mInitialX;
+ final float deltaY = currY - mInitialY;
+ final float absDeltaX = Math.abs(deltaX);
+ final float absDeltaY = Math.abs(deltaY);
+
+ if (!mIsSwiping && absDeltaY > mTouchSlop
+ && absDeltaY > (ERROR_FACTOR_MULTIPLIER * absDeltaX)) {
+ // Stop detecting swipe for the remainder of this gesture.
+ onGestureEnd();
+ return false;
+ }
+
+ if (absDeltaX > mTouchSlop) {
+ // Swipe detected. Return true so we can handle the gesture in
+ // onTouchEvent.
+ mIsSwiping = true;
+
+ // We don't want to suddenly jump the slop distance.
+ mInitialX = event.getX();
+ mInitialY = event.getY();
+
+ onSwipeGestureStart(mListItemView);
+ return true;
+ }
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (hasGestureSwipeTarget()) {
+ onGestureEnd();
+ }
+ break;
+ }
+
+ // Start intercepting touch events from children if we detect a swipe.
+ return mIsSwiping;
+ }
+
+ @Override
+ public void onTouchEvent(final RecyclerView recyclerView, final MotionEvent event) {
+ // We should only be here if we intercepted the touch due to swipe.
+ Assert.isTrue(mIsSwiping);
+
+ // We are now tracking a swipe gesture.
+ mVelocityTracker.addMovement(event);
+
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_OUTSIDE:
+ case MotionEvent.ACTION_MOVE:
+ if (hasValidGestureSwipeTarget()) {
+ mListItemView.setSwipeTranslationX(event.getX() - mInitialX);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (hasValidGestureSwipeTarget()) {
+ final float maxVelocity = mMaximumFlingVelocity;
+ mVelocityTracker.computeCurrentVelocity(UNIT_SECONDS, maxVelocity);
+ final float velocityX = getLastComputedXVelocity();
+
+ final float translationX = mListItemView.getSwipeTranslationX();
+
+ int swipeDirection = SWIPE_DIRECTION_NONE;
+ if (translationX != 0) {
+ swipeDirection =
+ translationX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
+ } else if (velocityX != 0) {
+ swipeDirection =
+ velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
+ }
+
+ final boolean fastEnough = isTargetSwipedFastEnough();
+ final boolean farEnough = isTargetSwipedFarEnough();
+
+ final boolean shouldDismiss = (fastEnough || farEnough);
+
+ if (shouldDismiss) {
+ if (fastEnough) {
+ animateDismiss(mListItemView, velocityX);
+ } else {
+ animateDismiss(mListItemView, swipeDirection);
+ }
+ } else {
+ animateRestore(mListItemView, velocityX);
+ }
+
+ onSwipeGestureEnd(mListItemView,
+ shouldDismiss ? swipeDirection : SWIPE_DIRECTION_NONE);
+ } else {
+ onGestureEnd();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (hasValidGestureSwipeTarget()) {
+ animateRestore(mListItemView, 0f);
+ onSwipeGestureEnd(mListItemView, SWIPE_DIRECTION_NONE);
+ } else {
+ onGestureEnd();
+ }
+ break;
+ }
+ }
+
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ }
+
+ /**
+ * We have started to intercept a series of touch events.
+ */
+ private void onGestureStart() {
+ mIsSwiping = false;
+ // Work around bug in RecyclerView that sends two identical ACTION_DOWN
+ // events to #onInterceptTouchEvent.
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.clear();
+ }
+
+ /**
+ * The series of touch events has been detected as a swipe.
+ *
+ * Now that the gesture is a swipe, we will begin translating the view of the
+ * given viewHolder.
+ */
+ private void onSwipeGestureStart(final ConversationListItemView itemView) {
+ mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
+ setHardwareAnimatingLayerType(itemView, ANIMATING);
+ itemView.setAnimating(true);
+ }
+
+ /**
+ * The current swipe gesture is complete.
+ */
+ private void onSwipeGestureEnd(final ConversationListItemView itemView,
+ final int swipeDirection) {
+ if (swipeDirection == SWIPE_DIRECTION_RIGHT || swipeDirection == SWIPE_DIRECTION_LEFT) {
+ itemView.onSwipeComplete();
+ }
+
+ // Balances out onSwipeGestureStart.
+ itemView.setAnimating(false);
+
+ onGestureEnd();
+ }
+
+ /**
+ * The series of touch events has ended in an {@link MotionEvent#ACTION_UP}
+ * or {@link MotionEvent#ACTION_CANCEL}.
+ */
+ private void onGestureEnd() {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ mIsSwiping = false;
+ mListItemView = null;
+ }
+
+ /**
+ * A swipe animation has started.
+ */
+ private void onSwipeAnimationStart(final ConversationListItemView itemView) {
+ // Disallow interactions.
+ itemView.setAnimating(true);
+ ViewCompat.setHasTransientState(itemView, true);
+ setHardwareAnimatingLayerType(itemView, ANIMATING);
+ }
+
+ /**
+ * The swipe animation has ended.
+ */
+ private void onSwipeAnimationEnd(final ConversationListItemView itemView) {
+ // Restore interactions.
+ itemView.setAnimating(false);
+ ViewCompat.setHasTransientState(itemView, false);
+ setHardwareAnimatingLayerType(itemView, !ANIMATING);
+ }
+
+ /**
+ * Animate the dismissal of the given item. The given velocityX is taken into consideration for
+ * the animation duration. Whether the item is dismissed to the left or right is dependent on
+ * the given velocityX.
+ */
+ private void animateDismiss(final ConversationListItemView itemView, final float velocityX) {
+ Assert.isTrue(velocityX != 0);
+ final int direction = velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
+ animateDismiss(itemView, direction, velocityX);
+ }
+
+ /**
+ * Animate the dismissal of the given item. The velocityX is assumed to be 0.
+ */
+ private void animateDismiss(final ConversationListItemView itemView, final int swipeDirection) {
+ animateDismiss(itemView, swipeDirection, 0f);
+ }
+
+ /**
+ * Animate the dismissal of the given item.
+ */
+ private void animateDismiss(final ConversationListItemView itemView,
+ final int swipeDirection, final float velocityX) {
+ Assert.isTrue(swipeDirection != SWIPE_DIRECTION_NONE);
+
+ onSwipeAnimationStart(itemView);
+
+ final float animateTo = (swipeDirection == SWIPE_DIRECTION_RIGHT) ?
+ mRecyclerView.getWidth() : -mRecyclerView.getWidth();
+ final long duration;
+ if (velocityX != 0) {
+ final float deltaX = animateTo - itemView.getSwipeTranslationX();
+ duration = calculateTranslationDuration(deltaX, velocityX);
+ } else {
+ duration = mDefaultDismissAnimationDuration;
+ }
+
+ final ObjectAnimator animator = getSwipeTranslationXAnimator(
+ itemView, animateTo, duration, UiUtils.DEFAULT_INTERPOLATOR);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ onSwipeAnimationEnd(itemView);
+ }
+ });
+ animator.start();
+ }
+
+ /**
+ * Animate the bounce back of the given item.
+ */
+ private void animateRestore(final ConversationListItemView itemView,
+ final float velocityX) {
+ onSwipeAnimationStart(itemView);
+
+ final float translationX = itemView.getSwipeTranslationX();
+ final long duration;
+ if (velocityX != 0 // Has velocity.
+ && velocityX > 0 != translationX > 0) { // Right direction.
+ duration = calculateTranslationDuration(translationX, velocityX);
+ } else {
+ duration = mDefaultRestoreAnimationDuration;
+ }
+ final ObjectAnimator animator = getSwipeTranslationXAnimator(
+ itemView, 0f, duration, UiUtils.DEFAULT_INTERPOLATOR);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(final Animator animation) {
+ onSwipeAnimationEnd(itemView);
+ }
+ });
+ animator.start();
+ }
+
+ /**
+ * Create and start an animator that animates the given view's translationX
+ * from its current value to the value given by animateTo.
+ */
+ private ObjectAnimator getSwipeTranslationXAnimator(final ConversationListItemView itemView,
+ final float animateTo, final long duration, final TimeInterpolator interpolator) {
+ final ObjectAnimator animator =
+ ObjectAnimator.ofFloat(itemView, "swipeTranslationX", animateTo);
+ animator.setDuration(duration);
+ animator.setInterpolator(interpolator);
+ return animator;
+ }
+
+ /**
+ * Determine if the swipe has enough velocity to be dismissed.
+ */
+ private boolean isTargetSwipedFastEnough() {
+ final float velocityX = getLastComputedXVelocity();
+ final float velocityY = mVelocityTracker.getYVelocity();
+ final float minVelocity = mMinimumFlingVelocity;
+ final float translationX = mListItemView.getSwipeTranslationX();
+ final float width = mListItemView.getWidth();
+ return (Math.abs(velocityX) > minVelocity) // Fast enough.
+ && (Math.abs(velocityX) > Math.abs(velocityY)) // Not unintentional.
+ && (velocityX > 0) == (translationX > 0) // Right direction.
+ && Math.abs(translationX) >
+ FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement.
+ }
+
+ /**
+ * Only used during a swipe gesture. Determine if the swipe has enough distance to be
+ * dismissed.
+ */
+ private boolean isTargetSwipedFarEnough() {
+ final float velocityX = getLastComputedXVelocity();
+
+ final float translationX = mListItemView.getSwipeTranslationX();
+ final float width = mListItemView.getWidth();
+
+ return (velocityX >= 0) == (translationX > 0) // Right direction.
+ && Math.abs(translationX) >
+ PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement.
+ }
+
+ private long calculateTranslationDuration(final float deltaPosition, final float velocity) {
+ Assert.isTrue(velocity != 0);
+ final float durationInSeconds = Math.abs(deltaPosition / velocity);
+ return Math.min((int) (durationInSeconds * UNIT_SECONDS), mMaxTranslationAnimationDuration);
+ }
+
+ private boolean hasGestureSwipeTarget() {
+ return mListItemView != null;
+ }
+
+ private boolean hasValidGestureSwipeTarget() {
+ return hasGestureSwipeTarget() && mListItemView.getParent() == mRecyclerView;
+ }
+
+ /**
+ * Enable a hardware layer for the it view and build that layer.
+ */
+ private void setHardwareAnimatingLayerType(final ConversationListItemView itemView,
+ final boolean animating) {
+ if (animating) {
+ itemView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ if (itemView.getWindowToken() != null) {
+ itemView.buildLayer();
+ }
+ } else {
+ itemView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ private float getLastComputedXVelocity() {
+ return mVelocityTracker.getXVelocity();
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java b/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java
new file mode 100644
index 0000000..61e3640
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java
@@ -0,0 +1,81 @@
+/*
+ * 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.conversationlist;
+
+import android.app.Fragment;
+import android.os.Bundle;
+
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.ui.BaseBugleActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
+import com.android.messaging.util.Assert;
+
+/**
+ * An activity that lets the user forward a SMS/MMS message by picking from a conversation in the
+ * conversation list.
+ */
+public class ForwardMessageActivity extends BaseBugleActivity
+ implements ConversationListFragmentHost {
+ private MessageData mDraftMessage;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final ConversationListFragment fragment =
+ ConversationListFragment.createForwardMessageConversationListFragment();
+ getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
+ mDraftMessage = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ Assert.isTrue(fragment instanceof ConversationListFragment);
+ final ConversationListFragment clf = (ConversationListFragment) fragment;
+ clf.setHost(this);
+ }
+
+ @Override
+ public void onConversationClick(final ConversationListData listData,
+ final ConversationListItemData conversationListItemData,
+ final boolean isLongClick, final ConversationListItemView converastionView) {
+ UIIntents.get().launchConversationActivity(
+ this, conversationListItemData.getConversationId(), mDraftMessage);
+ }
+
+ @Override
+ public void onCreateConversationClick() {
+ UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage);
+ }
+
+ @Override
+ public boolean isConversationSelected(final String conversationId) {
+ return false;
+ }
+
+ @Override
+ public boolean isSwipeAnimatable() {
+ return false;
+ }
+
+ @Override
+ public boolean isSelectionMode() {
+ return false;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java b/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java
new file mode 100644
index 0000000..bfeec51
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java
@@ -0,0 +1,219 @@
+/*
+ * 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.conversationlist;
+
+import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
+import android.view.ActionMode;
+import android.view.ActionMode.Callback;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.util.Assert;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+public class MultiSelectActionModeCallback implements Callback {
+ private HashSet<String> mBlockedSet;
+
+ public interface Listener {
+ void onActionBarDelete(Collection<SelectedConversation> conversations);
+ void onActionBarArchive(Iterable<SelectedConversation> conversations,
+ boolean isToArchive);
+ void onActionBarNotification(Iterable<SelectedConversation> conversations,
+ boolean isNotificationOn);
+ void onActionBarAddContact(final SelectedConversation conversation);
+ void onActionBarBlock(final SelectedConversation conversation);
+ void onActionBarHome();
+ }
+
+ static class SelectedConversation {
+ public final String conversationId;
+ public final long timestamp;
+ public final String icon;
+ public final String otherParticipantNormalizedDestination;
+ public final CharSequence participantLookupKey;
+ public final boolean isGroup;
+ public final boolean isArchived;
+ public final boolean notificationEnabled;
+ public SelectedConversation(ConversationListItemData data) {
+ conversationId = data.getConversationId();
+ timestamp = data.getTimestamp();
+ icon = data.getIcon();
+ otherParticipantNormalizedDestination = data.getOtherParticipantNormalizedDestination();
+ participantLookupKey = data.getParticipantLookupKey();
+ isGroup = data.getIsGroup();
+ isArchived = data.getIsArchived();
+ notificationEnabled = data.getNotificationEnabled();
+ }
+ }
+
+ private final ArrayMap<String, SelectedConversation> mSelectedConversations;
+
+ private Listener mListener;
+ private MenuItem mArchiveMenuItem;
+ private MenuItem mUnarchiveMenuItem;
+ private MenuItem mAddContactMenuItem;
+ private MenuItem mBlockMenuItem;
+ private MenuItem mNotificationOnMenuItem;
+ private MenuItem mNotificationOffMenuItem;
+ private boolean mHasInflated;
+
+ public MultiSelectActionModeCallback(final Listener listener) {
+ mListener = listener;
+ mSelectedConversations = new ArrayMap<>();
+
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
+ actionMode.getMenuInflater().inflate(R.menu.conversation_list_fragment_select_menu, menu);
+ mArchiveMenuItem = menu.findItem(R.id.action_archive);
+ mUnarchiveMenuItem = menu.findItem(R.id.action_unarchive);
+ mAddContactMenuItem = menu.findItem(R.id.action_add_contact);
+ mBlockMenuItem = menu.findItem(R.id.action_block);
+ mNotificationOffMenuItem = menu.findItem(R.id.action_notification_off);
+ mNotificationOnMenuItem = menu.findItem(R.id.action_notification_on);
+ mHasInflated = true;
+ updateActionIconsVisiblity();
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
+ switch(menuItem.getItemId()) {
+ case R.id.action_delete:
+ mListener.onActionBarDelete(mSelectedConversations.values());
+ return true;
+ case R.id.action_archive:
+ mListener.onActionBarArchive(mSelectedConversations.values(), true);
+ return true;
+ case R.id.action_unarchive:
+ mListener.onActionBarArchive(mSelectedConversations.values(), false);
+ return true;
+ case R.id.action_notification_off:
+ mListener.onActionBarNotification(mSelectedConversations.values(), false);
+ return true;
+ case R.id.action_notification_on:
+ mListener.onActionBarNotification(mSelectedConversations.values(), true);
+ return true;
+ case R.id.action_add_contact:
+ Assert.isTrue(mSelectedConversations.size() == 1);
+ mListener.onActionBarAddContact(mSelectedConversations.valueAt(0));
+ return true;
+ case R.id.action_block:
+ Assert.isTrue(mSelectedConversations.size() == 1);
+ mListener.onActionBarBlock(mSelectedConversations.valueAt(0));
+ return true;
+ case android.R.id.home:
+ mListener.onActionBarHome();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode actionMode) {
+ mListener = null;
+ mSelectedConversations.clear();
+ mHasInflated = false;
+ }
+
+ public void toggleSelect(final ConversationListData listData,
+ final ConversationListItemData conversationListItemData) {
+ Assert.notNull(conversationListItemData);
+ mBlockedSet = listData.getBlockedParticipants();
+ final String id = conversationListItemData.getConversationId();
+ if (mSelectedConversations.containsKey(id)) {
+ mSelectedConversations.remove(id);
+ } else {
+ mSelectedConversations.put(id, new SelectedConversation(conversationListItemData));
+ }
+
+ if (mSelectedConversations.isEmpty()) {
+ mListener.onActionBarHome();
+ } else {
+ updateActionIconsVisiblity();
+ }
+ }
+
+ public boolean isSelected(final String selectedId) {
+ return mSelectedConversations.containsKey(selectedId);
+ }
+
+ private void updateActionIconsVisiblity() {
+ if (!mHasInflated) {
+ return;
+ }
+
+ if (mSelectedConversations.size() == 1) {
+ final SelectedConversation conversation = mSelectedConversations.valueAt(0);
+ // The look up key is a key given to us by contacts app, so if we have a look up key,
+ // we know that the participant is already in contacts.
+ final boolean isInContacts = !TextUtils.isEmpty(conversation.participantLookupKey);
+ mAddContactMenuItem.setVisible(!conversation.isGroup && !isInContacts);
+ // ParticipantNormalizedDestination is always null for group conversations.
+ final String otherParticipant = conversation.otherParticipantNormalizedDestination;
+ mBlockMenuItem.setVisible(otherParticipant != null
+ && !mBlockedSet.contains(otherParticipant));
+ } else {
+ mBlockMenuItem.setVisible(false);
+ mAddContactMenuItem.setVisible(false);
+ }
+
+ boolean hasCurrentlyArchived = false;
+ boolean hasCurrentlyUnarchived = false;
+ boolean hasCurrentlyOnNotification = false;
+ boolean hasCurrentlyOffNotification = false;
+ final Iterable<SelectedConversation> conversations = mSelectedConversations.values();
+ for (final SelectedConversation conversation : conversations) {
+ if (conversation.notificationEnabled) {
+ hasCurrentlyOnNotification = true;
+ } else {
+ hasCurrentlyOffNotification = true;
+ }
+
+ if (conversation.isArchived) {
+ hasCurrentlyArchived = true;
+ } else {
+ hasCurrentlyUnarchived = true;
+ }
+
+ // If we found at least one of each example we don't need to keep looping.
+ if (hasCurrentlyOffNotification && hasCurrentlyOnNotification &&
+ hasCurrentlyArchived && hasCurrentlyUnarchived) {
+ break;
+ }
+ }
+ // If we have notification off conversations we show on button, if we have notification on
+ // conversation we show off button. We can show both if we have a mixture.
+ mNotificationOffMenuItem.setVisible(hasCurrentlyOnNotification);
+ mNotificationOnMenuItem.setVisible(hasCurrentlyOffNotification);
+
+ mArchiveMenuItem.setVisible(hasCurrentlyUnarchived);
+ mUnarchiveMenuItem.setVisible(hasCurrentlyArchived);
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
new file mode 100644
index 0000000..ef7fcef
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
@@ -0,0 +1,177 @@
+/*
+ * 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.conversationlist;
+
+import android.app.Fragment;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.ui.BaseBugleActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaMetadataRetrieverWrapper;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class ShareIntentActivity extends BaseBugleActivity implements
+ ShareIntentFragment.HostInterface {
+
+ private MessageData mDraftMessage;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = getIntent();
+ if (Intent.ACTION_SEND.equals(intent.getAction()) &&
+ (!TextUtils.isEmpty(intent.getStringExtra("address")) ||
+ !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL)))) {
+ // This is really more like a SENDTO intent because a destination is supplied.
+ // It's coming through the SEND intent because that's the intent that is used
+ // when invoking the chooser with Intent.createChooser().
+ final Intent convIntent = UIIntents.get().getLaunchConversationActivityIntent(this);
+ // Copy the important items from the original intent to the new intent.
+ convIntent.putExtras(intent);
+ convIntent.setAction(Intent.ACTION_SENDTO);
+ convIntent.setDataAndType(intent.getData(), intent.getType());
+ // We have to fire off the intent and finish before trying to show the fragment,
+ // otherwise we get some flashing.
+ startActivity(convIntent);
+ finish();
+ return;
+ }
+ new ShareIntentFragment().show(getFragmentManager(), "ShareIntentFragment");
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+ if (Intent.ACTION_SEND.equals(action)) {
+ final Uri contentUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ final String contentType = extractContentType(contentUri, intent.getType());
+ if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
+ LogUtil.d(LogUtil.BUGLE_TAG, String.format(
+ "onAttachFragment: contentUri=%s, intent.getType()=%s, inferredType=%s",
+ contentUri, intent.getType(), contentType));
+ }
+ if (ContentType.TEXT_PLAIN.equals(contentType)) {
+ final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (sharedText != null) {
+ mDraftMessage = MessageData.createSharedMessage(sharedText);
+ } else {
+ mDraftMessage = null;
+ }
+ } else if (ContentType.isImageType(contentType) ||
+ ContentType.isVCardType(contentType) ||
+ ContentType.isAudioType(contentType) ||
+ ContentType.isVideoType(contentType)) {
+ if (contentUri != null) {
+ mDraftMessage = MessageData.createSharedMessage(null);
+ addSharedImagePartToDraft(contentType, contentUri);
+ } else {
+ mDraftMessage = null;
+ }
+ } else {
+ // Unsupported content type.
+ Assert.fail("Unsupported shared content type for " + contentUri + ": " + contentType
+ + " (" + intent.getType() + ")");
+ }
+ } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+ final String contentType = intent.getType();
+ if (ContentType.isImageType(contentType)) {
+ // Handle sharing multiple images.
+ final ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(
+ Intent.EXTRA_STREAM);
+ if (imageUris != null && imageUris.size() > 0) {
+ mDraftMessage = MessageData.createSharedMessage(null);
+ for (final Uri imageUri : imageUris) {
+ final String actualContentType = extractContentType(imageUri, contentType);
+ addSharedImagePartToDraft(actualContentType, imageUri);
+ }
+ } else {
+ mDraftMessage = null;
+ }
+ } else {
+ // Unsupported content type.
+ Assert.fail("Unsupported shared content type: " + contentType);
+ }
+ } else {
+ // Unsupported action.
+ Assert.fail("Unsupported action type for sharing: " + action);
+ }
+ }
+
+ private static String extractContentType(final Uri uri, final String contentType) {
+ if (uri == null) {
+ return contentType;
+ }
+ // First try looking at file extension. This is less reliable in some ways but it's
+ // recommended by
+ // https://developer.android.com/training/secure-file-sharing/retrieve-info.html
+ // Some implementations of MediaMetadataRetriever get things horribly
+ // wrong for common formats such as jpeg (reports as video/ffmpeg)
+ final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ final String typeFromExtension = resolver.getType(uri);
+ if (typeFromExtension != null) {
+ return typeFromExtension;
+ }
+ final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
+ try {
+ retriever.setDataSource(uri);
+ final String extractedType = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
+ if (extractedType != null) {
+ return extractedType;
+ }
+ } catch (final IOException e) {
+ LogUtil.i(LogUtil.BUGLE_TAG, "Could not determine type of " + uri, e);
+ } finally {
+ retriever.release();
+ }
+ return contentType;
+ }
+
+ private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) {
+ mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType,
+ imageUri));
+ }
+
+ @Override
+ public void onConversationClick(final ConversationListItemData conversationListItemData) {
+ UIIntents.get().launchConversationActivity(
+ this, conversationListItemData.getConversationId(), mDraftMessage);
+ finish();
+ }
+
+ @Override
+ public void onCreateConversationClick() {
+ UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage);
+ finish();
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java b/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java
new file mode 100644
index 0000000..e894145
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java
@@ -0,0 +1,138 @@
+/*
+ * 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.conversationlist;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.PersonItemData;
+import com.android.messaging.ui.CursorRecyclerAdapter;
+import com.android.messaging.ui.PersonItemView;
+import com.android.messaging.ui.PersonItemView.PersonItemViewListener;
+import com.android.messaging.util.PhoneUtils;
+
+/**
+ * Turn conversation rows into PeopleItemViews
+ */
+public class ShareIntentAdapter
+ extends CursorRecyclerAdapter<ShareIntentAdapter.ShareIntentViewHolder> {
+
+ public interface HostInterface {
+ void onConversationClicked(final ConversationListItemData conversationListItemData);
+ }
+
+ private final HostInterface mHostInterface;
+
+ public ShareIntentAdapter(final Context context, final Cursor cursor,
+ final HostInterface hostInterface) {
+ super(context, cursor, 0);
+ mHostInterface = hostInterface;
+ setHasStableIds(true);
+ }
+
+ @Override
+ public void bindViewHolder(final ShareIntentViewHolder holder, final Context context,
+ final Cursor cursor) {
+ holder.bind(cursor);
+ }
+
+ @Override
+ public ShareIntentViewHolder createViewHolder(final Context context,
+ final ViewGroup parent, final int viewType) {
+ final PersonItemView itemView = (PersonItemView) LayoutInflater.from(context).inflate(
+ R.layout.people_list_item_view, null);
+ return new ShareIntentViewHolder(itemView);
+ }
+
+ /**
+ * Holds a PersonItemView and keeps it synced with a ConversationListItemData.
+ */
+ public class ShareIntentViewHolder extends RecyclerView.ViewHolder implements
+ PersonItemView.PersonItemViewListener {
+ private final ConversationListItemData mData = new ConversationListItemData();
+ private final PersonItemData mItemData = new PersonItemData() {
+ @Override
+ public Uri getAvatarUri() {
+ return mData.getIcon() == null ? null : Uri.parse(mData.getIcon());
+ }
+
+ @Override
+ public String getDisplayName() {
+ return mData.getName();
+ }
+
+ @Override
+ public String getDetails() {
+ final String conversationName = mData.getName();
+ final String conversationPhone = PhoneUtils.getDefault().formatForDisplay(
+ mData.getOtherParticipantNormalizedDestination());
+ if (conversationPhone == null || conversationPhone.equals(conversationName)) {
+ return null;
+ }
+ return conversationPhone;
+ }
+
+ @Override
+ public Intent getClickIntent() {
+ return null;
+ }
+
+ @Override
+ public long getContactId() {
+ return ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
+ }
+
+ @Override
+ public String getLookupKey() {
+ return null;
+ }
+
+ @Override
+ public String getNormalizedDestination() {
+ return null;
+ }
+ };
+
+ public ShareIntentViewHolder(final PersonItemView itemView) {
+ super(itemView);
+ itemView.setListener(this);
+ }
+
+ public void bind(Cursor cursor) {
+ mData.bind(cursor);
+ ((PersonItemView) itemView).bind(mItemData);
+ }
+
+ @Override
+ public void onPersonClicked(PersonItemData data) {
+ mHostInterface.onConversationClicked(mData);
+ }
+
+ @Override
+ public boolean onPersonLongClicked(PersonItemData data) {
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java b/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java
new file mode 100644
index 0000000..bc549ea
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java
@@ -0,0 +1,163 @@
+/*
+ * 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.conversationlist;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
+import com.android.messaging.ui.ListEmptyView;
+import com.android.messaging.datamodel.DataModel;
+
+/**
+ * Allow user to pick conversation to which an incoming attachment will be shared.
+ */
+public class ShareIntentFragment extends DialogFragment implements ConversationListDataListener,
+ ShareIntentAdapter.HostInterface {
+ public static final String HIDE_NEW_CONVERSATION_BUTTON_KEY = "hide_conv_button_key";
+
+ public interface HostInterface {
+ public void onConversationClick(final ConversationListItemData conversationListItemData);
+ public void onCreateConversationClick();
+ }
+
+ private final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
+ private RecyclerView mRecyclerView;
+ private ListEmptyView mEmptyListMessageView;
+ private ShareIntentAdapter mAdapter;
+ private HostInterface mHost;
+ private boolean mDismissed;
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public Dialog onCreateDialog(final Bundle bundle) {
+ final Activity activity = getActivity();
+ final LayoutInflater inflater = activity.getLayoutInflater();
+ View view = inflater.inflate(R.layout.share_intent_conversation_list_view, null);
+ mEmptyListMessageView = (ListEmptyView) view.findViewById(R.id.no_conversations_view);
+ mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
+ // The default behavior for default layout param generation by LinearLayoutManager is to
+ // provide width and height of WRAP_CONTENT, but this is not desirable for
+ // ShareIntentFragment; the view in each row should be a width of MATCH_PARENT so that
+ // the entire row is tappable.
+ final LinearLayoutManager manager = new LinearLayoutManager(activity) {
+ @Override
+ public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+ return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ };
+ mListBinding.getData().init(getLoaderManager(), mListBinding);
+ mAdapter = new ShareIntentAdapter(activity, null, this);
+ mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
+ mRecyclerView.setLayoutManager(manager);
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setAdapter(mAdapter);
+ final Builder dialogBuilder = new AlertDialog.Builder(activity)
+ .setView(view)
+ .setTitle(R.string.share_intent_activity_label);
+
+ final Bundle arguments = getArguments();
+ if (arguments == null || !arguments.getBoolean(HIDE_NEW_CONVERSATION_BUTTON_KEY)) {
+ dialogBuilder.setPositiveButton(R.string.share_new_message, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mDismissed = true;
+ mHost.onCreateConversationClick();
+ }
+ });
+ }
+ return dialogBuilder.setNegativeButton(R.string.share_cancel, null)
+ .create();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ if (!mDismissed) {
+ final Activity activity = getActivity();
+ if (activity != null) {
+ activity.finish();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc} from Fragment
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mListBinding.unbind();
+ }
+
+ @Override
+ public void onAttach(final Activity activity) {
+ super.onAttach(activity);
+ if (activity instanceof HostInterface) {
+ mHost = (HostInterface) activity;
+ }
+ mListBinding.bind(DataModel.get().createConversationListData(activity, this, false));
+ }
+
+ @Override
+ public void onConversationListCursorUpdated(final ConversationListData data,
+ final Cursor cursor) {
+ mListBinding.ensureBound(data);
+ mAdapter.swapCursor(cursor);
+ updateEmptyListUi(cursor == null || cursor.getCount() == 0);
+ }
+
+ /**
+ * {@inheritDoc} from SharIntentItemView.HostInterface
+ */
+ @Override
+ public void onConversationClicked(final ConversationListItemData conversationListItemData) {
+ mHost.onConversationClick(conversationListItemData);
+ }
+
+ // Show and hide empty list UI as needed with appropriate text based on view specifics
+ private void updateEmptyListUi(final boolean isEmpty) {
+ if (isEmpty) {
+ mEmptyListMessageView.setTextHint(R.string.conversation_list_empty_text);
+ mEmptyListMessageView.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyListMessageView.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void setBlockedParticipantsAvailable(boolean blockedAvailable) {
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java b/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java
new file mode 100644
index 0000000..d727001
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java
@@ -0,0 +1,66 @@
+/*
+ * 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.conversationsettings;
+
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.util.AccessibilityUtil;
+
+public class CopyContactDetailDialog implements DialogInterface.OnClickListener {
+
+ private final Context mContext;
+ private final String mContactDetail;
+
+ public CopyContactDetailDialog(final Context context, final String contactDetail) {
+ mContext = context;
+ mContactDetail = contactDetail;
+ }
+
+ public void show() {
+ new AlertDialog.Builder(mContext)
+ .setView(createBodyView())
+ .setTitle(R.string.copy_to_clipboard_dialog_title)
+ .setPositiveButton(R.string.copy_to_clipboard, this)
+ .show();
+ }
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final ClipboardManager clipboard =
+ (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(ClipData.newPlainText(null /* label */, mContactDetail));
+ }
+
+ private View createBodyView() {
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ TextView textView = (TextView) inflater.inflate(R.layout.copy_contact_dialog_view, null,
+ false);
+ textView.setText(mContactDetail);
+ final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
+ mContext.getResources(), mContactDetail);
+ textView.setContentDescription(vocalizedDisplayName);
+ return textView;
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java
new file mode 100644
index 0000000..f017328
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.conversationsettings;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+
+/**
+ * Shows a list of participants in a conversation.
+ */
+public class PeopleAndOptionsActivity extends BugleActionBarActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.people_and_options_activity);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public void onAttachFragment(final Fragment fragment) {
+ if (fragment instanceof PeopleAndOptionsFragment) {
+ final String conversationId =
+ getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+ Assert.notNull(conversationId);
+ final PeopleAndOptionsFragment peopleAndOptionsFragment =
+ (PeopleAndOptionsFragment) fragment;
+ peopleAndOptionsFragment.setConversationId(conversationId);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // Treat the home press as back press so that when we go back to
+ // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
+ onBackPressed();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java
new file mode 100644
index 0000000..b86d952
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java
@@ -0,0 +1,329 @@
+/*
+ * 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.conversationsettings;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.ParticipantListItemData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
+import com.android.messaging.datamodel.data.PeopleOptionsItemData;
+import com.android.messaging.datamodel.data.PersonItemData;
+import com.android.messaging.ui.CompositeAdapter;
+import com.android.messaging.ui.PersonItemView;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.conversation.ConversationActivity;
+import com.android.messaging.util.Assert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows a list of participants of a conversation and displays options.
+ */
+public class PeopleAndOptionsFragment extends Fragment
+ implements PeopleAndOptionsDataListener, PeopleOptionsItemView.HostInterface {
+ private ListView mListView;
+ private OptionsListAdapter mOptionsListAdapter;
+ private PeopleListAdapter mPeopleListAdapter;
+ private final Binding<PeopleAndOptionsData> mBinding =
+ BindingBase.createBinding(this);
+
+ private static final int REQUEST_CODE_RINGTONE_PICKER = 1000;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinding.getData().init(getLoaderManager(), mBinding);
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.people_and_options_fragment, container, false);
+ mListView = (ListView) view.findViewById(android.R.id.list);
+ mPeopleListAdapter = new PeopleListAdapter(getActivity());
+ mOptionsListAdapter = new OptionsListAdapter();
+ final CompositeAdapter compositeAdapter = new CompositeAdapter(getActivity());
+ compositeAdapter.addPartition(new PeopleAndOptionsPartition(mOptionsListAdapter,
+ R.string.general_settings_title, false));
+ compositeAdapter.addPartition(new PeopleAndOptionsPartition(mPeopleListAdapter,
+ R.string.participant_list_title, true));
+ mListView.setAdapter(compositeAdapter);
+ return view;
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_RINGTONE_PICKER) {
+ final Parcelable pick = data.getParcelableExtra(
+ RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+ final String pickedUri = pick == null ? "" : pick.toString();
+ mBinding.getData().setConversationNotificationSound(mBinding, pickedUri);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mBinding.unbind();
+ }
+
+ public void setConversationId(final String conversationId) {
+ Assert.isTrue(getView() == null);
+ Assert.notNull(conversationId);
+ mBinding.bind(DataModel.get().createPeopleAndOptionsData(conversationId, getActivity(),
+ this));
+ }
+
+ @Override
+ public void onOptionsCursorUpdated(final PeopleAndOptionsData data, final Cursor cursor) {
+ Assert.isTrue(cursor == null || cursor.getCount() == 1);
+ mBinding.ensureBound(data);
+ mOptionsListAdapter.swapCursor(cursor);
+ }
+
+ @Override
+ public void onParticipantsListLoaded(final PeopleAndOptionsData data,
+ final List<ParticipantData> participants) {
+ mBinding.ensureBound(data);
+ mPeopleListAdapter.updateParticipants(participants);
+ final ParticipantData otherParticipant = participants.size() == 1 ?
+ participants.get(0) : null;
+ mOptionsListAdapter.setOtherParticipant(otherParticipant);
+ }
+
+ @Override
+ public void onOptionsItemViewClicked(final PeopleOptionsItemData item,
+ final boolean isChecked) {
+ switch (item.getItemId()) {
+ case PeopleOptionsItemData.SETTING_NOTIFICATION_ENABLED:
+ mBinding.getData().enableConversationNotifications(mBinding, isChecked);
+ break;
+
+ case PeopleOptionsItemData.SETTING_NOTIFICATION_SOUND_URI:
+ final Intent ringtonePickerIntent = UIIntents.get().getRingtonePickerIntent(
+ getString(R.string.notification_sound_pref_title),
+ item.getRingtoneUri(), Settings.System.DEFAULT_NOTIFICATION_URI,
+ RingtoneManager.TYPE_NOTIFICATION);
+ startActivityForResult(ringtonePickerIntent, REQUEST_CODE_RINGTONE_PICKER);
+ break;
+
+ case PeopleOptionsItemData.SETTING_NOTIFICATION_VIBRATION:
+ mBinding.getData().enableConversationNotificationVibration(mBinding,
+ isChecked);
+ break;
+
+ case PeopleOptionsItemData.SETTING_BLOCKED:
+ if (item.getOtherParticipant().isBlocked()) {
+ mBinding.getData().setDestinationBlocked(mBinding, false);
+ break;
+ }
+ final Resources res = getResources();
+ final Activity activity = getActivity();
+ new AlertDialog.Builder(activity)
+ .setTitle(res.getString(R.string.block_confirmation_title,
+ item.getOtherParticipant().getDisplayDestination()))
+ .setMessage(res.getString(R.string.block_confirmation_message))
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface arg0, int arg1) {
+ mBinding.getData().setDestinationBlocked(mBinding, true);
+ activity.setResult(ConversationActivity.FINISH_RESULT_CODE);
+ activity.finish();
+ }
+ })
+ .create()
+ .show();
+ break;
+ }
+ }
+
+ /**
+ * A simple adapter that takes a conversation metadata cursor and binds
+ * PeopleAndOptionsItemViews to individual COLUMNS of the first cursor record. (Note
+ * that this is not a CursorAdapter because it treats individual columns of the cursor as
+ * separate options to display for the conversation, e.g. notification settings).
+ */
+ private class OptionsListAdapter extends BaseAdapter {
+ private Cursor mOptionsCursor;
+ private ParticipantData mOtherParticipantData;
+
+ public Cursor swapCursor(final Cursor newCursor) {
+ final Cursor oldCursor = mOptionsCursor;
+ if (newCursor != oldCursor) {
+ mOptionsCursor = newCursor;
+ notifyDataSetChanged();
+ }
+ return oldCursor;
+ }
+
+ public void setOtherParticipant(final ParticipantData participantData) {
+ if (mOtherParticipantData != participantData) {
+ mOtherParticipantData = participantData;
+ notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public int getCount() {
+ int count = PeopleOptionsItemData.SETTINGS_COUNT;
+ if (mOtherParticipantData == null) {
+ count--;
+ }
+ return mOptionsCursor == null ? 0 : count;
+ }
+
+ @Override
+ public Object getItem(final int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(final int position) {
+ return 0;
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ final PeopleOptionsItemView itemView;
+ if (convertView != null && convertView instanceof PeopleOptionsItemView) {
+ itemView = (PeopleOptionsItemView) convertView;
+ } else {
+ final LayoutInflater inflater = (LayoutInflater) getActivity()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ itemView = (PeopleOptionsItemView)
+ inflater.inflate(R.layout.people_options_item_view, parent, false);
+ }
+ mOptionsCursor.moveToFirst();
+ itemView.bind(mOptionsCursor, position, mOtherParticipantData,
+ PeopleAndOptionsFragment.this);
+ return itemView;
+ }
+ }
+
+ /**
+ * An adapter that takes a list of ParticipantData and displays them as a list of
+ * ParticipantListItemViews.
+ */
+ private class PeopleListAdapter extends ArrayAdapter<ParticipantData> {
+ public PeopleListAdapter(final Context context) {
+ super(context, R.layout.people_list_item_view, new ArrayList<ParticipantData>());
+ }
+
+ public void updateParticipants(final List<ParticipantData> newList) {
+ clear();
+ addAll(newList);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ PersonItemView itemView;
+ final ParticipantData item = getItem(position);
+ if (convertView != null && convertView instanceof PersonItemView) {
+ itemView = (PersonItemView) convertView;
+ } else {
+ final LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ itemView = (PersonItemView) inflater.inflate(R.layout.people_list_item_view, parent,
+ false);
+ }
+ final ParticipantListItemData itemData =
+ DataModel.get().createParticipantListItemData(item);
+ itemView.bind(itemData);
+
+ // Any click on the row should have the same effect as clicking the avatar icon
+ final PersonItemView itemViewClosure = itemView;
+ itemView.setListener(new PersonItemView.PersonItemViewListener() {
+ @Override
+ public void onPersonClicked(final PersonItemData data) {
+ itemViewClosure.performClickOnAvatar();
+ }
+
+ @Override
+ public boolean onPersonLongClicked(final PersonItemData data) {
+ if (mBinding.isBound()) {
+ final CopyContactDetailDialog dialog = new CopyContactDetailDialog(
+ getContext(), data.getDetails());
+ dialog.show();
+ return true;
+ }
+ return false;
+ }
+ });
+ return itemView;
+ }
+ }
+
+ /**
+ * Represents a partition/section in the People & Options list (e.g. "general options" and
+ * "people in this conversation" sections).
+ */
+ private class PeopleAndOptionsPartition extends CompositeAdapter.Partition {
+ private final int mHeaderResId;
+ private final boolean mNeedDivider;
+
+ public PeopleAndOptionsPartition(final BaseAdapter adapter, final int headerResId,
+ final boolean needDivider) {
+ super(true /* showIfEmpty */, true /* hasHeader */, adapter);
+ mHeaderResId = headerResId;
+ mNeedDivider = needDivider;
+ }
+
+ @Override
+ public View getHeaderView(final View convertView, final ViewGroup parentView) {
+ View view = null;
+ if (convertView != null && convertView.getId() == R.id.people_and_options_header) {
+ view = convertView;
+ } else {
+ view = LayoutInflater.from(getActivity()).inflate(
+ R.layout.people_and_options_section_header, parentView, false);
+ }
+ final TextView text = (TextView) view.findViewById(R.id.header_text);
+ final View divider = view.findViewById(R.id.divider);
+ text.setText(mHeaderResId);
+ divider.setVisibility(mNeedDivider ? View.VISIBLE : View.GONE);
+ return view;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java b/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java
new file mode 100644
index 0000000..42ecfeb
--- /dev/null
+++ b/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java
@@ -0,0 +1,99 @@
+/*
+ * 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.conversationsettings;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.SwitchCompat;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+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.ParticipantData;
+import com.android.messaging.datamodel.data.PeopleOptionsItemData;
+import com.android.messaging.util.Assert;
+
+/**
+ * The view for a single entry in the options section of people & options activity.
+ */
+public class PeopleOptionsItemView extends LinearLayout {
+ /**
+ * Implemented by the host of this view that handles options click event.
+ */
+ public interface HostInterface {
+ void onOptionsItemViewClicked(PeopleOptionsItemData item, boolean isChecked);
+ }
+
+ private TextView mTitle;
+ private TextView mSubtitle;
+ private SwitchCompat mSwitch;
+ private final PeopleOptionsItemData mData;
+ private HostInterface mHostInterface;
+
+ public PeopleOptionsItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = DataModel.get().createPeopleOptionsItemData(context);
+ }
+
+ @Override
+ protected void onFinishInflate () {
+ mTitle = (TextView) findViewById(R.id.title);
+ mSubtitle = (TextView) findViewById(R.id.subtitle);
+ mSwitch = (SwitchCompat) findViewById(R.id.switch_button);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHostInterface.onOptionsItemViewClicked(mData, !mData.getChecked());
+ }
+ });
+ }
+
+ public void bind(final Cursor cursor, final int columnIndex, ParticipantData otherParticipant,
+ final HostInterface hostInterface) {
+ Assert.isTrue(columnIndex < PeopleOptionsItemData.SETTINGS_COUNT && columnIndex >= 0);
+ mData.bind(cursor, otherParticipant, columnIndex);
+ mHostInterface = hostInterface;
+
+ mTitle.setText(mData.getTitle());
+ final String subtitle = mData.getSubtitle();
+ if (TextUtils.isEmpty(subtitle)) {
+ mSubtitle.setVisibility(GONE);
+ } else {
+ mSubtitle.setVisibility(VISIBLE);
+ mSubtitle.setText(subtitle);
+ }
+
+ if (mData.getCheckable()) {
+ mSwitch.setVisibility(VISIBLE);
+ mSwitch.setChecked(mData.getChecked());
+ } else {
+ mSwitch.setVisibility(GONE);
+ }
+
+ final boolean enabled = mData.getEnabled();
+ if (enabled != isEnabled()) {
+ mTitle.setEnabled(enabled);
+ mSubtitle.setEnabled(enabled);
+ mSwitch.setEnabled(enabled);
+ setAlpha(enabled ? 1.0f : 0.5f);
+ setEnabled(enabled);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java b/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java
new file mode 100644
index 0000000..485dcf7
--- /dev/null
+++ b/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.debug;
+
+import android.os.Bundle;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.BaseBugleActivity;
+
+/**
+ * Show list of all MmsConfig key/value pairs and allow editing.
+ */
+public class DebugMmsConfigActivity extends BaseBugleActivity {
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.debug_mmsconfig_activity);
+ }
+}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java b/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java
new file mode 100644
index 0000000..7c54db5
--- /dev/null
+++ b/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java
@@ -0,0 +1,147 @@
+/*
+ * 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.debug;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.ui.debug.DebugMmsConfigItemView.MmsConfigItemListener;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Show list of all MmsConfig key/value pairs and allow editing.
+ */
+public class DebugMmsConfigFragment extends Fragment {
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ final View fragmentView = inflater.inflate(R.layout.mms_config_debug_fragment, container,
+ false);
+ final ListView listView = (ListView) fragmentView.findViewById(android.R.id.list);
+ final Spinner spinner = (Spinner) fragmentView.findViewById(R.id.sim_selector);
+ final Integer[] subIdArray = getActiveSubIds();
+ ArrayAdapter<Integer> spinnerAdapter = new ArrayAdapter<Integer>(getActivity(),
+ android.R.layout.simple_spinner_item, subIdArray);
+ spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(spinnerAdapter);
+ spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ listView.setAdapter(new MmsConfigAdapter(getActivity(), subIdArray[position]));
+
+ final int[] mccmnc = PhoneUtils.get(subIdArray[position]).getMccMnc();
+ // Set the title with the mcc/mnc
+ final TextView title = (TextView) fragmentView.findViewById(R.id.sim_title);
+ title.setText("(" + mccmnc[0] + "/" + mccmnc[1] + ") " +
+ getActivity().getString(R.string.debug_sub_id_spinner_text));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ return fragmentView;
+ }
+
+ public static Integer[] getActiveSubIds() {
+ if (!OsUtil.isAtLeastL_MR1()) {
+ return new Integer[] { ParticipantData.DEFAULT_SELF_SUB_ID };
+ }
+ final List<SubscriptionInfo> subRecords =
+ PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
+ if (subRecords == null) {
+ return new Integer[0];
+ }
+ final Integer[] retArray = new Integer[subRecords.size()];
+ for (int i = 0; i < subRecords.size(); i++) {
+ retArray[i] = subRecords.get(i).getSubscriptionId();
+ }
+ return retArray;
+ }
+
+ private class MmsConfigAdapter extends BaseAdapter implements
+ DebugMmsConfigItemView.MmsConfigItemListener {
+ private final LayoutInflater mInflater;
+ private final List<String> mKeys;
+ private final MmsConfig mMmsConfig;
+
+ public MmsConfigAdapter(Context context, int subId) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mMmsConfig = MmsConfig.get(subId);
+ mKeys = new ArrayList<>(mMmsConfig.keySet());
+ Collections.sort(mKeys);
+ }
+
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ final DebugMmsConfigItemView view;
+ if (convertView != null && convertView instanceof DebugMmsConfigItemView) {
+ view = (DebugMmsConfigItemView) convertView;
+ } else {
+ view = (DebugMmsConfigItemView) mInflater.inflate(
+ R.layout.debug_mmsconfig_item_view, parent, false);
+ }
+ final String key = mKeys.get(position);
+ view.bind(key,
+ MmsConfig.getKeyType(key),
+ String.valueOf(mMmsConfig.getValue(key)),
+ this);
+ return view;
+ }
+
+ @Override
+ public void onValueChanged(String key, String keyType, String value) {
+ mMmsConfig.update(key, value, keyType);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mKeys.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mKeys.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java b/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java
new file mode 100644
index 0000000..7b899c0
--- /dev/null
+++ b/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java
@@ -0,0 +1,134 @@
+/*
+ * 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.debug;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnShowListener;
+import android.text.InputType;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.util.LogUtil;
+
+public class DebugMmsConfigItemView extends LinearLayout implements OnClickListener,
+ OnCheckedChangeListener, DialogInterface.OnClickListener {
+
+ public interface MmsConfigItemListener {
+ void onValueChanged(String key, String keyType, String value);
+ }
+
+ private TextView mTitle;
+ private TextView mTextValue;
+ private Switch mSwitch;
+ private String mKey;
+ private String mKeyType;
+ private MmsConfigItemListener mListener;
+ private EditText mEditText;
+
+ public DebugMmsConfigItemView(Context context, AttributeSet attributeSet) {
+ super(context, attributeSet);
+ }
+
+ @Override
+ protected void onFinishInflate () {
+ mTitle = (TextView) findViewById(R.id.title);
+ mTextValue = (TextView) findViewById(R.id.text_value);
+ mSwitch = (Switch) findViewById(R.id.switch_button);
+ setOnClickListener(this);
+ mSwitch.setOnCheckedChangeListener(this);
+ }
+
+ public void bind(final String key, final String keyType, final String value,
+ final MmsConfigItemListener listener) {
+ mListener = listener;
+ mKey = key;
+ mKeyType = keyType;
+ mTitle.setText(key);
+
+ switch (keyType) {
+ case MmsConfig.KEY_TYPE_BOOL:
+ mSwitch.setVisibility(View.VISIBLE);
+ mTextValue.setVisibility(View.GONE);
+ mSwitch.setChecked(Boolean.valueOf(value));
+ break;
+ case MmsConfig.KEY_TYPE_STRING:
+ case MmsConfig.KEY_TYPE_INT:
+ mTextValue.setVisibility(View.VISIBLE);
+ mSwitch.setVisibility(View.GONE);
+ mTextValue.setText(value);
+ break;
+ default:
+ mTextValue.setVisibility(View.GONE);
+ mSwitch.setVisibility(View.GONE);
+ LogUtil.e(LogUtil.BUGLE_TAG, "Unexpected keytype: " + keyType);
+ break;
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mListener.onValueChanged(mKey, mKeyType, String.valueOf(isChecked));
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (MmsConfig.KEY_TYPE_BOOL.equals(mKeyType)) {
+ return;
+ }
+ final Context context = getContext();
+ mEditText = new EditText(context);
+ mEditText.setText(mTextValue.getText());
+ mEditText.setFocusable(true);
+ if (MmsConfig.KEY_TYPE_INT.equals(mKeyType)) {
+ mEditText.setInputType(InputType.TYPE_CLASS_PHONE);
+ } else {
+ mEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ }
+ final AlertDialog dialog = new AlertDialog.Builder(context)
+ .setTitle(mKey)
+ .setView(mEditText)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ dialog.setOnShowListener(new OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ mEditText.requestFocus();
+ mEditText.selectAll();
+ ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE))
+ .toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
+ }
+ });
+ dialog.show();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mListener.onValueChanged(mKey, mKeyType, mEditText.getText().toString());
+ }
+}
diff --git a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java b/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java
new file mode 100644
index 0000000..1aa8be3
--- /dev/null
+++ b/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java
@@ -0,0 +1,171 @@
+/*
+ * 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.debug;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.telephony.SmsMessage;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.action.ReceiveMmsMessageAction;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.receiver.SmsReceiver;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.DebugUtils;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Class that displays UI for choosing SMS/MMS dump files for debugging
+ */
+public class DebugSmsMmsFromDumpFileDialogFragment extends DialogFragment {
+ public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
+ public static final String KEY_DUMP_FILES = "dump_files";
+ public static final String KEY_ACTION = "action";
+
+ public static final String ACTION_LOAD = "load";
+ public static final String ACTION_EMAIL = "email";
+
+ private String[] mDumpFiles;
+ private String mAction;
+
+ public static DebugSmsMmsFromDumpFileDialogFragment newInstance(final String[] dumpFiles,
+ final String action) {
+ final DebugSmsMmsFromDumpFileDialogFragment frag =
+ new DebugSmsMmsFromDumpFileDialogFragment();
+ final Bundle args = new Bundle();
+ args.putSerializable(KEY_DUMP_FILES, dumpFiles);
+ args.putString(KEY_ACTION, action);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ mDumpFiles = (String[]) args.getSerializable(KEY_DUMP_FILES);
+ mAction = args.getString(KEY_ACTION);
+
+ final LayoutInflater inflater = getActivity().getLayoutInflater();
+ final View layout = inflater.inflate(
+ R.layout.debug_sms_mms_from_dump_file_dialog, null/*root*/);
+ final ListView list = (ListView) layout.findViewById(R.id.dump_file_list);
+ list.setAdapter(new DumpFileListAdapter(getActivity(), mDumpFiles));
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final Resources resources = getResources();
+ if (ACTION_LOAD.equals(mAction)) {
+ builder.setTitle(resources.getString(
+ R.string.load_sms_mms_from_dump_file_dialog_title));
+ } else if (ACTION_EMAIL.equals(mAction)) {
+ builder.setTitle(resources.getString(
+ R.string.email_sms_mms_from_dump_file_dialog_title));
+ }
+ builder.setView(layout);
+ return builder.create();
+ }
+
+ private class DumpFileListAdapter extends ArrayAdapter<String> {
+ public DumpFileListAdapter(final Context context, final String[] dumpFiles) {
+ super(context, R.layout.sms_mms_dump_file_list_item, dumpFiles);
+ }
+
+ @Override
+ public View getView(final int position, final View view, final ViewGroup parent) {
+ TextView actionItemView;
+ if (view == null || !(view instanceof TextView)) {
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ actionItemView = (TextView) inflater.inflate(
+ R.layout.sms_mms_dump_file_list_item, parent, false);
+ } else {
+ actionItemView = (TextView) view;
+ }
+
+ final String file = getItem(position);
+ actionItemView.setText(file);
+ actionItemView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ dismiss();
+ if (ACTION_LOAD.equals(mAction)) {
+ receiveFromDumpFile(file);
+ } else if (ACTION_EMAIL.equals(mAction)) {
+ emailDumpFile(file);
+ }
+ }
+ });
+ return actionItemView;
+ }
+ }
+
+ /**
+ * Load MMS/SMS from the dump file
+ */
+ private void receiveFromDumpFile(final String dumpFileName) {
+ if (dumpFileName.startsWith(MmsUtils.SMS_DUMP_PREFIX)) {
+ final SmsMessage[] messages = DebugUtils.retreiveSmsFromDumpFile(dumpFileName);
+ if (messages != null) {
+ SmsReceiver.deliverSmsMessages(getActivity(), ParticipantData.DEFAULT_SELF_SUB_ID,
+ 0, messages);
+ } else {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "receiveFromDumpFile: invalid sms dump file " + dumpFileName);
+ }
+ } else if (dumpFileName.startsWith(MmsUtils.MMS_DUMP_PREFIX)) {
+ final byte[] data = MmsUtils.createDebugNotificationInd(dumpFileName);
+ if (data != null) {
+ final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction(
+ ParticipantData.DEFAULT_SELF_SUB_ID, data);
+ action.start();
+ } else {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "receiveFromDumpFile: invalid mms dump file " + dumpFileName);
+ }
+ } else {
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "receiveFromDumpFile: invalid dump file name " + dumpFileName);
+ }
+ }
+
+ /**
+ * Launch email app to send the dump file
+ */
+ private void emailDumpFile(final String file) {
+ final Resources resources = getResources();
+ final String fileLocation = "file://"
+ + Environment.getExternalStorageDirectory() + "/" + file;
+ final Intent sharingIntent = new Intent(Intent.ACTION_SEND);
+ sharingIntent.setType(APPLICATION_OCTET_STREAM);
+ sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileLocation));
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT,
+ resources.getString(R.string.email_sms_mms_dump_file_subject));
+ getActivity().startActivity(Intent.createChooser(sharingIntent,
+ resources.getString(R.string.email_sms_mms_dump_file_chooser_title)));
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java b/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java
new file mode 100644
index 0000000..a211058
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Keeps track of the speech level as last observed by the recognition
+ * engine as microphone data flows through it. Can be polled by the UI to
+ * animate its views.
+ */
+@ThreadSafe
+public class AudioLevelSource {
+ private volatile int mSpeechLevel;
+ private volatile Listener mListener;
+
+ public static final int LEVEL_UNKNOWN = -1;
+
+ public interface Listener {
+ void onSpeechLevel(int speechLevel);
+ }
+
+ public void setSpeechLevel(int speechLevel) {
+ Preconditions.checkArgument(speechLevel >= 0 && speechLevel <= 100 ||
+ speechLevel == LEVEL_UNKNOWN);
+ mSpeechLevel = speechLevel;
+ maybeNotify();
+ }
+
+ public int getSpeechLevel() {
+ return mSpeechLevel;
+ }
+
+ public void reset() {
+ setSpeechLevel(LEVEL_UNKNOWN);
+ }
+
+ public boolean isValid() {
+ return mSpeechLevel > 0;
+ }
+
+ private void maybeNotify() {
+ final Listener l = mListener;
+ if (l != null) {
+ l.onSpeechLevel(mSpeechLevel);
+ }
+ }
+
+ public synchronized void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public synchronized void clearListener(Listener listener) {
+ if (mListener == listener) {
+ mListener = null;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
new file mode 100644
index 0000000..5d79293
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
@@ -0,0 +1,130 @@
+/*
+ * 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.Manifest;
+import android.content.pm.PackageManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Chooser which allows the user to record audio
+ */
+class AudioMediaChooser extends MediaChooser implements
+ AudioRecordView.HostInterface {
+ private View mEnabledView;
+ private View mMissingPermissionView;
+
+ AudioMediaChooser(final MediaPicker mediaPicker) {
+ super(mediaPicker);
+ }
+
+ @Override
+ public int getSupportedMediaTypes() {
+ return MediaPicker.MEDIA_TYPE_AUDIO;
+ }
+
+ @Override
+ public int getIconResource() {
+ return R.drawable.ic_audio_light;
+ }
+
+ @Override
+ public int getIconDescriptionResource() {
+ return R.string.mediapicker_audioChooserDescription;
+ }
+
+ @Override
+ public void onAudioRecorded(final MessagePartData item) {
+ mMediaPicker.dispatchItemsSelected(item, true);
+ }
+
+ @Override
+ public void setThemeColor(final int color) {
+ if (mView != null) {
+ ((AudioRecordView) mView).setThemeColor(color);
+ }
+ }
+
+ @Override
+ protected View createView(final ViewGroup container) {
+ final LayoutInflater inflater = getLayoutInflater();
+ final AudioRecordView view = (AudioRecordView) inflater.inflate(
+ R.layout.mediapicker_audio_chooser,
+ container /* root */,
+ false /* attachToRoot */);
+ view.setHostInterface(this);
+ view.setThemeColor(mMediaPicker.getConversationThemeColor());
+ mEnabledView = view.findViewById(R.id.mediapicker_enabled);
+ mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
+ return view;
+ }
+
+ @Override
+ int getActionBarTitleResId() {
+ return R.string.mediapicker_audio_title;
+ }
+
+ @Override
+ public boolean isHandlingTouch() {
+ // Whenever the user is in the process of recording audio, we want to allow the user
+ // to move the finger within the panel without interpreting that as dragging the media
+ // picker panel.
+ return ((AudioRecordView) mView).shouldHandleTouch();
+ }
+
+ @Override
+ public void stopTouchHandling() {
+ ((AudioRecordView) mView).stopTouchHandling();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mView != null) {
+ ((AudioRecordView) mView).onPause();
+ }
+ }
+
+ @Override
+ protected void setSelected(final boolean selected) {
+ super.setSelected(selected);
+ if (selected && !OsUtil.hasRecordAudioPermission()) {
+ requestRecordAudioPermission();
+ }
+ }
+
+ private void requestRecordAudioPermission() {
+ mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
+ MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
+ }
+
+ @Override
+ protected void onRequestPermissionsResult(
+ 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;
+ mEnabledView.setVisibility(permissionGranted ? View.VISIBLE : View.GONE);
+ mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java b/src/com/android/messaging/ui/mediapicker/AudioRecordView.java
new file mode 100644
index 0000000..fba493f
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioRecordView.java
@@ -0,0 +1,351 @@
+/*
+ * 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.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
+import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.MediaUtil;
+import com.android.messaging.util.MediaUtil.OnCompletionListener;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.ThreadUtil;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Hosts an audio recorder with tap and hold to record functionality.
+ */
+public class AudioRecordView extends FrameLayout implements
+ MediaRecorder.OnErrorListener,
+ MediaRecorder.OnInfoListener {
+ /**
+ * An interface that communicates with the hosted AudioRecordView.
+ */
+ public interface HostInterface extends DraftMessageSubscriptionDataProvider {
+ void onAudioRecorded(final MessagePartData item);
+ }
+
+ /** The initial state, the user may press and hold to start recording */
+ private static final int MODE_IDLE = 1;
+
+ /** The user has pressed the record button and we are playing the sound indicating the
+ * start of recording session. Don't record yet since we don't want the beeping sound
+ * to get into the recording. */
+ private static final int MODE_STARTING = 2;
+
+ /** When the user is actively recording */
+ private static final int MODE_RECORDING = 3;
+
+ /** When the user has finished recording, we need to record for some additional time. */
+ private static final int MODE_STOPPING = 4;
+
+ // Bug: 16020175: The framework's MediaRecorder would cut off the ending portion of the
+ // recorded audio by about half a second. To mitigate this issue, we continue the recording
+ // for some extra time before stopping it.
+ private static final int AUDIO_RECORD_ENDING_BUFFER_MILLIS = 500;
+
+ /**
+ * The minimum duration of any recording. Below this threshold, it will be treated as if the
+ * user clicked the record button and inform the user to tap and hold to record.
+ */
+ private static final int AUDIO_RECORD_MINIMUM_DURATION_MILLIS = 300;
+
+ // For accessibility, the touchable record button is bigger than the record button visual.
+ private ImageView mRecordButtonVisual;
+ private View mRecordButton;
+ private SoundLevels mSoundLevels;
+ private TextView mHintTextView;
+ private PausableChronometer mTimerTextView;
+ private LevelTrackingMediaRecorder mMediaRecorder;
+ private long mAudioRecordStartTimeMillis;
+
+ private int mCurrentMode = MODE_IDLE;
+ private HostInterface mHostInterface;
+ private int mThemeColor;
+
+ public AudioRecordView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mMediaRecorder = new LevelTrackingMediaRecorder();
+ }
+
+ public void setHostInterface(final HostInterface hostInterface) {
+ mHostInterface = hostInterface;
+ }
+
+ @VisibleForTesting
+ public void testSetMediaRecorder(final LevelTrackingMediaRecorder recorder) {
+ mMediaRecorder = recorder;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSoundLevels = (SoundLevels) findViewById(R.id.sound_levels);
+ mRecordButtonVisual = (ImageView) findViewById(R.id.record_button_visual);
+ mRecordButton = findViewById(R.id.record_button);
+ mHintTextView = (TextView) findViewById(R.id.hint_text);
+ mTimerTextView = (PausableChronometer) findViewById(R.id.timer_text);
+ mSoundLevels.setLevelSource(mMediaRecorder.getLevelSource());
+ mRecordButton.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(final View v, final MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ onRecordButtonTouchDown();
+
+ // Don't let the record button handle the down event to let it fall through
+ // so that we can handle it for the entire panel in onTouchEvent(). This is
+ // done so that: 1) the user taps on the record button to start recording
+ // 2) the entire panel owns the touch event so we'd keep recording even
+ // if the user moves outside the button region.
+ return false;
+ }
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public boolean onTouchEvent(final MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ return shouldHandleTouch();
+
+ case MotionEvent.ACTION_MOVE:
+ return true;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ return onRecordButtonTouchUp();
+ }
+ return super.onTouchEvent(event);
+ }
+
+ public void onPause() {
+ // The conversation draft cannot take any updates when it's paused. Therefore, forcibly
+ // stop recording on pause.
+ stopRecording();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ stopRecording();
+ }
+
+ private boolean isRecording() {
+ return mMediaRecorder.isRecording() && mCurrentMode == MODE_RECORDING;
+ }
+
+ public boolean shouldHandleTouch() {
+ return mCurrentMode != MODE_IDLE;
+ }
+
+ public void stopTouchHandling() {
+ setMode(MODE_IDLE);
+ stopRecording();
+ }
+
+ private void setMode(final int mode) {
+ if (mCurrentMode != mode) {
+ mCurrentMode = mode;
+ updateVisualState();
+ }
+ }
+
+ private void updateVisualState() {
+ switch (mCurrentMode) {
+ case MODE_IDLE:
+ mHintTextView.setVisibility(VISIBLE);
+ mHintTextView.setTypeface(null, Typeface.NORMAL);
+ mTimerTextView.setVisibility(GONE);
+ mSoundLevels.setEnabled(false);
+ mTimerTextView.stop();
+ break;
+
+ case MODE_RECORDING:
+ case MODE_STOPPING:
+ mHintTextView.setVisibility(GONE);
+ mTimerTextView.setVisibility(VISIBLE);
+ mSoundLevels.setEnabled(true);
+ mTimerTextView.restart();
+ break;
+
+ case MODE_STARTING:
+ break; // No-Op.
+
+ default:
+ Assert.fail("invalid mode for AudioRecordView!");
+ break;
+ }
+ updateRecordButtonAppearance();
+ }
+
+ public void setThemeColor(final int color) {
+ mThemeColor = color;
+ updateRecordButtonAppearance();
+ }
+
+ private void updateRecordButtonAppearance() {
+ final Drawable foregroundDrawable = getResources().getDrawable(R.drawable.ic_mp_audio_mic);
+ final GradientDrawable backgroundDrawable = ((GradientDrawable) getResources()
+ .getDrawable(R.drawable.audio_record_control_button_background));
+ if (isRecording()) {
+ foregroundDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
+ backgroundDrawable.setColor(mThemeColor);
+ } else {
+ foregroundDrawable.setColorFilter(mThemeColor, PorterDuff.Mode.SRC_ATOP);
+ backgroundDrawable.setColor(Color.WHITE);
+ }
+ mRecordButtonVisual.setImageDrawable(foregroundDrawable);
+ mRecordButtonVisual.setBackground(backgroundDrawable);
+ }
+
+ @VisibleForTesting
+ boolean onRecordButtonTouchDown() {
+ if (!mMediaRecorder.isRecording() && mCurrentMode == MODE_IDLE) {
+ setMode(MODE_STARTING);
+ playAudioStartSound(new OnCompletionListener() {
+ @Override
+ public void onCompletion() {
+ // Double-check the current mode before recording since the user may have
+ // lifted finger from the button before the beeping sound is played through.
+ final int maxSize = MmsConfig.get(mHostInterface.getConversationSelfSubId())
+ .getMaxMessageSize();
+ if (mCurrentMode == MODE_STARTING &&
+ mMediaRecorder.startRecording(AudioRecordView.this,
+ AudioRecordView.this, maxSize)) {
+ setMode(MODE_RECORDING);
+ }
+ }
+ });
+ mAudioRecordStartTimeMillis = System.currentTimeMillis();
+ return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ boolean onRecordButtonTouchUp() {
+ if (System.currentTimeMillis() - mAudioRecordStartTimeMillis <
+ AUDIO_RECORD_MINIMUM_DURATION_MILLIS) {
+ // The recording is too short, bolden the hint text to instruct the user to
+ // "tap+hold" to record audio.
+ final Uri outputUri = stopRecording();
+ if (outputUri != null) {
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ Factory.get().getApplicationContext().getContentResolver().delete(
+ outputUri, null, null);
+ }
+ });
+ }
+ setMode(MODE_IDLE);
+ mHintTextView.setTypeface(null, Typeface.BOLD);
+ } else if (isRecording()) {
+ // Record for some extra time to ensure the ending part is saved.
+ setMode(MODE_STOPPING);
+ ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onFinishedRecording();
+ }
+ }, AUDIO_RECORD_ENDING_BUFFER_MILLIS);
+ } else {
+ setMode(MODE_IDLE);
+ }
+ return true;
+ }
+
+ private Uri stopRecording() {
+ if (mMediaRecorder.isRecording()) {
+ return mMediaRecorder.stopRecording();
+ }
+ return null;
+ }
+
+ @Override // From MediaRecorder.OnInfoListener
+ public void onInfo(final MediaRecorder mr, final int what, final int extra) {
+ if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+ // Max size reached. Finish recording immediately.
+ LogUtil.i(LogUtil.BUGLE_TAG, "Max size reached while recording audio");
+ onFinishedRecording();
+ } else {
+ // These are unknown errors.
+ onErrorWhileRecording(what, extra);
+ }
+ }
+
+ @Override // From MediaRecorder.OnErrorListener
+ public void onError(final MediaRecorder mr, final int what, final int extra) {
+ onErrorWhileRecording(what, extra);
+ }
+
+ private void onErrorWhileRecording(final int what, final int extra) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error occurred during audio recording what=" + what +
+ ", extra=" + extra);
+ UiUtils.showToastAtBottom(R.string.audio_recording_error);
+ setMode(MODE_IDLE);
+ stopRecording();
+ }
+
+ private void onFinishedRecording() {
+ final Uri outputUri = stopRecording();
+ if (outputUri != null) {
+ final Rect startRect = new Rect();
+ mRecordButtonVisual.getGlobalVisibleRect(startRect);
+ final MediaPickerMessagePartData audioItem =
+ new MediaPickerMessagePartData(startRect,
+ ContentType.AUDIO_3GPP, outputUri, 0, 0);
+ mHostInterface.onAudioRecorded(audioItem);
+ }
+ playAudioEndSound();
+ setMode(MODE_IDLE);
+ }
+
+ private void playAudioStartSound(final OnCompletionListener completionListener) {
+ MediaUtil.get().playSound(getContext(), R.raw.audio_initiate, completionListener);
+ }
+
+ private void playAudioEndSound() {
+ MediaUtil.get().playSound(getContext(), R.raw.audio_end, null);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraManager.java b/src/com/android/messaging/ui/mediapicker/CameraManager.java
new file mode 100644
index 0000000..166ebd7
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/CameraManager.java
@@ -0,0 +1,1200 @@
+/*
+ * 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.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.sms.MmsConfig;
+import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager;
+import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BugleGservicesKeys;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Class which manages interactions with the camera, but does not do any UI. This class is
+ * designed to be a singleton to ensure there is one component managing the camera and releasing
+ * the native resources.
+ * In order to acquire a camera, a caller must:
+ * <ul>
+ * <li>Call selectCamera to select front or back camera
+ * <li>Call setSurface to control where the preview is shown
+ * <li>Call openCamera to request the camera start preview
+ * </ul>
+ * Callers should call onPause and onResume to ensure that the camera is release while the activity
+ * is not active.
+ * This class is not thread safe. It should only be called from one thread (the UI thread or test
+ * thread)
+ */
+class CameraManager implements FocusOverlayManager.Listener {
+ /**
+ * Wrapper around the framework camera API to allow mocking different hardware scenarios while
+ * unit testing
+ */
+ interface CameraWrapper {
+ int getNumberOfCameras();
+ void getCameraInfo(int index, CameraInfo cameraInfo);
+ Camera open(int cameraId);
+ /** Add a wrapper for release because a final method cannot be mocked */
+ void release(Camera camera);
+ }
+
+ /**
+ * Callbacks for the camera manager listener
+ */
+ interface CameraManagerListener {
+ void onCameraError(int errorCode, Exception e);
+ void onCameraChanged();
+ }
+
+ /**
+ * Callback when taking image or video
+ */
+ interface MediaCallback {
+ static final int MEDIA_CAMERA_CHANGED = 1;
+ static final int MEDIA_NO_DATA = 2;
+
+ void onMediaReady(Uri uriToMedia, String contentType, int width, int height);
+ void onMediaFailed(Exception exception);
+ void onMediaInfo(int what);
+ }
+
+ // Error codes
+ static final int ERROR_OPENING_CAMERA = 1;
+ static final int ERROR_SHOWING_PREVIEW = 2;
+ static final int ERROR_INITIALIZING_VIDEO = 3;
+ static final int ERROR_STORAGE_FAILURE = 4;
+ static final int ERROR_RECORDING_VIDEO = 5;
+ static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6;
+ static final int ERROR_TAKING_PICTURE = 7;
+
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final int NO_CAMERA_SELECTED = -1;
+
+ private static CameraManager sInstance;
+
+ /** Default camera wrapper which directs calls to the framework APIs */
+ private static CameraWrapper sCameraWrapper = new CameraWrapper() {
+ @Override
+ public int getNumberOfCameras() {
+ return Camera.getNumberOfCameras();
+ }
+
+ @Override
+ public void getCameraInfo(final int index, final CameraInfo cameraInfo) {
+ Camera.getCameraInfo(index, cameraInfo);
+ }
+
+ @Override
+ public Camera open(final int cameraId) {
+ return Camera.open(cameraId);
+ }
+
+ @Override
+ public void release(final Camera camera) {
+ camera.release();
+ }
+ };
+
+ /** The CameraInfo for the currently selected camera */
+ private final CameraInfo mCameraInfo;
+
+ /**
+ * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet
+ */
+ private int mCameraIndex;
+
+ /** True if the device has front and back cameras */
+ private final boolean mHasFrontAndBackCamera;
+
+ /** True if the camera should be open (may not yet be actually open) */
+ private boolean mOpenRequested;
+
+ /** True if the camera is requested to be in video mode */
+ private boolean mVideoModeRequested;
+
+ /** The media recorder for video mode */
+ private MmsVideoRecorder mMediaRecorder;
+
+ /** Callback to call with video recording updates */
+ private MediaCallback mVideoCallback;
+
+ /** The preview view to show the preview on */
+ private CameraPreview mCameraPreview;
+
+ /** The helper classs to handle orientation changes */
+ private OrientationHandler mOrientationHandler;
+
+ /** Tracks whether the preview has hardware acceleration */
+ private boolean mIsHardwareAccelerationSupported;
+
+ /**
+ * The task for opening the camera, so it doesn't block the UI thread
+ * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't
+ * need to be on the UI thread
+ * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may
+ * need to create a dedicated thread, or synchronize the threads in the thread pool
+ */
+ private AsyncTask<Integer, Void, Camera> mOpenCameraTask;
+
+ /**
+ * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if
+ * no open task is pending
+ */
+ private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
+
+ /** The instance of the currently opened camera */
+ private Camera mCamera;
+
+ /** The rotation of the screen relative to the camera's natural orientation */
+ private int mRotation;
+
+ /** The callback to notify when errors or other events occur */
+ private CameraManagerListener mListener;
+
+ /** True if the camera is currently in the process of taking an image */
+ private boolean mTakingPicture;
+
+ /** Provides subscription-related data to access per-subscription configurations. */
+ private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
+
+ /** Manages auto focus visual and behavior */
+ private final FocusOverlayManager mFocusOverlayManager;
+
+ private CameraManager() {
+ mCameraInfo = new CameraInfo();
+ mCameraIndex = NO_CAMERA_SELECTED;
+
+ // Check to see if a front and back camera exist
+ boolean hasFrontCamera = false;
+ boolean hasBackCamera = false;
+ final CameraInfo cameraInfo = new CameraInfo();
+ final int cameraCount = sCameraWrapper.getNumberOfCameras();
+ try {
+ for (int i = 0; i < cameraCount; i++) {
+ sCameraWrapper.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
+ hasFrontCamera = true;
+ } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
+ hasBackCamera = true;
+ }
+ if (hasFrontCamera && hasBackCamera) {
+ break;
+ }
+ }
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "Unable to load camera info", e);
+ }
+ mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera;
+ mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper());
+
+ // Assume the best until we are proven otherwise
+ mIsHardwareAccelerationSupported = true;
+ }
+
+ /** Gets the singleton instance */
+ static CameraManager get() {
+ if (sInstance == null) {
+ sInstance = new CameraManager();
+ }
+ return sInstance;
+ }
+
+ /** Allows tests to inject a custom camera wrapper */
+ @VisibleForTesting
+ static void setCameraWrapper(final CameraWrapper cameraWrapper) {
+ sCameraWrapper = cameraWrapper;
+ sInstance = null;
+ }
+
+ /**
+ * Sets the surface to use to display the preview
+ * This must only be called AFTER the CameraPreview has a texture ready
+ * @param preview The preview surface view
+ */
+ void setSurface(final CameraPreview preview) {
+ if (preview == mCameraPreview) {
+ return;
+ }
+
+ if (preview != null) {
+ Assert.isTrue(preview.isValid());
+ preview.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(final View view, final MotionEvent motionEvent) {
+ if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) ==
+ MotionEvent.ACTION_UP) {
+ mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight());
+ mFocusOverlayManager.onSingleTapUp(
+ (int) motionEvent.getX() + view.getLeft(),
+ (int) motionEvent.getY() + view.getTop());
+ }
+ return true;
+ }
+ });
+ }
+ mCameraPreview = preview;
+ tryShowPreview();
+ }
+
+ void setRenderOverlay(final RenderOverlay renderOverlay) {
+ mFocusOverlayManager.setFocusRenderer(renderOverlay != null ?
+ renderOverlay.getPieRenderer() : null);
+ }
+
+ /** Convenience function to swap between front and back facing cameras */
+ void swapCamera() {
+ Assert.isTrue(mCameraIndex >= 0);
+ selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ?
+ CameraInfo.CAMERA_FACING_BACK :
+ CameraInfo.CAMERA_FACING_FRONT);
+ }
+
+ /**
+ * Selects the first camera facing the desired direction, or the first camera if there is no
+ * camera in the desired direction
+ * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants
+ * @return True if a camera was selected, or false if selecting a camera failed
+ */
+ boolean selectCamera(final int desiredFacing) {
+ try {
+ // We already selected a camera facing that direction
+ if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) {
+ return true;
+ }
+
+ final int cameraCount = sCameraWrapper.getNumberOfCameras();
+ Assert.isTrue(cameraCount > 0);
+
+ mCameraIndex = NO_CAMERA_SELECTED;
+ setCamera(null);
+ final CameraInfo cameraInfo = new CameraInfo();
+ for (int i = 0; i < cameraCount; i++) {
+ sCameraWrapper.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == desiredFacing) {
+ mCameraIndex = i;
+ sCameraWrapper.getCameraInfo(i, mCameraInfo);
+ break;
+ }
+ }
+
+ // There's no camera in the desired facing direction, just select the first camera
+ // regardless of direction
+ if (mCameraIndex < 0) {
+ mCameraIndex = 0;
+ sCameraWrapper.getCameraInfo(0, mCameraInfo);
+ }
+
+ if (mOpenRequested) {
+ // The camera is open, so reopen with the newly selected camera
+ openCamera();
+ }
+ return true;
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_OPENING_CAMERA, e);
+ }
+ return false;
+ }
+ }
+
+ int getCameraIndex() {
+ return mCameraIndex;
+ }
+
+ void selectCameraByIndex(final int cameraIndex) {
+ if (mCameraIndex == cameraIndex) {
+ return;
+ }
+
+ try {
+ mCameraIndex = cameraIndex;
+ sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo);
+ if (mOpenRequested) {
+ openCamera();
+ }
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_OPENING_CAMERA, e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ CameraInfo getCameraInfo() {
+ if (mCameraIndex == NO_CAMERA_SELECTED) {
+ return null;
+ }
+ return mCameraInfo;
+ }
+
+ /** @return True if this device has camera capabilities */
+ boolean hasAnyCamera() {
+ return sCameraWrapper.getNumberOfCameras() > 0;
+ }
+
+ /** @return True if the device has both a front and back camera */
+ boolean hasFrontAndBackCamera() {
+ return mHasFrontAndBackCamera;
+ }
+
+ /**
+ * Opens the camera on a separate thread and initiates the preview if one is available
+ */
+ void openCamera() {
+ if (mCameraIndex == NO_CAMERA_SELECTED) {
+ // Ensure a selected camera if none is currently selected. This may happen if the
+ // camera chooser is not the default media chooser.
+ selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ }
+ mOpenRequested = true;
+ // We're already opening the camera or already have the camera handle, nothing more to do
+ if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) {
+ return;
+ }
+
+ // True if the task to open the camera has to be delayed until the current one completes
+ boolean delayTask = false;
+
+ // Cancel any previous open camera tasks
+ if (mOpenCameraTask != null) {
+ mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
+ delayTask = true;
+ }
+
+ mPendingOpenCameraIndex = mCameraIndex;
+ mOpenCameraTask = new AsyncTask<Integer, Void, Camera>() {
+ private Exception mException;
+
+ @Override
+ protected Camera doInBackground(final Integer... params) {
+ try {
+ final int cameraIndex = params[0];
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Opening camera " + mCameraIndex);
+ }
+ return sCameraWrapper.open(cameraIndex);
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "Exception while opening camera", e);
+ mException = e;
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(final Camera camera) {
+ // If we completed, but no longer want this camera, then release the camera
+ if (mOpenCameraTask != this || !mOpenRequested) {
+ releaseCamera(camera);
+ cleanup();
+ return;
+ }
+
+ cleanup();
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null));
+ }
+
+ setCamera(camera);
+ if (camera == null) {
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_OPENING_CAMERA, mException);
+ }
+ LogUtil.e(TAG, "Error opening camera");
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+ cleanup();
+ }
+
+ private void cleanup() {
+ mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
+ if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) {
+ // If there's another task waiting on this one to complete, start it now
+ mOpenCameraTask.execute(mCameraIndex);
+ } else {
+ mOpenCameraTask = null;
+ }
+
+ }
+ };
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Start opening camera " + mCameraIndex);
+ }
+
+ if (!delayTask) {
+ mOpenCameraTask.execute(mCameraIndex);
+ }
+ }
+
+ boolean isVideoMode() {
+ return mVideoModeRequested;
+ }
+
+ boolean isRecording() {
+ return mVideoModeRequested && mVideoCallback != null;
+ }
+
+ void setVideoMode(final boolean videoMode) {
+ if (mVideoModeRequested == videoMode) {
+ return;
+ }
+ mVideoModeRequested = videoMode;
+ tryInitOrCleanupVideoMode();
+ }
+
+ /** Closes the camera releasing the resources it uses */
+ void closeCamera() {
+ mOpenRequested = false;
+ setCamera(null);
+ }
+
+ /** Temporarily closes the camera if it is open */
+ void onPause() {
+ setCamera(null);
+ }
+
+ /** Reopens the camera if it was opened when onPause was called */
+ void onResume() {
+ if (mOpenRequested) {
+ openCamera();
+ }
+ }
+
+ /**
+ * Sets the listener which will be notified of errors or other events in the camera
+ * @param listener The listener to notify
+ */
+ void setListener(final CameraManagerListener listener) {
+ Assert.isMainThread();
+ mListener = listener;
+ if (!mIsHardwareAccelerationSupported && mListener != null) {
+ mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
+ }
+ }
+
+ void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
+ mSubscriptionDataProvider = provider;
+ }
+
+ void takePicture(final float heightPercent, @NonNull final MediaCallback callback) {
+ Assert.isTrue(!mVideoModeRequested);
+ Assert.isTrue(!mTakingPicture);
+ Assert.notNull(callback);
+ if (mCamera == null) {
+ // The caller should have checked isCameraAvailable first, but just in case, protect
+ // against a null camera by notifying the callback that taking the picture didn't work
+ callback.onMediaFailed(null);
+ return;
+ }
+ final Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
+ @Override
+ public void onPictureTaken(final byte[] bytes, final Camera camera) {
+ mTakingPicture = false;
+ if (mCamera != camera) {
+ // This may happen if the camera was changed between front/back while the
+ // picture is being taken.
+ callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED);
+ return;
+ }
+
+ if (bytes == null) {
+ callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA);
+ return;
+ }
+
+ final Camera.Size size = camera.getParameters().getPictureSize();
+ int width;
+ int height;
+ if (mRotation == 90 || mRotation == 270) {
+ width = size.height;
+ height = size.width;
+ } else {
+ width = size.width;
+ height = size.height;
+ }
+ new ImagePersistTask(
+ width, height, heightPercent, bytes, mCameraPreview.getContext(), callback)
+ .executeOnThreadPool();
+ }
+ };
+
+ mTakingPicture = true;
+ try {
+ mCamera.takePicture(
+ null /* shutter */,
+ null /* raw */,
+ null /* postView */,
+ jpegCallback);
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e);
+ mTakingPicture = false;
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_TAKING_PICTURE, e);
+ }
+ }
+ }
+
+ void startVideo(final MediaCallback callback) {
+ Assert.notNull(callback);
+ Assert.isTrue(!isRecording());
+ mVideoCallback = callback;
+ tryStartVideoCapture();
+ }
+
+ /**
+ * Asynchronously releases a camera
+ * @param camera The camera to release
+ */
+ private void releaseCamera(final Camera camera) {
+ if (camera == null) {
+ return;
+ }
+
+ mFocusOverlayManager.onCameraReleased();
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(final Void... params) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "Releasing camera " + mCameraIndex);
+ }
+ sCameraWrapper.release(camera);
+ return null;
+ }
+ }.execute();
+ }
+
+ private void releaseMediaRecorder(final boolean cleanupFile) {
+ if (mMediaRecorder == null) {
+ return;
+ }
+ mVideoModeRequested = false;
+
+ if (cleanupFile) {
+ mMediaRecorder.cleanupTempFile();
+ if (mVideoCallback != null) {
+ final MediaCallback callback = mVideoCallback;
+ mVideoCallback = null;
+ // Notify the callback that we've stopped recording
+ callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/,
+ 0 /*height*/);
+ }
+ }
+
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+
+ if (mCamera != null) {
+ try {
+ mCamera.reconnect();
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_OPENING_CAMERA, e);
+ }
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_OPENING_CAMERA, e);
+ }
+ }
+ }
+ restoreRequestedOrientation();
+ }
+
+ /** Updates the orientation of the camera to match the orientation of the device */
+ private void updateCameraOrientation() {
+ if (mCamera == null || mCameraPreview == null || mTakingPicture) {
+ return;
+ }
+
+ final WindowManager windowManager =
+ (WindowManager) mCameraPreview.getContext().getSystemService(
+ Context.WINDOW_SERVICE);
+
+ int degrees = 0;
+ switch (windowManager.getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_0: degrees = 0; break;
+ case Surface.ROTATION_90: degrees = 90; break;
+ case Surface.ROTATION_180: degrees = 180; break;
+ case Surface.ROTATION_270: degrees = 270; break;
+ }
+
+ // The display orientation of the camera (this controls the preview image).
+ int orientation;
+
+ // The clockwise rotation angle relative to the orientation of the camera. This affects
+ // pictures returned by the camera in Camera.PictureCallback.
+ int rotation;
+ if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ orientation = (mCameraInfo.orientation + degrees) % 360;
+ rotation = orientation;
+ // compensate the mirror but only for orientation
+ orientation = (360 - orientation) % 360;
+ } else { // back-facing
+ orientation = (mCameraInfo.orientation - degrees + 360) % 360;
+ rotation = orientation;
+ }
+ mRotation = rotation;
+ if (mMediaRecorder == null) {
+ try {
+ mCamera.setDisplayOrientation(orientation);
+ final Camera.Parameters params = mCamera.getParameters();
+ params.setRotation(rotation);
+ mCamera.setParameters(params);
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_OPENING_CAMERA, e);
+ }
+ }
+ }
+ }
+
+ /** Sets the current camera, releasing any previously opened camera */
+ private void setCamera(final Camera camera) {
+ if (mCamera == camera) {
+ return;
+ }
+
+ releaseMediaRecorder(true /* cleanupFile */);
+ releaseCamera(mCamera);
+ mCamera = camera;
+ tryShowPreview();
+ if (mListener != null) {
+ mListener.onCameraChanged();
+ }
+ }
+
+ /** Shows the preview if the camera is open and the preview is loaded */
+ private void tryShowPreview() {
+ if (mCameraPreview == null || mCamera == null) {
+ if (mOrientationHandler != null) {
+ mOrientationHandler.disable();
+ mOrientationHandler = null;
+ }
+ releaseMediaRecorder(true /* cleanupFile */);
+ mFocusOverlayManager.onPreviewStopped();
+ return;
+ }
+ try {
+ mCamera.stopPreview();
+ updateCameraOrientation();
+
+ final Camera.Parameters params = mCamera.getParameters();
+ final Camera.Size pictureSize = chooseBestPictureSize();
+ final Camera.Size previewSize = chooseBestPreviewSize(pictureSize);
+ params.setPreviewSize(previewSize.width, previewSize.height);
+ params.setPictureSize(pictureSize.width, pictureSize.height);
+ logCameraSize("Setting preview size: ", previewSize);
+ logCameraSize("Setting picture size: ", pictureSize);
+ mCameraPreview.setSize(previewSize, mCameraInfo.orientation);
+ for (final String focusMode : params.getSupportedFocusModes()) {
+ if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ // Use continuous focus if available
+ params.setFocusMode(focusMode);
+ break;
+ }
+ }
+
+ mCamera.setParameters(params);
+ mCameraPreview.startPreview(mCamera);
+ mCamera.startPreview();
+ mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
+ @Override
+ public void onAutoFocusMoving(final boolean start, final Camera camera) {
+ mFocusOverlayManager.onAutoFocusMoving(start);
+ }
+ });
+ mFocusOverlayManager.setParameters(mCamera.getParameters());
+ mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK);
+ mFocusOverlayManager.onPreviewStarted();
+ tryInitOrCleanupVideoMode();
+ if (mOrientationHandler == null) {
+ mOrientationHandler = new OrientationHandler(mCameraPreview.getContext());
+ mOrientationHandler.enable();
+ }
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
+ }
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
+ }
+ }
+ }
+
+ private void tryInitOrCleanupVideoMode() {
+ if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) {
+ releaseMediaRecorder(true /* cleanupFile */);
+ return;
+ }
+
+ if (mMediaRecorder != null) {
+ return;
+ }
+
+ try {
+ mCamera.unlock();
+ final int maxMessageSize = getMmsConfig().getMaxMessageSize();
+ mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize);
+ mMediaRecorder.prepare();
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_STORAGE_FAILURE, e);
+ }
+ setVideoMode(false);
+ return;
+ } catch (final IOException e) {
+ LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
+ }
+ setVideoMode(false);
+ return;
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
+ }
+ setVideoMode(false);
+ return;
+ }
+
+ tryStartVideoCapture();
+ }
+
+ private void tryStartVideoCapture() {
+ if (mMediaRecorder == null || mVideoCallback == null) {
+ return;
+ }
+
+ mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
+ @Override
+ public void onError(final MediaRecorder mediaRecorder, final int what,
+ final int extra) {
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_RECORDING_VIDEO, null);
+ }
+ restoreRequestedOrientation();
+ }
+ });
+
+ mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
+ @Override
+ public void onInfo(final MediaRecorder mediaRecorder, final int what, final int extra) {
+ if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
+ what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+ stopVideo();
+ }
+ }
+ });
+
+ try {
+ mMediaRecorder.start();
+ final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
+ activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ lockOrientation();
+ } catch (final IllegalStateException e) {
+ LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
+ }
+ setVideoMode(false);
+ restoreRequestedOrientation();
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e);
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
+ }
+ setVideoMode(false);
+ restoreRequestedOrientation();
+ }
+ }
+
+ void stopVideo() {
+ int width = ImageRequest.UNSPECIFIED_SIZE;
+ int height = ImageRequest.UNSPECIFIED_SIZE;
+ Uri uri = null;
+ String contentType = null;
+ try {
+ final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
+ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mMediaRecorder.stop();
+ width = mMediaRecorder.getVideoWidth();
+ height = mMediaRecorder.getVideoHeight();
+ uri = mMediaRecorder.getVideoUri();
+ contentType = mMediaRecorder.getContentType();
+ } catch (final RuntimeException e) {
+ // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the
+ // finally clause call the callback with null uri and handle cleanup
+ LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e);
+ } finally {
+ final MediaCallback videoCallback = mVideoCallback;
+ mVideoCallback = null;
+ releaseMediaRecorder(false /* cleanupFile */);
+ if (uri == null) {
+ tryInitOrCleanupVideoMode();
+ }
+ videoCallback.onMediaReady(uri, contentType, width, height);
+ }
+ }
+
+ boolean isCameraAvailable() {
+ return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported;
+ }
+
+ /**
+ * External components call into this to report if hardware acceleration is supported. When
+ * hardware acceleration isn't supported, we need to report an error through the listener
+ * interface
+ * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware
+ * accelerated view.
+ */
+ void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) {
+ Assert.isMainThread();
+ if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) {
+ // If the value hasn't changed nothing more to do
+ return;
+ }
+
+ mIsHardwareAccelerationSupported = isHardwareAccelerationSupported;
+ if (!isHardwareAccelerationSupported) {
+ LogUtil.e(TAG, "Software rendering - cannot open camera");
+ if (mListener != null) {
+ mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
+ }
+ }
+ }
+
+ /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */
+ private float getScaleFactorForMaxAllowedSize(final int width, final int height,
+ final int maxWidth, final int maxHeight) {
+ if (maxWidth <= 0 || maxHeight <= 0) {
+ // MmsConfig initialization runs asynchronously on application startup, so there's a
+ // chance (albeit a very slight one) that we don't have it yet.
+ LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig");
+ return 1.0f;
+ }
+
+ if (width <= maxWidth && height <= maxHeight) {
+ // Already meeting requirements.
+ return 1.0f;
+ }
+
+ return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height);
+ }
+
+ private MmsConfig getMmsConfig() {
+ final int subId = mSubscriptionDataProvider != null ?
+ mSubscriptionDataProvider.getConversationSelfSubId() :
+ ParticipantData.DEFAULT_SELF_SUB_ID;
+ return MmsConfig.get(subId);
+ }
+
+ /**
+ * Choose the best picture size by trying to find a size close to the MmsConfig's max size,
+ * which is closest to the screen aspect ratio
+ */
+ private Camera.Size chooseBestPictureSize() {
+ final Context context = mCameraPreview.getContext();
+ final Resources resources = context.getResources();
+ final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
+ final int displayOrientation = resources.getConfiguration().orientation;
+ int cameraOrientation = mCameraInfo.orientation;
+
+ int screenWidth;
+ int screenHeight;
+ if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ // Rotate the camera orientation 90 degrees to compensate for the rotated display
+ // metrics. Direction doesn't matter because we're just using it for width/height
+ cameraOrientation += 90;
+ }
+
+ // Check the camera orientation relative to the display.
+ // For 0, 180, 360, the screen width/height are the display width/height
+ // For 90, 270, the screen width/height are inverted from the display
+ if (cameraOrientation % 180 == 0) {
+ screenWidth = displayMetrics.widthPixels;
+ screenHeight = displayMetrics.heightPixels;
+ } else {
+ screenWidth = displayMetrics.heightPixels;
+ screenHeight = displayMetrics.widthPixels;
+ }
+
+ final MmsConfig mmsConfig = getMmsConfig();
+ final int maxWidth = mmsConfig.getMaxImageWidth();
+ final int maxHeight = mmsConfig.getMaxImageHeight();
+
+ // Constrain the size within the max width/height defined by MmsConfig.
+ final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight,
+ maxWidth, maxHeight);
+ screenWidth *= scaleFactor;
+ screenHeight *= scaleFactor;
+
+ final float aspectRatio = BugleGservices.get().getFloat(
+ BugleGservicesKeys.CAMERA_ASPECT_RATIO,
+ screenWidth / (float) screenHeight);
+ final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
+ mCamera.getParameters().getSupportedPictureSizes());
+ final int maxPixels = maxWidth * maxHeight;
+
+ // Sort the sizes so the best size is first
+ Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels));
+
+ return sizes.get(0);
+ }
+
+ /**
+ * Chose the best preview size based on the picture size. Try to find a size with the same
+ * aspect ratio and size as the picture if possible
+ */
+ private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) {
+ final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
+ mCamera.getParameters().getSupportedPreviewSizes());
+ final float aspectRatio = pictureSize.width / (float) pictureSize.height;
+ final int capturePixels = pictureSize.width * pictureSize.height;
+
+ // Sort the sizes so the best size is first
+ Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE,
+ aspectRatio, capturePixels));
+
+ return sizes.get(0);
+ }
+
+ private class OrientationHandler extends OrientationEventListener {
+ OrientationHandler(final Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(final int orientation) {
+ updateCameraOrientation();
+ }
+ }
+
+ private static class SizeComparator implements Comparator<Camera.Size> {
+ private static final int PREFER_LEFT = -1;
+ private static final int PREFER_RIGHT = 1;
+
+ // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit
+ private final int mMaxWidth;
+ private final int mMaxHeight;
+
+ // The desired aspect ratio
+ private final float mTargetAspectRatio;
+
+ // The desired size (width x height) to try to match
+ private final int mTargetPixels;
+
+ public SizeComparator(final int maxWidth, final int maxHeight,
+ final float targetAspectRatio, final int targetPixels) {
+ mMaxWidth = maxWidth;
+ mMaxHeight = maxHeight;
+ mTargetAspectRatio = targetAspectRatio;
+ mTargetPixels = targetPixels;
+ }
+
+ /**
+ * Returns a negative value if left is a better choice than right, or a positive value if
+ * right is a better choice is better than left. 0 if they are equal
+ */
+ @Override
+ public int compare(final Camera.Size left, final Camera.Size right) {
+ // If one size is less than the max size prefer it over the other
+ if ((left.width <= mMaxWidth && left.height <= mMaxHeight) !=
+ (right.width <= mMaxWidth && right.height <= mMaxHeight)) {
+ return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT;
+ }
+
+ // If one is closer to the target aspect ratio, prefer it.
+ final float leftAspectRatio = left.width / (float) left.height;
+ final float rightAspectRatio = right.width / (float) right.height;
+ final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio);
+ final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio);
+ if (leftAspectRatioDiff != rightAspectRatioDiff) {
+ return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ?
+ PREFER_LEFT : PREFER_RIGHT;
+ }
+
+ // At this point they have the same aspect ratio diff and are either both bigger
+ // than the max size or both smaller than the max size, so prefer the one closest
+ // to target size
+ final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels);
+ final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels);
+ return leftDiff - rightDiff;
+ }
+ }
+
+ @Override // From FocusOverlayManager.Listener
+ public void autoFocus() {
+ if (mCamera == null) {
+ return;
+ }
+
+ try {
+ mCamera.autoFocus(new Camera.AutoFocusCallback() {
+ @Override
+ public void onAutoFocus(final boolean success, final Camera camera) {
+ mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */);
+ }
+ });
+ } catch (final RuntimeException e) {
+ LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e);
+ // If autofocus fails, the camera should have called the callback with success=false,
+ // but some throw an exception here
+ mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/);
+ }
+ }
+
+ @Override // From FocusOverlayManager.Listener
+ public void cancelAutoFocus() {
+ if (mCamera == null) {
+ return;
+ }
+ try {
+ mCamera.cancelAutoFocus();
+ } catch (final RuntimeException e) {
+ // Ignore
+ LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e);
+ }
+ }
+
+ @Override // From FocusOverlayManager.Listener
+ public boolean capture() {
+ return false;
+ }
+
+ @Override // From FocusOverlayManager.Listener
+ public void setFocusParameters() {
+ if (mCamera == null) {
+ return;
+ }
+ try {
+ final Camera.Parameters parameters = mCamera.getParameters();
+ parameters.setFocusMode(mFocusOverlayManager.getFocusMode());
+ if (parameters.getMaxNumFocusAreas() > 0) {
+ // Don't set focus areas (even to null) if focus areas aren't supported, camera may
+ // crash
+ parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas());
+ }
+ parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas());
+ mCamera.setParameters(parameters);
+ } catch (final RuntimeException e) {
+ // This occurs when the device is out of space or when the camera is locked
+ LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters");
+ }
+ }
+
+ private void logCameraSize(final String prefix, final Camera.Size size) {
+ // Log the camera size and aspect ratio for help when examining bug reports for camera
+ // failures
+ LogUtil.i(TAG, prefix + size.width + "x" + size.height +
+ " (" + (size.width / (float) size.height) + ")");
+ }
+
+
+ private Integer mSavedOrientation = null;
+
+ private void lockOrientation() {
+ // when we start recording, lock our orientation
+ final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
+ final WindowManager windowManager =
+ (WindowManager) a.getSystemService(Context.WINDOW_SERVICE);
+ final int rotation = windowManager.getDefaultDisplay().getRotation();
+
+ mSavedOrientation = a.getRequestedOrientation();
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ a.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ break;
+ case Surface.ROTATION_90:
+ a.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ break;
+ case Surface.ROTATION_180:
+ a.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ break;
+ case Surface.ROTATION_270:
+ a.setRequestedOrientation(
+ ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ break;
+ }
+
+ }
+
+ private void restoreRequestedOrientation() {
+ if (mSavedOrientation != null) {
+ final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
+ if (a != null) {
+ a.setRequestedOrientation(mSavedOrientation);
+ }
+ mSavedOrientation = null;
+ }
+ }
+
+ static boolean hasCameraPermission() {
+ return OsUtil.hasPermission(Manifest.permission.CAMERA);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
new file mode 100644
index 0000000..2c7a7f2
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
@@ -0,0 +1,481 @@
+/*
+ * 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.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.widget.Chronometer;
+import android.widget.ImageButton;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
+import com.android.messaging.ui.mediapicker.CameraManager.MediaCallback;
+import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Chooser which allows the user to take pictures or video without leaving the current app/activity
+ */
+class CameraMediaChooser extends MediaChooser implements
+ CameraManager.CameraManagerListener {
+ private CameraPreview.CameraPreviewHost mCameraPreviewHost;
+ private ImageButton mFullScreenButton;
+ private ImageButton mSwapCameraButton;
+ private ImageButton mSwapModeButton;
+ private ImageButton mCaptureButton;
+ private ImageButton mCancelVideoButton;
+ private Chronometer mVideoCounter;
+ private boolean mVideoCancelled;
+ private int mErrorToast;
+ private View mEnabledView;
+ private View mMissingPermissionView;
+
+ CameraMediaChooser(final MediaPicker mediaPicker) {
+ super(mediaPicker);
+ }
+
+ @Override
+ public int getSupportedMediaTypes() {
+ if (CameraManager.get().hasAnyCamera()) {
+ return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
+ } else {
+ return MediaPicker.MEDIA_TYPE_NONE;
+ }
+ }
+
+ @Override
+ public View destroyView() {
+ CameraManager.get().closeCamera();
+ CameraManager.get().setListener(null);
+ CameraManager.get().setSubscriptionDataProvider(null);
+ return super.destroyView();
+ }
+
+ @Override
+ protected View createView(final ViewGroup container) {
+ CameraManager.get().setListener(this);
+ CameraManager.get().setSubscriptionDataProvider(this);
+ CameraManager.get().setVideoMode(false);
+ final LayoutInflater inflater = getLayoutInflater();
+ final CameraMediaChooserView view = (CameraMediaChooserView) inflater.inflate(
+ R.layout.mediapicker_camera_chooser,
+ container /* root */,
+ false /* attachToRoot */);
+ mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById(
+ R.id.camera_preview);
+ mCameraPreviewHost.getView().setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(final View view, final MotionEvent motionEvent) {
+ if (CameraManager.get().isVideoMode()) {
+ // Prevent the swipe down in video mode because video is always captured in
+ // full screen
+ return true;
+ }
+
+ return false;
+ }
+ });
+
+ final View shutterVisual = view.findViewById(R.id.camera_shutter_visual);
+
+ mFullScreenButton = (ImageButton) view.findViewById(R.id.camera_fullScreen_button);
+ mFullScreenButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ mMediaPicker.setFullScreen(true);
+ }
+ });
+
+ mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button);
+ mSwapCameraButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ CameraManager.get().swapCamera();
+ }
+ });
+
+ mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button);
+ mCaptureButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() /
+ (float) mCameraPreviewHost.getView().getHeight(), 1);
+
+ if (CameraManager.get().isRecording()) {
+ CameraManager.get().stopVideo();
+ } else {
+ final CameraManager.MediaCallback callback = new CameraManager.MediaCallback() {
+ @Override
+ public void onMediaReady(
+ final Uri uriToVideo, final String contentType,
+ final int width, final int height) {
+ mVideoCounter.stop();
+ if (mVideoCancelled || uriToVideo == null) {
+ mVideoCancelled = false;
+ } else {
+ final Rect startRect = new Rect();
+ // It's possible to throw out the chooser while taking the
+ // picture/video. In that case, still use the attachment, just
+ // skip the startRect
+ if (mView != null) {
+ mView.getGlobalVisibleRect(startRect);
+ }
+ mMediaPicker.dispatchItemsSelected(
+ new MediaPickerMessagePartData(startRect, contentType,
+ uriToVideo, width, height),
+ true /* dismissMediaPicker */);
+ }
+ updateViewState();
+ }
+
+ @Override
+ public void onMediaFailed(final Exception exception) {
+ UiUtils.showToastAtBottom(R.string.camera_media_failure);
+ updateViewState();
+ }
+
+ @Override
+ public void onMediaInfo(final int what) {
+ if (what == MediaCallback.MEDIA_NO_DATA) {
+ UiUtils.showToastAtBottom(R.string.camera_media_failure);
+ }
+ updateViewState();
+ }
+ };
+ if (CameraManager.get().isVideoMode()) {
+ CameraManager.get().startVideo(callback);
+ mVideoCounter.setBase(SystemClock.elapsedRealtime());
+ mVideoCounter.start();
+ updateViewState();
+ } else {
+ showShutterEffect(shutterVisual);
+ CameraManager.get().takePicture(heightPercent, callback);
+ updateViewState();
+ }
+ }
+ }
+ });
+
+ mSwapModeButton = (ImageButton) view.findViewById(R.id.camera_swap_mode_button);
+ mSwapModeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode();
+ if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) {
+ requestRecordAudioPermission();
+ } else {
+ onSwapMode();
+ }
+ }
+ });
+
+ mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button);
+ mCancelVideoButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ mVideoCancelled = true;
+ CameraManager.get().stopVideo();
+ mMediaPicker.dismiss(true);
+ }
+ });
+
+ mVideoCounter = (Chronometer) view.findViewById(R.id.camera_video_counter);
+
+ CameraManager.get().setRenderOverlay((RenderOverlay) view.findViewById(R.id.focus_visual));
+
+ mEnabledView = view.findViewById(R.id.mediapicker_enabled);
+ mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
+
+ // Must set mView before calling updateViewState because it operates on mView
+ mView = view;
+ updateViewState();
+ updateForPermissionState(CameraManager.hasCameraPermission());
+ return view;
+ }
+
+ @Override
+ public int getIconResource() {
+ return R.drawable.ic_camera_light;
+ }
+
+ @Override
+ public int getIconDescriptionResource() {
+ return R.string.mediapicker_cameraChooserDescription;
+ }
+
+ /**
+ * Updates the view when entering or leaving full-screen camera mode
+ * @param fullScreen
+ */
+ @Override
+ void onFullScreenChanged(final boolean fullScreen) {
+ super.onFullScreenChanged(fullScreen);
+ if (!fullScreen && CameraManager.get().isVideoMode()) {
+ CameraManager.get().setVideoMode(false);
+ }
+ updateViewState();
+ }
+
+ /**
+ * Initializes the control to a default state when it is opened / closed
+ * @param open True if the control is opened
+ */
+ @Override
+ void onOpenedChanged(final boolean open) {
+ super.onOpenedChanged(open);
+ updateViewState();
+ }
+
+ @Override
+ protected void setSelected(final boolean selected) {
+ super.setSelected(selected);
+ if (selected) {
+ if (CameraManager.hasCameraPermission()) {
+ // If an error occurred before the chooser was selected, show it now
+ showErrorToastIfNeeded();
+ } else {
+ requestCameraPermission();
+ }
+ }
+ }
+
+ private void requestCameraPermission() {
+ mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA },
+ MediaPicker.CAMERA_PERMISSION_REQUEST_CODE);
+ }
+
+ private void requestRecordAudioPermission() {
+ mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
+ MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
+ }
+
+ @Override
+ protected void onRequestPermissionsResult(
+ final int requestCode, final String permissions[], final int[] grantResults) {
+ if (requestCode == MediaPicker.CAMERA_PERMISSION_REQUEST_CODE) {
+ final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ updateForPermissionState(permissionGranted);
+ if (permissionGranted) {
+ mCameraPreviewHost.onCameraPermissionGranted();
+ }
+ } else if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
+ Assert.isFalse(CameraManager.get().isVideoMode());
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Switch to video mode
+ onSwapMode();
+ } else {
+ // Stay in still-photo mode
+ }
+ }
+ }
+
+ private void updateForPermissionState(final boolean granted) {
+ // onRequestPermissionsResult can sometimes get called before createView().
+ if (mEnabledView == null) {
+ return;
+ }
+
+ mEnabledView.setVisibility(granted ? View.VISIBLE : View.GONE);
+ mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
+ }
+
+ @Override
+ public boolean canSwipeDown() {
+ if (CameraManager.get().isVideoMode()) {
+ return true;
+ }
+ return super.canSwipeDown();
+ }
+
+ /**
+ * Handles an error from the camera manager by showing the appropriate error message to the user
+ * @param errorCode One of the CameraManager.ERROR_* constants
+ * @param e The exception which caused the error, if any
+ */
+ @Override
+ public void onCameraError(final int errorCode, final Exception e) {
+ switch (errorCode) {
+ case CameraManager.ERROR_OPENING_CAMERA:
+ case CameraManager.ERROR_SHOWING_PREVIEW:
+ mErrorToast = R.string.camera_error_opening;
+ break;
+ case CameraManager.ERROR_INITIALIZING_VIDEO:
+ mErrorToast = R.string.camera_error_video_init_fail;
+ updateViewState();
+ break;
+ case CameraManager.ERROR_STORAGE_FAILURE:
+ mErrorToast = R.string.camera_error_storage_fail;
+ updateViewState();
+ break;
+ case CameraManager.ERROR_TAKING_PICTURE:
+ mErrorToast = R.string.camera_error_failure_taking_picture;
+ break;
+ default:
+ mErrorToast = R.string.camera_error_unknown;
+ LogUtil.w(LogUtil.BUGLE_TAG, "Unknown camera error:" + errorCode);
+ break;
+ }
+ showErrorToastIfNeeded();
+ }
+
+ private void showErrorToastIfNeeded() {
+ if (mErrorToast != 0 && mSelected) {
+ UiUtils.showToastAtBottom(mErrorToast);
+ mErrorToast = 0;
+ }
+ }
+
+ @Override
+ public void onCameraChanged() {
+ updateViewState();
+ }
+
+ private void onSwapMode() {
+ CameraManager.get().setVideoMode(!CameraManager.get().isVideoMode());
+ if (CameraManager.get().isVideoMode()) {
+ mMediaPicker.setFullScreen(true);
+
+ // For now we start recording immediately
+ mCaptureButton.performClick();
+ }
+ updateViewState();
+ }
+
+ private void showShutterEffect(final View shutterVisual) {
+ final float maxAlpha = getContext().getResources().getFraction(
+ R.fraction.camera_shutter_max_alpha, 1 /* base */, 1 /* pBase */);
+
+ // Divide by 2 so each half of the animation adds up to the full duration
+ final int animationDuration = getContext().getResources().getInteger(
+ R.integer.camera_shutter_duration) / 2;
+
+ final AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
+ final Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
+ alphaInAnimation.setDuration(animationDuration);
+ animation.addAnimation(alphaInAnimation);
+
+ final Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
+ alphaOutAnimation.setStartOffset(animationDuration);
+ alphaOutAnimation.setDuration(animationDuration);
+ animation.addAnimation(alphaOutAnimation);
+
+ animation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(final Animation animation) {
+ shutterVisual.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(final Animation animation) {
+ shutterVisual.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationRepeat(final Animation animation) {
+ }
+ });
+ shutterVisual.startAnimation(animation);
+ }
+
+ /** Updates the state of the buttons and overlays based on the current state of the view */
+ private void updateViewState() {
+ if (mView == null) {
+ return;
+ }
+
+ final Context context = getContext();
+ if (context == null) {
+ // Context is null if the fragment was already removed from the activity
+ return;
+ }
+ final boolean fullScreen = mMediaPicker.isFullScreen();
+ final boolean videoMode = CameraManager.get().isVideoMode();
+ final boolean isRecording = CameraManager.get().isRecording();
+ final boolean isCameraAvailable = isCameraAvailable();
+ final Camera.CameraInfo cameraInfo = CameraManager.get().getCameraInfo();
+ final boolean frontCamera = cameraInfo != null && cameraInfo.facing ==
+ Camera.CameraInfo.CAMERA_FACING_FRONT;
+
+ mView.setSystemUiVisibility(
+ fullScreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE :
+ View.SYSTEM_UI_FLAG_VISIBLE);
+
+ mFullScreenButton.setVisibility(!fullScreen ? View.VISIBLE : View.GONE);
+ mFullScreenButton.setEnabled(isCameraAvailable);
+ mSwapCameraButton.setVisibility(
+ fullScreen && !isRecording && CameraManager.get().hasFrontAndBackCamera() ?
+ View.VISIBLE : View.GONE);
+ mSwapCameraButton.setImageResource(frontCamera ?
+ R.drawable.ic_camera_front_light :
+ R.drawable.ic_camera_rear_light);
+ mSwapCameraButton.setEnabled(isCameraAvailable);
+
+ mCancelVideoButton.setVisibility(isRecording ? View.VISIBLE : View.GONE);
+ mVideoCounter.setVisibility(isRecording ? View.VISIBLE : View.GONE);
+
+ mSwapModeButton.setImageResource(videoMode ?
+ R.drawable.ic_mp_camera_small_light :
+ R.drawable.ic_mp_video_small_light);
+ mSwapModeButton.setContentDescription(context.getString(videoMode ?
+ R.string.camera_switch_to_still_mode : R.string.camera_switch_to_video_mode));
+ mSwapModeButton.setVisibility(isRecording ? View.GONE : View.VISIBLE);
+ mSwapModeButton.setEnabled(isCameraAvailable);
+
+ if (isRecording) {
+ mCaptureButton.setImageResource(R.drawable.ic_mp_capture_stop_large_light);
+ mCaptureButton.setContentDescription(context.getString(
+ R.string.camera_stop_recording));
+ } else if (videoMode) {
+ mCaptureButton.setImageResource(R.drawable.ic_mp_video_large_light);
+ mCaptureButton.setContentDescription(context.getString(
+ R.string.camera_start_recording));
+ } else {
+ mCaptureButton.setImageResource(R.drawable.ic_checkmark_large_light);
+ mCaptureButton.setContentDescription(context.getString(
+ R.string.camera_take_picture));
+ }
+ mCaptureButton.setEnabled(isCameraAvailable);
+ }
+
+ @Override
+ int getActionBarTitleResId() {
+ return 0;
+ }
+
+ /**
+ * Returns if the camera is currently ready camera is loaded and not taking a picture.
+ * otherwise we should avoid taking another picture, swapping camera or recording video.
+ */
+ private boolean isCameraAvailable() {
+ return CameraManager.get().isCameraAvailable();
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
new file mode 100644
index 0000000..64c07b2
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
@@ -0,0 +1,102 @@
+/*
+ * 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.graphics.Canvas;
+import android.hardware.Camera;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import com.android.messaging.R;
+import com.android.messaging.ui.PersistentInstanceState;
+import com.android.messaging.util.ThreadUtil;
+
+public class CameraMediaChooserView extends FrameLayout implements PersistentInstanceState {
+ private static final String KEY_CAMERA_INDEX = "camera_index";
+
+ // True if we have at least queued an update to the view tree to support software rendering
+ // fallback
+ private boolean mIsSoftwareFallbackActive;
+
+ public CameraMediaChooserView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Bundle bundle = new Bundle();
+ bundle.putInt(KEY_CAMERA_INDEX, CameraManager.get().getCameraIndex());
+ return bundle;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Parcelable state) {
+ if (!(state instanceof Bundle)) {
+ return;
+ }
+
+ final Bundle bundle = (Bundle) state;
+ CameraManager.get().selectCameraByIndex(bundle.getInt(KEY_CAMERA_INDEX));
+ }
+
+ @Override
+ public Parcelable saveState() {
+ return onSaveInstanceState();
+ }
+
+ @Override
+ public void restoreState(final Parcelable restoredState) {
+ onRestoreInstanceState(restoredState);
+ }
+
+ @Override
+ public void resetState() {
+ CameraManager.get().selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ super.onDraw(canvas);
+ // If the canvas isn't hardware accelerated, we have to replace the HardwareCameraPreview
+ // with a SoftwareCameraPreview which supports software rendering
+ if (!canvas.isHardwareAccelerated() && !mIsSoftwareFallbackActive) {
+ mIsSoftwareFallbackActive = true;
+ // Post modifying the tree since we can't modify the view tree during a draw pass
+ ThreadUtil.getMainThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ final HardwareCameraPreview cameraPreview =
+ (HardwareCameraPreview) findViewById(R.id.camera_preview);
+ if (cameraPreview == null) {
+ return;
+ }
+ final ViewGroup parent = ((ViewGroup) cameraPreview.getParent());
+ final int index = parent.indexOfChild(cameraPreview);
+ final SoftwareCameraPreview softwareCameraPreview =
+ new SoftwareCameraPreview(getContext());
+ // Be sure to remove the hardware view before adding the software view to
+ // prevent having 2 camera previews active at the same time
+ parent.removeView(cameraPreview);
+ parent.addView(softwareCameraPreview, index);
+ }
+ });
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraPreview.java b/src/com/android/messaging/ui/mediapicker/CameraPreview.java
new file mode 100644
index 0000000..ecac978
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/CameraPreview.java
@@ -0,0 +1,152 @@
+/*
+ * 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.content.res.Configuration;
+import android.hardware.Camera;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import com.android.messaging.util.Assert;
+
+import java.io.IOException;
+
+/**
+ * Contains shared code for SoftwareCameraPreview and HardwareCameraPreview, cannot use inheritance
+ * because those classes must inherit from separate Views, so those classes delegate calls to this
+ * helper class. Specifics for each implementation are in CameraPreviewHost
+ */
+public class CameraPreview {
+ public interface CameraPreviewHost {
+ View getView();
+ boolean isValid();
+ void startPreview(final Camera camera) throws IOException;
+ void onCameraPermissionGranted();
+
+ }
+
+ private int mCameraWidth = -1;
+ private int mCameraHeight = -1;
+
+ private final CameraPreviewHost mHost;
+
+ public CameraPreview(final CameraPreviewHost host) {
+ Assert.notNull(host);
+ Assert.notNull(host.getView());
+ mHost = host;
+ }
+
+ public void setSize(final Camera.Size size, final int orientation) {
+ switch (orientation) {
+ case 0:
+ case 180:
+ mCameraWidth = size.width;
+ mCameraHeight = size.height;
+ break;
+ case 90:
+ case 270:
+ default:
+ mCameraWidth = size.height;
+ mCameraHeight = size.width;
+ }
+ mHost.getView().requestLayout();
+ }
+
+ public int getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
+ if (mCameraHeight >= 0) {
+ final int width = View.MeasureSpec.getSize(widthMeasureSpec);
+ return MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ } else {
+ return widthMeasureSpec;
+ }
+ }
+
+ public int getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
+ if (mCameraHeight >= 0) {
+ final int orientation = getContext().getResources().getConfiguration().orientation;
+ final int width = View.MeasureSpec.getSize(widthMeasureSpec);
+ final float aspectRatio = (float) mCameraWidth / (float) mCameraHeight;
+ int height;
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ height = (int) (width * aspectRatio);
+ } else {
+ height = (int) (width / aspectRatio);
+ }
+ return View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+ } else {
+ return heightMeasureSpec;
+ }
+ }
+
+ public void onVisibilityChanged(final int visibility) {
+ if (CameraManager.hasCameraPermission()) {
+ if (visibility == View.VISIBLE) {
+ CameraManager.get().openCamera();
+ } else {
+ CameraManager.get().closeCamera();
+ }
+ }
+ }
+
+ public Context getContext() {
+ return mHost.getView().getContext();
+ }
+
+ public void setOnTouchListener(final View.OnTouchListener listener) {
+ mHost.getView().setOnTouchListener(listener);
+ }
+
+ public int getHeight() {
+ return mHost.getView().getHeight();
+ }
+
+ public void onAttachedToWindow() {
+ if (CameraManager.hasCameraPermission()) {
+ CameraManager.get().openCamera();
+ }
+ }
+
+ public void onDetachedFromWindow() {
+ CameraManager.get().closeCamera();
+ }
+
+ public void onRestoreInstanceState() {
+ if (CameraManager.hasCameraPermission()) {
+ CameraManager.get().openCamera();
+ }
+ }
+
+ public void onCameraPermissionGranted() {
+ CameraManager.get().openCamera();
+ }
+
+ /**
+ * @return True if the view is valid and prepared for the camera to start showing the preview
+ */
+ public boolean isValid() {
+ return mHost.isValid();
+ }
+
+ /**
+ * Starts the camera preview on the current surface. Abstracts out the differences in API
+ * from the CameraManager
+ * @throws IOException Which is caught by the CameraManager to display an error
+ */
+ public void startPreview(final Camera camera) throws IOException {
+ mHost.startPreview(camera);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
new file mode 100644
index 0000000..2c36752
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
@@ -0,0 +1,128 @@
+/*
+ * 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.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.ImageUtils;
+import com.android.messaging.util.SafeAsyncTask;
+
+/**
+ * Wraps around the functionalities to allow the user to pick images from the document
+ * picker. Instances of this class must be tied to a Fragment which is able to delegate activity
+ * result callbacks.
+ */
+public class DocumentImagePicker {
+
+ /**
+ * An interface for a listener that listens for when a document has been picked.
+ */
+ public interface SelectionListener {
+ /**
+ * Called when an document is selected from picker. At this point, the file hasn't been
+ * actually loaded and staged in the temp directory, so we are passing in a pending
+ * MessagePartData, which the consumer should use to display a placeholder image.
+ * @param pendingItem a temporary attachment data for showing the placeholder state.
+ */
+ void onDocumentSelected(PendingAttachmentData pendingItem);
+ }
+
+ // The owning fragment.
+ private final Fragment mFragment;
+
+ // The listener on the picker events.
+ private final SelectionListener mListener;
+
+ private static final String EXTRA_PHOTO_URL = "photo_url";
+
+ /**
+ * Creates a new instance of DocumentImagePicker.
+ * @param activity The activity that owns the picker, or the activity that hosts the owning
+ * fragment.
+ */
+ public DocumentImagePicker(final Fragment fragment,
+ final SelectionListener listener) {
+ mFragment = fragment;
+ mListener = listener;
+ }
+
+ /**
+ * Intent out to open an image/video from document picker.
+ */
+ public void launchPicker() {
+ UIIntents.get().launchDocumentImagePicker(mFragment);
+ }
+
+ /**
+ * Must be called from the fragment/activity's onActivityResult().
+ */
+ public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ if (requestCode == UIIntents.REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER &&
+ resultCode == Activity.RESULT_OK) {
+ // Sometimes called after media item has been picked from the document picker.
+ String url = data.getStringExtra(EXTRA_PHOTO_URL);
+ if (url == null) {
+ // we're using the builtin photo picker which supplies the return
+ // url as it's "data"
+ url = data.getDataString();
+ if (url == null) {
+ final Bundle extras = data.getExtras();
+ if (extras != null) {
+ final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
+ if (uri != null) {
+ url = uri.toString();
+ }
+ }
+ }
+ }
+
+ // Guard against null uri cases for when the activity returns a null/invalid intent.
+ if (url != null) {
+ final Uri uri = Uri.parse(url);
+ prepareDocumentForAttachment(uri);
+ }
+ }
+ }
+
+ private void prepareDocumentForAttachment(final Uri documentUri) {
+ // Notify our listener with a PendingAttachmentData containing the metadata.
+ // Asynchronously get the content type for the picked image since
+ // ImageUtils.getContentType() potentially involves I/O and can be expensive.
+ new SafeAsyncTask<Void, Void, String>() {
+ @Override
+ protected String doInBackgroundTimed(final Void... params) {
+ return ImageUtils.getContentType(
+ Factory.get().getApplicationContext().getContentResolver(), documentUri);
+ }
+
+ @Override
+ protected void onPostExecute(final String contentType) {
+ // Ask the listener to create a temporary placeholder item to show the progress.
+ final PendingAttachmentData pendingItem =
+ PendingAttachmentData.createPendingAttachmentData(contentType,
+ documentUri);
+ mListener.onDocumentSelected(pendingItem);
+ }
+ }.executeOnThreadPool();
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java b/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java
new file mode 100644
index 0000000..fda3b19
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.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.GalleryGridItemView.HostInterface;
+import com.android.messaging.util.Assert;
+
+/**
+ * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView.
+ */
+public class GalleryGridAdapter extends CursorAdapter {
+ private GalleryGridItemView.HostInterface mGgivHostInterface;
+
+ public GalleryGridAdapter(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 GalleryGridItemView);
+ final GalleryGridItemView galleryImageView = (GalleryGridItemView) view;
+ galleryImageView.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.gallery_grid_item_view, parent, false);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
new file mode 100644
index 0000000..3d71fe6
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
@@ -0,0 +1,159 @@
+/*
+ * 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.database.Cursor;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+import android.widget.ImageView.ScaleType;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.ui.AsyncImageView;
+import com.android.messaging.ui.ConversationDrawables;
+import com.google.common.annotations.VisibleForTesting;
+
+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 {
+ /**
+ * Implemented by the owner of this GalleryGridItemView instance to communicate on media
+ * picking and selection events.
+ */
+ public interface HostInterface {
+ void onItemClicked(View view, GalleryGridItemData data, boolean longClick);
+ boolean isItemSelected(GalleryGridItemData data);
+ boolean isMultiSelectEnabled();
+ }
+
+ @VisibleForTesting
+ GalleryGridItemData mData;
+ private AsyncImageView mImageView;
+ private CheckBox mCheckBox;
+ private HostInterface mHostInterface;
+ private final OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
+ }
+ };
+
+ public GalleryGridItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = DataModel.get().createGalleryGridItemData();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = (AsyncImageView) findViewById(R.id.image);
+ mCheckBox = (CheckBox) findViewById(R.id.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);
+ addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ // Enlarge the clickable region for the checkbox to fill the entire view.
+ final Rect region = new Rect(0, 0, getWidth(), getHeight());
+ setTouchDelegate(new TouchDelegate(region, mCheckBox) {
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ setPressed(true);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ setPressed(false);
+ break;
+ }
+ return super.onTouchEvent(event);
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ // The grid view auto-fit the columns, so we want to let the height match the width
+ // to make the image square.
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+
+ public void bind(final Cursor cursor, final HostInterface hostInterface) {
+ final int desiredSize = getResources()
+ .getDimensionPixelSize(R.dimen.gallery_image_cell_size);
+ mData.bind(cursor, desiredSize, desiredSize);
+ mHostInterface = hostInterface;
+ updateViewState();
+ }
+
+ private void updateViewState() {
+ updateImageView();
+ if (mHostInterface.isMultiSelectEnabled() && !mData.isDocumentPickerItem()) {
+ mCheckBox.setVisibility(VISIBLE);
+ mCheckBox.setClickable(true);
+ mCheckBox.setChecked(mHostInterface.isItemSelected(mData));
+ } else {
+ mCheckBox.setVisibility(GONE);
+ mCheckBox.setClickable(false);
+ }
+ }
+
+ private void updateImageView() {
+ if (mData.isDocumentPickerItem()) {
+ 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 {
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
new file mode 100644
index 0000000..a5a7dad
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
@@ -0,0 +1,315 @@
+/*
+ * 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.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.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.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 galley images from external storage in a GridView with multi-select
+ * capabilities, and with the option to intent out to a standalone image picker.
+ */
+public class GalleryGridView extends MediaPickerGridView implements
+ GalleryGridItemView.HostInterface,
+ PersistentInstanceState,
+ DraftMessageDataListener {
+ /**
+ * Implemented by the owner of this GalleryGridView instance to communicate on image
+ * picking and multi-image selection events.
+ */
+ public interface GalleryGridViewListener {
+ void onDocumentPickerItemClicked();
+ void onItemSelected(MessagePartData item);
+ void onItemUnselected(MessagePartData item);
+ void onConfirmSelection();
+ void onUpdate();
+ }
+
+ private GalleryGridViewListener mListener;
+
+ // TODO: Consider putting this into the data model object if we add more states.
+ private final ArrayMap<Uri, MessagePartData> mSelectedImages;
+ private boolean mIsMultiSelectMode = false;
+ private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+
+ public GalleryGridView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mSelectedImages = new ArrayMap<Uri, MessagePartData>();
+ }
+
+ public void setHostInterface(final GalleryGridViewListener 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 GalleryGridItemData data,
+ final boolean longClick) {
+ if (data.isDocumentPickerItem()) {
+ mListener.onDocumentPickerItemClicked();
+ } else 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 GalleryGridItemData data) {
+ return mSelectedImages.containsKey(data.getImageUri());
+ }
+
+ int getSelectionCount() {
+ return mSelectedImages.size();
+ }
+
+ @Override
+ public boolean isMultiSelectEnabled() {
+ return mIsMultiSelectMode;
+ }
+
+ private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) {
+ Assert.isTrue(isMultiSelectEnabled());
+ if (isItemSelected(data)) {
+ final MessagePartData item = mSelectedImages.remove(data.getImageUri());
+ mListener.onItemUnselected(item);
+ if (mSelectedImages.size() == 0) {
+ // No image is selected any more, turn off multi-select mode.
+ setMultiSelectEnabled(false);
+ }
+ } else {
+ final MessagePartData item = data.constructMessagePartData(startRect);
+ mSelectedImages.put(data.getImageUri(), 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 mSelectedImages.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 =
+ mSelectedImages.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.selectedImages = mSelectedImages.values()
+ .toArray(new MessagePartData[mSelectedImages.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;
+ mSelectedImages.clear();
+ for (int i = 0; i < savedState.selectedImages.length; i++) {
+ final MessagePartData selectedImage = savedState.selectedImages[i];
+ mSelectedImages.put(selectedImage.getContentUri(), selectedImage);
+ }
+ }
+
+ @Override // PersistentInstanceState
+ public void resetState() {
+ mSelectedImages.clear();
+ mIsMultiSelectMode = false;
+ invalidateViews();
+ }
+
+ public static class SavedState extends BaseSavedState {
+ boolean isMultiSelectMode;
+ MessagePartData[] selectedImages;
+
+ 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();
+ selectedImages = new MessagePartData[partCount];
+ for (int i = 0; i < partCount; i++) {
+ selectedImages[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(selectedImages.length);
+ for (final MessagePartData image : selectedImages) {
+ out.writeParcelable(image, flags);
+ }
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.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/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
new file mode 100644
index 0000000..9422386
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
@@ -0,0 +1,230 @@
+/*
+ * 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.Manifest;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
+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.GalleryGridItemData;
+import com.android.messaging.datamodel.data.MediaPickerData;
+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;
+
+/**
+ * Chooser which allows the user to select one or more existing images or videos
+ */
+class GalleryMediaChooser extends MediaChooser implements
+ GalleryGridView.GalleryGridViewListener, MediaPickerDataListener {
+ private final GalleryGridAdapter mAdapter;
+ private GalleryGridView mGalleryGridView;
+ private View mMissingPermissionView;
+
+ GalleryMediaChooser(final MediaPicker mediaPicker) {
+ super(mediaPicker);
+ mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null);
+ }
+
+ @Override
+ public int getSupportedMediaTypes() {
+ return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
+ }
+
+ @Override
+ public View destroyView() {
+ mGalleryGridView.setAdapter(null);
+ mAdapter.setHostInterface(null);
+ // The loader is started only if startMediaPickerDataLoader() is called
+ if (OsUtil.hasStoragePermission()) {
+ mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_IMAGE_LOADER);
+ }
+ return super.destroyView();
+ }
+
+ @Override
+ public int getIconResource() {
+ return R.drawable.ic_image_light;
+ }
+
+ @Override
+ public int getIconDescriptionResource() {
+ return R.string.mediapicker_galleryChooserDescription;
+ }
+
+ @Override
+ public boolean canSwipeDown() {
+ return mGalleryGridView.canSwipeDown();
+ }
+
+ @Override
+ public void onItemSelected(final MessagePartData item) {
+ mMediaPicker.dispatchItemsSelected(item, !mGalleryGridView.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(mGalleryGridView.isMultiSelectEnabled());
+ mMediaPicker.dispatchConfirmItemSelection();
+ }
+
+ @Override
+ public void onUpdate() {
+ mMediaPicker.invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
+ if (mView != null) {
+ mGalleryGridView.onCreateOptionsMenu(inflater, menu);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ return (mView != null) ? mGalleryGridView.onOptionsItemSelected(item) : false;
+ }
+
+ @Override
+ protected View createView(final ViewGroup container) {
+ final LayoutInflater inflater = getLayoutInflater();
+ final View view = inflater.inflate(
+ R.layout.mediapicker_image_chooser,
+ container /* root */,
+ false /* attachToRoot */);
+
+ mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view);
+ mAdapter.setHostInterface(mGalleryGridView);
+ mGalleryGridView.setAdapter(mAdapter);
+ mGalleryGridView.setHostInterface(this);
+ mGalleryGridView.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_gallery_title;
+ }
+
+ @Override
+ public void onDocumentPickerItemClicked() {
+ mMediaPicker.launchDocumentPicker();
+ }
+
+ @Override
+ void updateActionBar(final ActionBar actionBar) {
+ super.updateActionBar(actionBar);
+ if (mGalleryGridView == null) {
+ return;
+ }
+ final int selectionCount = mGalleryGridView.getSelectionCount();
+ if (selectionCount > 0 && mGalleryGridView.isMultiSelectEnabled()) {
+ actionBar.setTitle(getContext().getResources().getString(
+ R.string.mediapicker_gallery_title_selection,
+ selectionCount));
+ }
+ }
+
+ @Override
+ public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data,
+ final int loaderId) {
+ mBindingRef.ensureBound(mediaPickerData);
+ Assert.equals(MediaPickerData.GALLERY_IMAGE_LOADER, loaderId);
+ Cursor rawCursor = null;
+ 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 =
+ new MatrixCursor(GalleryGridItemData.SPECIAL_ITEM_COLUMNS);
+ specialItemsCursor.addRow(new Object[] { GalleryGridItemData.ID_DOCUMENT_PICKER_ITEM });
+ final MergeCursor cursor =
+ new MergeCursor(new Cursor[] { specialItemsCursor, rawCursor });
+ mAdapter.swapCursor(cursor);
+ }
+
+ @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();
+ }
+ }
+
+ @Override
+ protected void setSelected(final boolean selected) {
+ super.setSelected(selected);
+ if (selected && !OsUtil.hasStoragePermission()) {
+ mMediaPicker.requestPermissions(
+ new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
+ MediaPicker.GALLERY_PERMISSION_REQUEST_CODE);
+ }
+ }
+
+ private void startMediaPickerDataLoader() {
+ mBindingRef.getData().startLoader(MediaPickerData.GALLERY_IMAGE_LOADER, mBindingRef, null,
+ this);
+ }
+
+ @Override
+ protected void onRequestPermissionsResult(
+ final int requestCode, final String permissions[], final int[] grantResults) {
+ if (requestCode == MediaPicker.GALLERY_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 (mGalleryGridView == null) {
+ return;
+ }
+
+ mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE);
+ mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
new file mode 100644
index 0000000..45d9579
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
@@ -0,0 +1,118 @@
+/*
+ * 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.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.TextureView;
+import android.view.View;
+
+import java.io.IOException;
+
+/**
+ * A hardware accelerated preview texture for the camera. This is the preferred CameraPreview
+ * because it animates smoother. When hardware acceleration isn't available, SoftwareCameraPreview
+ * is used.
+ *
+ * There is a significant amount of duplication between HardwareCameraPreview and
+ * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
+ * implementations of the shared methods are delegated to CameraPreview
+ */
+public class HardwareCameraPreview extends TextureView implements CameraPreview.CameraPreviewHost {
+ private CameraPreview mPreview;
+
+ public HardwareCameraPreview(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mPreview = new CameraPreview(this);
+ setSurfaceTextureListener(new SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture, final int i, final int i2) {
+ CameraManager.get().setSurface(mPreview);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture, final int i, final int i2) {
+ CameraManager.get().setSurface(mPreview);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(final SurfaceTexture surfaceTexture) {
+ CameraManager.get().setSurface(null);
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(final SurfaceTexture surfaceTexture) {
+ CameraManager.get().setSurface(mPreview);
+ }
+ });
+ }
+
+ @Override
+ protected void onVisibilityChanged(final View changedView, final int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ mPreview.onVisibilityChanged(visibility);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPreview.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mPreview.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Parcelable state) {
+ super.onRestoreInstanceState(state);
+ mPreview.onRestoreInstanceState();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
+ heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public boolean isValid() {
+ return getSurfaceTexture() != null;
+ }
+
+ @Override
+ public void startPreview(final Camera camera) throws IOException {
+ camera.setPreviewTexture(getSurfaceTexture());
+ }
+
+ @Override
+ public void onCameraPermissionGranted() {
+ mPreview.onCameraPermissionGranted();
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java b/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
new file mode 100644
index 0000000..637eb84
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
@@ -0,0 +1,172 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.net.Uri;
+
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.exif.ExifInterface;
+import com.android.messaging.util.exif.ExifTag;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class ImagePersistTask extends SafeAsyncTask<Void, Void, Void> {
+ private static final String JPEG_EXTENSION = "jpg";
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private int mWidth;
+ private int mHeight;
+ private final float mHeightPercent;
+ private final byte[] mBytes;
+ private final Context mContext;
+ private final CameraManager.MediaCallback mCallback;
+ private Uri mOutputUri;
+ private Exception mException;
+
+ public ImagePersistTask(
+ final int width,
+ final int height,
+ final float heightPercent,
+ final byte[] bytes,
+ final Context context,
+ final CameraManager.MediaCallback callback) {
+ Assert.isTrue(heightPercent >= 0 && heightPercent <= 1);
+ Assert.notNull(bytes);
+ Assert.notNull(context);
+ Assert.notNull(callback);
+ mWidth = width;
+ mHeight = height;
+ mHeightPercent = heightPercent;
+ mBytes = bytes;
+ mContext = context;
+ mCallback = callback;
+ // TODO: We probably want to store directly in MMS storage to prevent this
+ // intermediate step
+ mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(JPEG_EXTENSION);
+ }
+
+ @Override
+ protected Void doInBackgroundTimed(final Void... params) {
+ OutputStream outputStream = null;
+ Bitmap bitmap = null;
+ Bitmap clippedBitmap = null;
+ try {
+ outputStream =
+ mContext.getContentResolver().openOutputStream(mOutputUri);
+ if (mHeightPercent != 1.0f) {
+ int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
+ final ExifInterface exifInterface = new ExifInterface();
+ try {
+ exifInterface.readExif(mBytes);
+ final Integer orientationValue =
+ exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (orientationValue != null) {
+ orientation = orientationValue.intValue();
+ }
+ // The thumbnail is of the full image, but we're cropping it, so just clear
+ // the thumbnail
+ exifInterface.setCompressedThumbnail((byte[]) null);
+ } catch (IOException e) {
+ // Couldn't get exif tags, not the end of the world
+ }
+ bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);
+ final int clippedWidth;
+ final int clippedHeight;
+ if (ExifInterface.getOrientationParams(orientation).invertDimensions) {
+ Assert.equals(mWidth, bitmap.getHeight());
+ Assert.equals(mHeight, bitmap.getWidth());
+ clippedWidth = (int) (mHeight * mHeightPercent);
+ clippedHeight = mWidth;
+ } else {
+ Assert.equals(mWidth, bitmap.getWidth());
+ Assert.equals(mHeight, bitmap.getHeight());
+ clippedWidth = mWidth;
+ clippedHeight = (int) (mHeight * mHeightPercent);
+ }
+ final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2;
+ final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2;
+ mWidth = clippedWidth;
+ mHeight = clippedHeight;
+ clippedBitmap = Bitmap.createBitmap(clippedWidth, clippedHeight,
+ Bitmap.Config.ARGB_8888);
+ clippedBitmap.setDensity(bitmap.getDensity());
+ final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap);
+ final Matrix matrix = new Matrix();
+ matrix.postTranslate(-offsetLeft, -offsetTop);
+ clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */);
+ clippedBitmapCanvas.save();
+ // EXIF data can take a big chunk of the file size and is often cleared by the
+ // carrier, only store orientation since that's critical
+ ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION);
+ exifInterface.clearExif();
+ exifInterface.setTag(orientationTag);
+ exifInterface.writeExif(clippedBitmap, outputStream);
+ } else {
+ outputStream.write(mBytes);
+ }
+ } catch (final IOException e) {
+ mOutputUri = null;
+ mException = e;
+ LogUtil.e(TAG, "Unable to persist image to temp storage " + e);
+ } finally {
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+
+ if (clippedBitmap != null) {
+ clippedBitmap.recycle();
+ }
+
+ if (outputStream != null) {
+ try {
+ outputStream.flush();
+ } catch (final IOException e) {
+ mOutputUri = null;
+ mException = e;
+ LogUtil.e(TAG, "error trying to flush and close the outputStream" + e);
+ } finally {
+ try {
+ outputStream.close();
+ } catch (final IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(final Void aVoid) {
+ if (mOutputUri != null) {
+ mCallback.onMediaReady(mOutputUri, ContentType.IMAGE_JPEG, mWidth, mHeight);
+ } else {
+ Assert.notNull(mException);
+ mCallback.onMediaFailed(mException);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java b/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java
new file mode 100644
index 0000000..06730a3
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java
@@ -0,0 +1,223 @@
+/*
+ * 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.media.MediaRecorder;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.UiUtils;
+
+import java.io.IOException;
+
+/**
+ * Wraps around the functionalities of MediaRecorder, performs routine setup for audio recording
+ * and updates the audio level to be displayed in UI.
+ *
+ * During the start and end of a recording session, we kick off a thread that polls for audio
+ * levels, and updates the thread-safe AudioLevelSource instance. Consumers may bind to the
+ * sound level by either polling from the level source, or register for a level change callback
+ * on the level source object. In Bugle, the UI element (SoundLevels) polls for the sound level
+ * on the UI thread by using animation ticks and invalidating itself.
+ *
+ * Aside from tracking sound levels, this also encapsulates the functionality to save the file
+ * to the scratch space. The saved file is returned by calling stopRecording().
+ */
+public class LevelTrackingMediaRecorder {
+ // We refresh sound level every 100ms during a recording session.
+ private static final int REFRESH_INTERVAL_MILLIS = 100;
+
+ // The native amplitude returned from MediaRecorder ranges from 0~32768 (unfortunately, this
+ // is not a constant that's defined anywhere, but the framework's Recorder app is using the
+ // same hard-coded number). Therefore, a constant is needed in order to make it 0~100.
+ private static final int MAX_AMPLITUDE_FACTOR = 32768 / 100;
+
+ // We want to limit the max audio file size by the max message size allowed by MmsConfig,
+ // plus multiplied by this fudge ratio to guarantee that we don't go over limit.
+ private static final float MAX_SIZE_RATIO = 0.8f;
+
+ // Default recorder settings for Bugle.
+ // TODO: Do we want these to be tweakable?
+ private static final int MEDIA_RECORDER_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
+ private static final int MEDIA_RECORDER_OUTPUT_FORMAT = MediaRecorder.OutputFormat.THREE_GPP;
+ private static final int MEDIA_RECORDER_AUDIO_ENCODER = MediaRecorder.AudioEncoder.AMR_NB;
+
+ private final AudioLevelSource mLevelSource;
+ private Thread mRefreshLevelThread;
+ private MediaRecorder mRecorder;
+ private Uri mOutputUri;
+ private ParcelFileDescriptor mOutputFD;
+
+ public LevelTrackingMediaRecorder() {
+ mLevelSource = new AudioLevelSource();
+ }
+
+ public AudioLevelSource getLevelSource() {
+ return mLevelSource;
+ }
+
+ /**
+ * @return if we are currently in a recording session.
+ */
+ public boolean isRecording() {
+ return mRecorder != null;
+ }
+
+ /**
+ * Start a new recording session.
+ * @return true if a session is successfully started; false if something went wrong or if
+ * we are already recording.
+ */
+ public boolean startRecording(final MediaRecorder.OnErrorListener errorListener,
+ final MediaRecorder.OnInfoListener infoListener, int maxSize) {
+ synchronized (LevelTrackingMediaRecorder.class) {
+ if (mRecorder == null) {
+ mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(
+ ContentType.THREE_GPP_EXTENSION);
+ mRecorder = new MediaRecorder();
+ try {
+ // The scratch space file is a Uri, however MediaRecorder
+ // API only accepts absolute FD's. Therefore, get the
+ // FileDescriptor from the content resolver to ensure the
+ // directory is created and get the file path to output the
+ // audio to.
+ maxSize *= MAX_SIZE_RATIO;
+ mOutputFD = Factory.get().getApplicationContext()
+ .getContentResolver().openFileDescriptor(mOutputUri, "w");
+ mRecorder.setAudioSource(MEDIA_RECORDER_AUDIO_SOURCE);
+ mRecorder.setOutputFormat(MEDIA_RECORDER_OUTPUT_FORMAT);
+ mRecorder.setAudioEncoder(MEDIA_RECORDER_AUDIO_ENCODER);
+ mRecorder.setOutputFile(mOutputFD.getFileDescriptor());
+ mRecorder.setMaxFileSize(maxSize);
+ mRecorder.setOnErrorListener(errorListener);
+ mRecorder.setOnInfoListener(infoListener);
+ mRecorder.prepare();
+ mRecorder.start();
+ startTrackingSoundLevel();
+ return true;
+ } catch (final Exception e) {
+ // There may be a device failure or I/O failure, record the error but
+ // don't fail.
+ LogUtil.e(LogUtil.BUGLE_TAG, "Something went wrong when starting " +
+ "media recorder. " + e);
+ UiUtils.showToastAtBottom(R.string.audio_recording_start_failed);
+ stopRecording();
+ }
+ } else {
+ Assert.fail("Trying to start a new recording session while already recording!");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Stop the current recording session.
+ * @return the Uri of the output file, or null if not currently recording.
+ */
+ public Uri stopRecording() {
+ synchronized (LevelTrackingMediaRecorder.class) {
+ if (mRecorder != null) {
+ try {
+ mRecorder.stop();
+ } catch (final RuntimeException ex) {
+ // This may happen when the recording is too short, so just drop the recording
+ // in this case.
+ LogUtil.w(LogUtil.BUGLE_TAG, "Something went wrong when stopping " +
+ "media recorder. " + ex);
+ if (mOutputUri != null) {
+ final Uri outputUri = mOutputUri;
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ Factory.get().getApplicationContext().getContentResolver().delete(
+ outputUri, null, null);
+ }
+ });
+ mOutputUri = null;
+ }
+ } finally {
+ mRecorder.release();
+ mRecorder = null;
+ }
+ } else {
+ Assert.fail("Not currently recording!");
+ return null;
+ }
+ }
+
+ if (mOutputFD != null) {
+ try {
+ mOutputFD.close();
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+ mOutputFD = null;
+ }
+
+ stopTrackingSoundLevel();
+ return mOutputUri;
+ }
+
+ private int getAmplitude() {
+ synchronized (LevelTrackingMediaRecorder.class) {
+ if (mRecorder != null) {
+ final int maxAmplitude = mRecorder.getMaxAmplitude() / MAX_AMPLITUDE_FACTOR;
+ return Math.min(maxAmplitude, 100);
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ private void startTrackingSoundLevel() {
+ stopTrackingSoundLevel();
+ mRefreshLevelThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ synchronized (LevelTrackingMediaRecorder.class) {
+ if (mRecorder != null) {
+ mLevelSource.setSpeechLevel(getAmplitude());
+ } else {
+ // The recording session is over, finish the thread.
+ return;
+ }
+ }
+ Thread.sleep(REFRESH_INTERVAL_MILLIS);
+ }
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ };
+ mRefreshLevelThread.start();
+ }
+
+ private void stopTrackingSoundLevel() {
+ if (mRefreshLevelThread != null && mRefreshLevelThread.isAlive()) {
+ mRefreshLevelThread.interrupt();
+ mRefreshLevelThread = null;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaChooser.java b/src/com/android/messaging/ui/mediapicker/MediaChooser.java
new file mode 100644
index 0000000..9ac0d1b
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MediaChooser.java
@@ -0,0 +1,216 @@
+/*
+ * 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.app.FragmentManager;
+import android.content.Context;
+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 android.widget.ImageButton;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.ImmutableBindingRef;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
+import com.android.messaging.ui.BasePagerViewHolder;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.OsUtil;
+
+abstract class MediaChooser extends BasePagerViewHolder
+ implements DraftMessageSubscriptionDataProvider {
+ /** The media picker that the chooser is hosted in */
+ protected final MediaPicker mMediaPicker;
+
+ /** Referencing the main media picker binding to perform data loading */
+ protected final ImmutableBindingRef<MediaPickerData> mBindingRef;
+
+ /** True if this is the selected chooser */
+ protected boolean mSelected;
+
+ /** True if this chooser is open */
+ protected boolean mOpen;
+
+ /** The button to show in the tab strip */
+ private ImageButton mTabButton;
+
+ /** Used by subclasses to indicate that no loader is required from the data model in order for
+ * this chooser to function.
+ */
+ public static final int NO_LOADER_REQUIRED = -1;
+
+ /**
+ * Initializes a new instance of the Chooser class
+ * @param mediaPicker The media picker that the chooser is hosted in
+ */
+ MediaChooser(final MediaPicker mediaPicker) {
+ Assert.notNull(mediaPicker);
+ mMediaPicker = mediaPicker;
+ mBindingRef = mediaPicker.getMediaPickerDataBinding();
+ mSelected = false;
+ }
+
+ protected void setSelected(final boolean selected) {
+ mSelected = selected;
+ if (selected) {
+ // If we're selected, it must be open
+ mOpen = true;
+ }
+ if (mTabButton != null) {
+ mTabButton.setSelected(selected);
+ mTabButton.setAlpha(selected ? 1 : 0.5f);
+ }
+ }
+
+ ImageButton getTabButton() {
+ return mTabButton;
+ }
+
+ void onCreateTabButton(final LayoutInflater inflater, final ViewGroup parent) {
+ mTabButton = (ImageButton) inflater.inflate(
+ R.layout.mediapicker_tab_button,
+ parent,
+ false /* addToParent */);
+ mTabButton.setImageResource(getIconResource());
+ mTabButton.setContentDescription(
+ inflater.getContext().getResources().getString(getIconDescriptionResource()));
+ setSelected(mSelected);
+ mTabButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ mMediaPicker.selectChooser(MediaChooser.this);
+ }
+ });
+ }
+
+ protected Context getContext() {
+ return mMediaPicker.getActivity();
+ }
+
+ protected FragmentManager getFragmentManager() {
+ return OsUtil.isAtLeastJB_MR1() ? mMediaPicker.getChildFragmentManager() :
+ mMediaPicker.getFragmentManager();
+ }
+ protected LayoutInflater getLayoutInflater() {
+ return LayoutInflater.from(getContext());
+ }
+
+ /** Allows the chooser to handle full screen change */
+ void onFullScreenChanged(final boolean fullScreen) {}
+
+ /** Allows the chooser to handle the chooser being opened or closed */
+ void onOpenedChanged(final boolean open) {
+ mOpen = open;
+ }
+
+ /** @return The bit field of media types that this chooser can pick */
+ public abstract int getSupportedMediaTypes();
+
+ /** @return The resource id of the icon for the chooser */
+ abstract int getIconResource();
+
+ /** @return The resource id of the string to use for the accessibility text of the icon */
+ abstract int getIconDescriptionResource();
+
+ /**
+ * Sets up the action bar to show the current state of the full-screen chooser
+ * @param actionBar The action bar to populate
+ */
+ void updateActionBar(final ActionBar actionBar) {
+ final int actionBarTitleResId = getActionBarTitleResId();
+ if (actionBarTitleResId == 0) {
+ actionBar.hide();
+ } else {
+ actionBar.setCustomView(null);
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.show();
+ // Use X instead of <- in the action bar
+ actionBar.setHomeAsUpIndicator(R.drawable.ic_remove_small_light);
+ actionBar.setTitle(actionBarTitleResId);
+ }
+ }
+
+ /**
+ * Returns the resource Id used for the action bar title.
+ */
+ abstract int getActionBarTitleResId();
+
+ /**
+ * Throws an exception if the media chooser object doesn't require data support.
+ */
+ public void onDataUpdated(final Object data, final int loaderId) {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Called by the MediaPicker to determine whether this panel can be swiped down further. If
+ * not, then a swipe down gestured will be captured by the MediaPickerPanel to shrink the
+ * entire panel.
+ */
+ public boolean canSwipeDown() {
+ return false;
+ }
+
+ /**
+ * Typically the media picker is closed when the IME is opened, but this allows the chooser to
+ * specify that showing the IME is okay while the chooser is up
+ */
+ public boolean canShowIme() {
+ return false;
+ }
+
+ public boolean onBackPressed() {
+ return false;
+ }
+
+ public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
+ }
+
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ return false;
+ }
+
+ public void setThemeColor(final int color) {
+ }
+
+ /**
+ * Returns true if the chooser is owning any incoming touch events, so that the media picker
+ * panel won't process it and slide the panel.
+ */
+ public boolean isHandlingTouch() {
+ return false;
+ }
+
+ public void stopTouchHandling() {
+ }
+
+ @Override
+ public int getConversationSelfSubId() {
+ return mMediaPicker.getConversationSelfSubId();
+ }
+
+ /** Optional activity life-cycle methods to be overridden by subclasses */
+ public void onPause() { }
+ public void onResume() { }
+ protected void onRequestPermissionsResult(
+ final int requestCode, final String permissions[], final int[] grantResults) { }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
new file mode 100644
index 0000000..f441d09
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
@@ -0,0 +1,736 @@
+/*
+ * 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.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+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 android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.binding.ImmutableBindingRef;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
+import com.android.messaging.ui.BugleActionBarActivity;
+import com.android.messaging.ui.FixedViewPagerAdapter;
+import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener;
+import com.android.messaging.util.AccessibilityUtil;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.UiUtils;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Fragment used to select or capture media to be added to the message
+ */
+public class MediaPicker extends Fragment implements DraftMessageSubscriptionDataProvider {
+ /** The listener interface for events from the media picker */
+ public interface MediaPickerListener {
+ /** Called when the media picker is opened so the host can accommodate the UI */
+ void onOpened();
+
+ /**
+ * Called when the media picker goes into or leaves full screen mode so the host can
+ * accommodate the fullscreen UI
+ */
+ void onFullScreenChanged(boolean fullScreen);
+
+ /**
+ * Called when the user selects one or more items
+ * @param items The list of items which were selected
+ */
+ void onItemsSelected(Collection<MessagePartData> items, boolean dismissMediaPicker);
+
+ /**
+ * Called when the user unselects one item.
+ */
+ void onItemUnselected(MessagePartData item);
+
+ /**
+ * Called when the media picker is closed. Always called immediately after onItemsSelected
+ */
+ void onDismissed();
+
+ /**
+ * Called when media item selection is confirmed in a multi-select action.
+ */
+ void onConfirmItemSelection();
+
+ /**
+ * Called when a pending attachment is added.
+ * @param pendingItem the pending attachment data being loaded.
+ */
+ void onPendingItemAdded(PendingAttachmentData pendingItem);
+
+ /**
+ * Called when a new media chooser is selected.
+ */
+ void onChooserSelected(final int chooserIndex);
+ }
+
+ /** The tag used when registering and finding this fragment */
+ public static final String FRAGMENT_TAG = "mediapicker";
+
+ // Media type constants that the media picker supports
+ public static final int MEDIA_TYPE_DEFAULT = 0x0000;
+ public static final int MEDIA_TYPE_NONE = 0x0000;
+ public static final int MEDIA_TYPE_IMAGE = 0x0001;
+ public static final int MEDIA_TYPE_VIDEO = 0x0002;
+ public static final int MEDIA_TYPE_AUDIO = 0x0004;
+ public static final int MEDIA_TYPE_VCARD = 0x0008;
+ public static final int MEDIA_TYPE_LOCATION = 0x0010;
+ private static final int MEDA_TYPE_INVALID = 0x0020;
+ public static final int MEDIA_TYPE_ALL = 0xFFFF;
+
+ /** The listener to call when events occur */
+ private MediaPickerListener mListener;
+
+ /** The handler used to dispatch calls to the listener */
+ private Handler mListenerHandler;
+
+ /** The bit flags of media types supported */
+ private int mSupportedMediaTypes;
+
+ /** The list of choosers which could be within the media picker */
+ private final MediaChooser[] mChoosers;
+
+ /** The list of currently enabled choosers */
+ private final ArrayList<MediaChooser> mEnabledChoosers;
+
+ /** The currently selected chooser */
+ private MediaChooser mSelectedChooser;
+
+ /** The main panel that controls the custom layout */
+ private MediaPickerPanel mMediaPickerPanel;
+
+ /** The linear layout that holds the icons to select individual chooser tabs */
+ private LinearLayout mTabStrip;
+
+ /** The view pager to swap between choosers */
+ private ViewPager mViewPager;
+
+ /** The current pager adapter for the view pager */
+ private FixedViewPagerAdapter<MediaChooser> mPagerAdapter;
+
+ /** True if the media picker is visible */
+ private boolean mOpen;
+
+ /** The theme color to use to make the media picker match the rest of the UI */
+ private int mThemeColor;
+
+ @VisibleForTesting
+ final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);
+
+ /** Handles picking image from the document picker */
+ private DocumentImagePicker mDocumentImagePicker;
+
+ /** Provides subscription-related data to access per-subscription configurations. */
+ private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
+
+ /** Provides access to DraftMessageData associated with the current conversation */
+ private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+
+ public MediaPicker() {
+ this(Factory.get().getApplicationContext());
+ }
+
+ public MediaPicker(final Context context) {
+ mBinding.bind(DataModel.get().createMediaPickerData(context));
+ mEnabledChoosers = new ArrayList<MediaChooser>();
+ mChoosers = new MediaChooser[] {
+ new CameraMediaChooser(this),
+ new GalleryMediaChooser(this),
+ new AudioMediaChooser(this),
+ };
+
+ mOpen = false;
+ setSupportedMediaTypes(MEDIA_TYPE_ALL);
+ }
+
+ private boolean mIsAttached;
+ private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID;
+ private boolean mAnimateOnAttach;
+
+ @Override
+ public void onAttach (final Activity activity) {
+ super.onAttach(activity);
+ mIsAttached = true;
+ if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) {
+ // open() was previously called. Do the pending open now.
+ doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach);
+ }
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinding.getData().init(getLoaderManager());
+ mDocumentImagePicker = new DocumentImagePicker(this,
+ new SelectionListener() {
+ @Override
+ public void onDocumentSelected(final PendingAttachmentData data) {
+ if (mBinding.isBound()) {
+ dispatchPendingItemAdded(data);
+ }
+ }
+ });
+ }
+
+ @Override
+ public View onCreateView(
+ final LayoutInflater inflater,
+ final ViewGroup container,
+ final Bundle savedInstanceState) {
+ mMediaPickerPanel = (MediaPickerPanel) inflater.inflate(
+ R.layout.mediapicker_fragment,
+ container,
+ false);
+ mMediaPickerPanel.setMediaPicker(this);
+ mTabStrip = (LinearLayout) mMediaPickerPanel.findViewById(R.id.mediapicker_tabstrip);
+ mTabStrip.setBackgroundColor(mThemeColor);
+ for (final MediaChooser chooser : mChoosers) {
+ chooser.onCreateTabButton(inflater, mTabStrip);
+ final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
+ MEDIA_TYPE_NONE;
+ final ImageButton tabButton = chooser.getTabButton();
+ if (tabButton != null) {
+ tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
+ mTabStrip.addView(tabButton);
+ }
+ }
+
+ mViewPager = (ViewPager) mMediaPickerPanel.findViewById(R.id.mediapicker_view_pager);
+ mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(
+ final int position,
+ final float positionOffset,
+ final int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ // The position returned is relative to if we are in RtL mode. This class never
+ // switches the indices of the elements if we are in RtL mode so we need to
+ // translate the index back. For example, if the user clicked the item most to the
+ // right in RtL mode we would want the index to appear as 0 here, however the
+ // position returned would the last possible index.
+ if (UiUtils.isRtlMode()) {
+ position = mEnabledChoosers.size() - 1 - position;
+ }
+ selectChooser(mEnabledChoosers.get(position));
+ }
+
+ @Override
+ public void onPageScrollStateChanged(final int state) {
+ }
+ });
+ // Camera initialization is expensive, so don't realize offscreen pages if not needed.
+ mViewPager.setOffscreenPageLimit(0);
+ mViewPager.setAdapter(mPagerAdapter);
+ final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
+ getActivity());
+ mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
+ mMediaPickerPanel.setExpanded(mOpen, true, mEnabledChoosers.indexOf(mSelectedChooser));
+ return mMediaPickerPanel;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ CameraManager.get().onPause();
+ for (final MediaChooser chooser : mEnabledChoosers) {
+ chooser.onPause();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ CameraManager.get().onResume();
+
+ for (final MediaChooser chooser : mEnabledChoosers) {
+ chooser.onResume();
+ }
+ }
+
+ @Override
+ public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mDocumentImagePicker.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mBinding.unbind();
+ }
+
+ /**
+ * Sets the theme color to make the media picker match the surrounding UI
+ * @param themeColor The new theme color
+ */
+ public void setConversationThemeColor(final int themeColor) {
+ mThemeColor = themeColor;
+ if (mTabStrip != null) {
+ mTabStrip.setBackgroundColor(mThemeColor);
+ }
+
+ for (final MediaChooser chooser : mEnabledChoosers) {
+ chooser.setThemeColor(mThemeColor);
+ }
+ }
+
+ /**
+ * Gets the current conversation theme color.
+ */
+ public int getConversationThemeColor() {
+ return mThemeColor;
+ }
+
+ public void setDraftMessageDataModel(final BindingBase<DraftMessageData> draftBinding) {
+ mDraftMessageDataModel = Binding.createBindingReference(draftBinding);
+ }
+
+ public ImmutableBindingRef<DraftMessageData> getDraftMessageDataModel() {
+ return mDraftMessageDataModel;
+ }
+
+ public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
+ mSubscriptionDataProvider = provider;
+ }
+
+ @Override
+ public int getConversationSelfSubId() {
+ return mSubscriptionDataProvider.getConversationSelfSubId();
+ }
+
+ /**
+ * Opens the media picker and optionally shows the chooser for the supplied media type
+ * @param startingMediaType The media type of the chooser to open if {@link #MEDIA_TYPE_DEFAULT}
+ * is used, then the default chooser from saved shared prefs is opened
+ */
+ public void open(final int startingMediaType, final boolean animate) {
+ mOpen = true;
+ if (mIsAttached) {
+ doOpen(startingMediaType, animate);
+ } else {
+ // open() can get called immediately after the MediaPicker is created. In that case,
+ // we defer doing work as it may require an attached fragment (eg. calling
+ // Fragment#requestPermission)
+ mStartingMediaTypeOnAttach = startingMediaType;
+ mAnimateOnAttach = animate;
+ }
+ }
+
+ private void doOpen(int startingMediaType, final boolean animate) {
+ final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
+ // getActivity() will be null at this point
+ Factory.get().getApplicationContext());
+
+ // If no specific starting type is specified (i.e. MEDIA_TYPE_DEFAULT), try to get the
+ // last opened chooser index from shared prefs.
+ if (startingMediaType == MEDIA_TYPE_DEFAULT) {
+ final int selectedChooserIndex = mBinding.getData().getSelectedChooserIndex();
+ if (selectedChooserIndex >= 0 && selectedChooserIndex < mEnabledChoosers.size()) {
+ selectChooser(mEnabledChoosers.get(selectedChooserIndex));
+ } else {
+ // This is the first time the picker is being used
+ if (isTouchExplorationEnabled) {
+ // Accessibility defaults to audio attachment mode.
+ startingMediaType = MEDIA_TYPE_AUDIO;
+ }
+ }
+ }
+
+ if (mSelectedChooser == null) {
+ for (final MediaChooser chooser : mEnabledChoosers) {
+ if (startingMediaType == MEDIA_TYPE_DEFAULT ||
+ (startingMediaType & chooser.getSupportedMediaTypes()) != MEDIA_TYPE_NONE) {
+ selectChooser(chooser);
+ break;
+ }
+ }
+ }
+
+ if (mSelectedChooser == null) {
+ // Fall back to the first chooser.
+ selectChooser(mEnabledChoosers.get(0));
+ }
+
+ if (mMediaPickerPanel != null) {
+ mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
+ mMediaPickerPanel.setExpanded(true, animate,
+ mEnabledChoosers.indexOf(mSelectedChooser));
+ }
+ }
+
+ /** @return True if the media picker is open */
+ public boolean isOpen() {
+ return mOpen;
+ }
+
+ /**
+ * Sets the list of media types to allow the user to select
+ * @param mediaTypes The bit flags of media types to allow. Can be any combination of the
+ * MEDIA_TYPE_* values
+ */
+ void setSupportedMediaTypes(final int mediaTypes) {
+ mSupportedMediaTypes = mediaTypes;
+ mEnabledChoosers.clear();
+ boolean selectNextChooser = false;
+ for (final MediaChooser chooser : mChoosers) {
+ final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
+ MEDIA_TYPE_NONE;
+ if (enabled) {
+ // TODO Add a way to inform the chooser which media types are supported
+ mEnabledChoosers.add(chooser);
+ if (selectNextChooser) {
+ selectChooser(chooser);
+ selectNextChooser = false;
+ }
+ } else if (mSelectedChooser == chooser) {
+ selectNextChooser = true;
+ }
+ final ImageButton tabButton = chooser.getTabButton();
+ if (tabButton != null) {
+ tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ if (selectNextChooser && mEnabledChoosers.size() > 0) {
+ selectChooser(mEnabledChoosers.get(0));
+ }
+ final MediaChooser[] enabledChoosers = new MediaChooser[mEnabledChoosers.size()];
+ mEnabledChoosers.toArray(enabledChoosers);
+ mPagerAdapter = new FixedViewPagerAdapter<MediaChooser>(enabledChoosers);
+ if (mViewPager != null) {
+ mViewPager.setAdapter(mPagerAdapter);
+ }
+
+ // Only rebind data if we are currently bound. Otherwise, we must have not
+ // bound to any data yet and should wait until onCreate() to bind data.
+ if (mBinding.isBound() && getActivity() != null) {
+ mBinding.unbind();
+ mBinding.bind(DataModel.get().createMediaPickerData(getActivity()));
+ mBinding.getData().init(getLoaderManager());
+ }
+ }
+
+ ViewPager getViewPager() {
+ return mViewPager;
+ }
+
+ /** Hides the media picker, and frees up any resources it’s using */
+ public void dismiss(final boolean animate) {
+ mOpen = false;
+ if (mMediaPickerPanel != null) {
+ mMediaPickerPanel.setExpanded(false, animate, MediaPickerPanel.PAGE_NOT_SET);
+ }
+ mSelectedChooser = null;
+ }
+
+ /**
+ * Sets the listener for the media picker events
+ * @param listener The listener which will receive events
+ */
+ public void setListener(final MediaPickerListener listener) {
+ Assert.isMainThread();
+ mListener = listener;
+ mListenerHandler = listener != null ? new Handler() : null;
+ }
+
+ /** @return True if the media picker is in full-screen mode */
+ public boolean isFullScreen() {
+ return mMediaPickerPanel != null && mMediaPickerPanel.isFullScreen();
+ }
+
+ public void setFullScreen(final boolean fullScreen) {
+ mMediaPickerPanel.setFullScreenView(fullScreen, true);
+ }
+
+ public void updateActionBar(final ActionBar actionBar) {
+ if (getActivity() == null) {
+ return;
+ }
+ if (isFullScreen() && mSelectedChooser != null) {
+ mSelectedChooser.updateActionBar(actionBar);
+ } else {
+ actionBar.hide();
+ }
+ }
+
+ /**
+ * Selects a new chooser
+ * @param newSelectedChooser The newly selected chooser
+ */
+ void selectChooser(final MediaChooser newSelectedChooser) {
+ if (mSelectedChooser == newSelectedChooser) {
+ return;
+ }
+
+ if (mSelectedChooser != null) {
+ mSelectedChooser.setSelected(false);
+ }
+ mSelectedChooser = newSelectedChooser;
+ if (mSelectedChooser != null) {
+ mSelectedChooser.setSelected(true);
+ }
+
+ final int chooserIndex = mEnabledChoosers.indexOf(mSelectedChooser);
+ if (mViewPager != null) {
+ mViewPager.setCurrentItem(chooserIndex, true /* smoothScroll */);
+ }
+
+ if (isFullScreen()) {
+ invalidateOptionsMenu();
+ }
+
+ // Save the newly selected chooser's index so we may directly switch to it the
+ // next time user opens the media picker.
+ mBinding.getData().saveSelectedChooserIndex(chooserIndex);
+ if (mMediaPickerPanel != null) {
+ mMediaPickerPanel.onChooserChanged();
+ }
+ dispatchChooserSelected(chooserIndex);
+ }
+
+ public boolean canShowIme() {
+ if (mSelectedChooser != null) {
+ return mSelectedChooser.canShowIme();
+ }
+ return false;
+ }
+
+ public boolean onBackPressed() {
+ return mSelectedChooser != null && mSelectedChooser.onBackPressed();
+ }
+
+ void invalidateOptionsMenu() {
+ ((BugleActionBarActivity) getActivity()).supportInvalidateOptionsMenu();
+ }
+
+ void dispatchOpened() {
+ setHasOptionsMenu(false);
+ mOpen = true;
+ mPagerAdapter.notifyDataSetChanged();
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onOpened();
+ }
+ });
+ }
+ if (mSelectedChooser != null) {
+ mSelectedChooser.onFullScreenChanged(false);
+ mSelectedChooser.onOpenedChanged(true);
+ }
+ }
+
+ void dispatchDismissed() {
+ setHasOptionsMenu(false);
+ mOpen = false;
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDismissed();
+ }
+ });
+ }
+ if (mSelectedChooser != null) {
+ mSelectedChooser.onOpenedChanged(false);
+ }
+ }
+
+ void dispatchFullScreen(final boolean fullScreen) {
+ setHasOptionsMenu(fullScreen);
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onFullScreenChanged(fullScreen);
+ }
+ });
+ }
+ if (mSelectedChooser != null) {
+ mSelectedChooser.onFullScreenChanged(fullScreen);
+ }
+ }
+
+ void dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker) {
+ final List<MessagePartData> items = new ArrayList<MessagePartData>(1);
+ items.add(item);
+ dispatchItemsSelected(items, dismissMediaPicker);
+ }
+
+ void dispatchItemsSelected(final Collection<MessagePartData> items,
+ final boolean dismissMediaPicker) {
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onItemsSelected(items, dismissMediaPicker);
+ }
+ });
+ }
+
+ if (isFullScreen() && !dismissMediaPicker) {
+ invalidateOptionsMenu();
+ }
+ }
+
+ void dispatchItemUnselected(final MessagePartData item) {
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onItemUnselected(item);
+ }
+ });
+ }
+
+ if (isFullScreen()) {
+ invalidateOptionsMenu();
+ }
+ }
+
+ void dispatchConfirmItemSelection() {
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onConfirmItemSelection();
+ }
+ });
+ }
+ }
+
+ void dispatchPendingItemAdded(final PendingAttachmentData pendingItem) {
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onPendingItemAdded(pendingItem);
+ }
+ });
+ }
+
+ if (isFullScreen()) {
+ invalidateOptionsMenu();
+ }
+ }
+
+ void dispatchChooserSelected(final int chooserIndex) {
+ if (mListener != null) {
+ mListenerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onChooserSelected(chooserIndex);
+ }
+ });
+ }
+ }
+
+ public boolean canSwipeDownChooser() {
+ return mSelectedChooser == null ? false : mSelectedChooser.canSwipeDown();
+ }
+
+ public boolean isChooserHandlingTouch() {
+ return mSelectedChooser == null ? false : mSelectedChooser.isHandlingTouch();
+ }
+
+ public void stopChooserTouchHandling() {
+ if (mSelectedChooser != null) {
+ mSelectedChooser.stopTouchHandling();
+ }
+ }
+
+ boolean getChooserShowsActionBarInFullScreen() {
+ return mSelectedChooser == null ? false : mSelectedChooser.getActionBarTitleResId() != 0;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ if (mSelectedChooser != null) {
+ mSelectedChooser.onCreateOptionsMenu(inflater, menu);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ return (mSelectedChooser != null && mSelectedChooser.onOptionsItemSelected(item)) ||
+ super.onOptionsItemSelected(item);
+ }
+
+ PagerAdapter getPagerAdapter() {
+ return mPagerAdapter;
+ }
+
+ public void resetViewHolderState() {
+ mPagerAdapter.resetState();
+ }
+
+ /**
+ * Launch an external picker to pick item from document picker as attachment.
+ */
+ public void launchDocumentPicker() {
+ mDocumentImagePicker.launchPicker();
+ }
+
+ public ImmutableBindingRef<MediaPickerData> getMediaPickerDataBinding() {
+ return BindingBase.createBindingReference(mBinding);
+ }
+
+ protected static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
+ 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;
+
+ @Override
+ public void onRequestPermissionsResult(
+ final int requestCode, final String permissions[], final int[] grantResults) {
+ if (mSelectedChooser != null) {
+ mSelectedChooser.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java b/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java
new file mode 100644
index 0000000..cc3a4a1
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+public class MediaPickerGridView extends GridView {
+
+ public MediaPickerGridView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Returns if the grid 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/ui/mediapicker/MediaPickerPanel.java b/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java
new file mode 100644
index 0000000..56b0a03
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java
@@ -0,0 +1,563 @@
+/*
+ * 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.content.res.Resources;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.widget.LinearLayout;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.PagingAwareViewPager;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UiUtils;
+
+/**
+ * Custom layout panel which makes the MediaPicker animations seamless and synchronized
+ * Designed to be very specific to the MediaPicker's usage
+ */
+public class MediaPickerPanel extends ViewGroup {
+ /**
+ * The window of time in which we might to decide to reinterpret the intent of a gesture.
+ */
+ private static final long TOUCH_RECAPTURE_WINDOW_MS = 500L;
+
+ // The two view components to layout
+ private LinearLayout mTabStrip;
+ private boolean mFullScreenOnly;
+ private PagingAwareViewPager mViewPager;
+
+ /**
+ * True if the MediaPicker is full screen or animating into it
+ */
+ private boolean mFullScreen;
+
+ /**
+ * True if the MediaPicker is open at all
+ */
+ private boolean mExpanded;
+
+ /**
+ * The current desired height of the MediaPicker. This property may be animated and the
+ * measure pass uses it to determine what size the components are.
+ */
+ private int mCurrentDesiredHeight;
+
+ private final Handler mHandler = new Handler();
+
+ /**
+ * The media picker for dispatching events to the MediaPicker's listener
+ */
+ private MediaPicker mMediaPicker;
+
+ /**
+ * The computed default "half-screen" height of the view pager in px
+ */
+ private final int mDefaultViewPagerHeight;
+
+ /**
+ * The action bar height used to compute the padding on the view pager when it's full screen.
+ */
+ private final int mActionBarHeight;
+
+ private TouchHandler mTouchHandler;
+
+ static final int PAGE_NOT_SET = -1;
+
+ public MediaPickerPanel(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ // Cache the computed dimension
+ mDefaultViewPagerHeight = getResources().getDimensionPixelSize(
+ R.dimen.mediapicker_default_chooser_height);
+ mActionBarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTabStrip = (LinearLayout) findViewById(R.id.mediapicker_tabstrip);
+ mViewPager = (PagingAwareViewPager) findViewById(R.id.mediapicker_view_pager);
+ mTouchHandler = new TouchHandler();
+ setOnTouchListener(mTouchHandler);
+ mViewPager.setOnTouchListener(mTouchHandler);
+
+ // Make sure full screen mode is updated in landscape mode change when the panel is open.
+ addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ private boolean mLandscapeMode = UiUtils.isLandscapeMode();
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ final boolean newLandscapeMode = UiUtils.isLandscapeMode();
+ if (mLandscapeMode != newLandscapeMode) {
+ mLandscapeMode = newLandscapeMode;
+ if (mExpanded) {
+ setExpanded(mExpanded, false /* animate */, mViewPager.getCurrentItem(),
+ true /* force */);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ int requestedHeight = MeasureSpec.getSize(heightMeasureSpec);
+ if (mMediaPicker.getChooserShowsActionBarInFullScreen()) {
+ requestedHeight -= mActionBarHeight;
+ }
+ int desiredHeight = Math.min(mCurrentDesiredHeight, requestedHeight);
+ if (mExpanded && desiredHeight == 0) {
+ // If we want to be shown, we have to have a non-0 height. Returning a height of 0 will
+ // cause the framework to abort the animation from 0, so we must always have some
+ // height once we start expanding
+ desiredHeight = 1;
+ } else if (!mExpanded && desiredHeight == 0) {
+ mViewPager.setVisibility(View.GONE);
+ mViewPager.setAdapter(null);
+ }
+
+ measureChild(mTabStrip, widthMeasureSpec, heightMeasureSpec);
+
+ int tabStripHeight;
+ if (requiresFullScreen()) {
+ // Ensure that the tab strip is always visible, even in full screen.
+ tabStripHeight = mTabStrip.getMeasuredHeight();
+ } else {
+ // Slide out the tab strip at the end of the animation to full screen.
+ tabStripHeight = Math.min(mTabStrip.getMeasuredHeight(),
+ requestedHeight - desiredHeight);
+ }
+
+ // If we are animating and have an interim desired height, use the default height. We can't
+ // take the max here as on some devices the mDefaultViewPagerHeight may be too big in
+ // landscape mode after animation.
+ final int tabAdjustedDesiredHeight = desiredHeight - tabStripHeight;
+ final int viewPagerHeight =
+ tabAdjustedDesiredHeight <= 1 ? mDefaultViewPagerHeight : tabAdjustedDesiredHeight;
+
+ int viewPagerHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ viewPagerHeight, MeasureSpec.EXACTLY);
+ measureChild(mViewPager, widthMeasureSpec, viewPagerHeightMeasureSpec);
+ setMeasuredDimension(mViewPager.getMeasuredWidth(), desiredHeight);
+ }
+
+ @Override
+ protected void onLayout(final boolean changed, final int left, final int top, final int right,
+ final int bottom) {
+ int y = top;
+ final int width = right - left;
+
+ final int viewPagerHeight = mViewPager.getMeasuredHeight();
+ mViewPager.layout(0, y, width, y + viewPagerHeight);
+ y += viewPagerHeight;
+
+ mTabStrip.layout(0, y, width, y + mTabStrip.getMeasuredHeight());
+ }
+
+ void onChooserChanged() {
+ if (mFullScreen) {
+ setDesiredHeight(getDesiredHeight(), true);
+ }
+ }
+
+ void setFullScreenOnly(boolean fullScreenOnly) {
+ mFullScreenOnly = fullScreenOnly;
+ }
+
+ boolean isFullScreen() {
+ return mFullScreen;
+ }
+
+ void setMediaPicker(final MediaPicker mediaPicker) {
+ mMediaPicker = mediaPicker;
+ }
+
+ /**
+ * Get the desired height of the media picker panel for when the panel is not in motion (i.e.
+ * not being dragged by the user).
+ */
+ private int getDesiredHeight() {
+ if (mFullScreen) {
+ int fullHeight = getContext().getResources().getDisplayMetrics().heightPixels;
+ if (OsUtil.isAtLeastKLP() && isAttachedToWindow()) {
+ // When we're attached to the window, we can get an accurate height, not necessary
+ // on older API level devices because they don't include the action bar height
+ View composeContainer =
+ getRootView().findViewById(R.id.conversation_and_compose_container);
+ if (composeContainer != null) {
+ // protect against composeContainer having been unloaded already
+ fullHeight -= UiUtils.getMeasuredBoundsOnScreen(composeContainer).top;
+ }
+ }
+ if (mMediaPicker.getChooserShowsActionBarInFullScreen()) {
+ return fullHeight - mActionBarHeight;
+ } else {
+ return fullHeight;
+ }
+ } else if (mExpanded) {
+ return LayoutParams.WRAP_CONTENT;
+ } else {
+ return 0;
+ }
+ }
+
+ private void setupViewPager(final int startingPage) {
+ mViewPager.setVisibility(View.VISIBLE);
+ if (startingPage >= 0 && startingPage < mMediaPicker.getPagerAdapter().getCount()) {
+ mViewPager.setAdapter(mMediaPicker.getPagerAdapter());
+ mViewPager.setCurrentItem(startingPage);
+ }
+ updateViewPager();
+ }
+
+ /**
+ * Expand the media picker panel. Since we always set the pager adapter to null when the panel
+ * is collapsed, we need to restore the adapter and the starting page.
+ * @param expanded expanded or collapsed
+ * @param animate need animation
+ * @param startingPage the desired selected page to start
+ */
+ void setExpanded(final boolean expanded, final boolean animate, final int startingPage) {
+ setExpanded(expanded, animate, startingPage, false /* force */);
+ }
+
+ private void setExpanded(final boolean expanded, final boolean animate, final int startingPage,
+ final boolean force) {
+ if (expanded == mExpanded && !force) {
+ return;
+ }
+ mFullScreen = false;
+ mExpanded = expanded;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ setDesiredHeight(getDesiredHeight(), animate);
+ }
+ });
+ if (expanded) {
+ setupViewPager(startingPage);
+ mMediaPicker.dispatchOpened();
+ } else {
+ mMediaPicker.dispatchDismissed();
+ }
+
+ // Call setFullScreenView() when we are in landscape mode so it can go full screen as
+ // soon as it is expanded.
+ if (expanded && requiresFullScreen()) {
+ setFullScreenView(true, animate);
+ }
+ }
+
+ private boolean requiresFullScreen() {
+ return mFullScreenOnly || UiUtils.isLandscapeMode();
+ }
+
+ private void setDesiredHeight(int height, final boolean animate) {
+ final int startHeight = mCurrentDesiredHeight;
+ if (height == LayoutParams.WRAP_CONTENT) {
+ height = measureHeight();
+ }
+ clearAnimation();
+ if (animate) {
+ final int deltaHeight = height - startHeight;
+ final Animation animation = new Animation() {
+ @Override
+ protected void applyTransformation(final float interpolatedTime,
+ final Transformation t) {
+ mCurrentDesiredHeight = (int) (startHeight + deltaHeight * interpolatedTime);
+ requestLayout();
+ }
+
+ @Override
+ public boolean willChangeBounds() {
+ return true;
+ }
+ };
+ animation.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
+ animation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR);
+ startAnimation(animation);
+ } else {
+ mCurrentDesiredHeight = height;
+ }
+ requestLayout();
+ }
+
+ /**
+ * @return The minimum total height of the view
+ */
+ private int measureHeight() {
+ final int measureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST);
+ measureChild(mTabStrip, measureSpec, measureSpec);
+ return mDefaultViewPagerHeight + mTabStrip.getMeasuredHeight();
+ }
+
+ /**
+ * Enters or leaves full screen view
+ *
+ * @param fullScreen True to enter full screen view, false to leave
+ * @param animate True to animate the transition
+ */
+ void setFullScreenView(final boolean fullScreen, final boolean animate) {
+ if (fullScreen == mFullScreen) {
+ return;
+ }
+
+ if (requiresFullScreen() && !fullScreen) {
+ setExpanded(false /* expanded */, true /* animate */, PAGE_NOT_SET);
+ return;
+ }
+ mFullScreen = fullScreen;
+ setDesiredHeight(getDesiredHeight(), animate);
+ mMediaPicker.dispatchFullScreen(mFullScreen);
+ updateViewPager();
+ }
+
+ /**
+ * ViewPager should have its paging disabled when in full screen mode.
+ */
+ private void updateViewPager() {
+ mViewPager.setPagingEnabled(!mFullScreen);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(final MotionEvent ev) {
+ return mTouchHandler.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
+ }
+
+ /**
+ * Helper class to handle touch events and swipe gestures
+ */
+ private class TouchHandler implements OnTouchListener {
+ /**
+ * The height of the view when the touch press started
+ */
+ private int mDownHeight = -1;
+
+ /**
+ * True if the panel moved at all (changed height) during the drag
+ */
+ private boolean mMoved = false;
+
+ // The threshold constants converted from DP to px
+ private final float mFlingThresholdPx;
+ private final float mBigFlingThresholdPx;
+
+ // The system defined pixel size to determine when a movement is considered a drag.
+ private final int mTouchSlop;
+
+ /**
+ * A copy of the MotionEvent that started the drag/swipe gesture
+ */
+ private MotionEvent mDownEvent;
+
+ /**
+ * Whether we are currently moving down. We may not be able to move down in full screen
+ * mode when the child view can swipe down (such as a list view).
+ */
+ private boolean mMovedDown = false;
+
+ /**
+ * Indicates whether the child view contained in the panel can swipe down at the beginning
+ * of the drag event (i.e. the initial down). The MediaPanel can contain
+ * scrollable children such as a list view / grid view. If the child view can swipe down,
+ * We want to let the child view handle the scroll first instead of handling it ourselves.
+ */
+ private boolean mCanChildViewSwipeDown = false;
+
+ /**
+ * Necessary direction ratio for a fling to be considered in one direction this prevents
+ * horizontal swipes with small vertical components from triggering vertical swipe actions
+ */
+ private static final float DIRECTION_RATIO = 1.1f;
+
+ TouchHandler() {
+ final Resources resources = getContext().getResources();
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mFlingThresholdPx = resources.getDimensionPixelSize(
+ R.dimen.mediapicker_fling_threshold);
+ mBigFlingThresholdPx = resources.getDimensionPixelSize(
+ R.dimen.mediapicker_big_fling_threshold);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ }
+
+ /**
+ * The media picker panel may contain scrollable children such as a GridView, which eats
+ * all touch events before we get to it. Therefore, we'd like to intercept these events
+ * before the children to determine if we should handle swiping down in full screen mode.
+ * In non-full screen mode, we should handle all vertical scrolling events and leave
+ * horizontal scrolling to the view pager.
+ */
+ public boolean onInterceptTouchEvent(final MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // Never capture the initial down, so that the children may handle it
+ // as well. Let the touch handler know about the down event as well.
+ mTouchHandler.onTouch(MediaPickerPanel.this, ev);
+
+ // Ask the MediaPicker whether the contained view can be swiped down.
+ // We record the value at the start of the drag to decide the swiping mode
+ // for the entire motion.
+ mCanChildViewSwipeDown = mMediaPicker.canSwipeDownChooser();
+ return false;
+
+ case MotionEvent.ACTION_MOVE: {
+ if (mMediaPicker.isChooserHandlingTouch()) {
+ if (shouldAllowRecaptureTouch(ev)) {
+ mMediaPicker.stopChooserTouchHandling();
+ mViewPager.setPagingEnabled(true);
+ return false;
+ }
+ // If the chooser is claiming ownership on all touch events, then we
+ // shouldn't try to handle them (neither should the view pager).
+ mViewPager.setPagingEnabled(false);
+ return false;
+ } else if (mCanChildViewSwipeDown) {
+ // Never capture event if the child view can swipe down.
+ return false;
+ } else if (!mFullScreen && mMoved) {
+ // When we are not fullscreen, we own any vertical drag motion.
+ return true;
+ } else if (mMovedDown) {
+ // We are currently handling the down swipe ourselves, so always
+ // capture this event.
+ return true;
+ } else {
+ // The current interaction mode is undetermined, so always let the
+ // touch handler know about this event. However, don't capture this
+ // event so the child may handle it as well.
+ mTouchHandler.onTouch(MediaPickerPanel.this, ev);
+
+ // Capture the touch event from now on if we are handling the drag.
+ return mFullScreen ? mMovedDown : mMoved;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether we think the user is actually trying to expand or slide despite the
+ * fact that they touched first on a chooser that captured the input.
+ */
+ private boolean shouldAllowRecaptureTouch(MotionEvent ev) {
+ final long elapsedMs = ev.getEventTime() - ev.getDownTime();
+ if (mDownEvent == null || elapsedMs == 0 || elapsedMs > TOUCH_RECAPTURE_WINDOW_MS) {
+ // Either we don't have info to decide or it's been long enough that we no longer
+ // want to reinterpret user intent.
+ return false;
+ }
+ final float dx = ev.getRawX() - mDownEvent.getRawX();
+ final float dy = ev.getRawY() - mDownEvent.getRawY();
+ final float dt = elapsedMs / 1000.0f;
+ final float maxAbsDelta = Math.max(Math.abs(dx), Math.abs(dy));
+ final float velocity = maxAbsDelta / dt;
+ return velocity > mFlingThresholdPx;
+ }
+
+ @Override
+ public boolean onTouch(final View view, final MotionEvent motionEvent) {
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_UP: {
+ if (!mMoved || mDownEvent == null) {
+ return false;
+ }
+ final float dx = motionEvent.getRawX() - mDownEvent.getRawX();
+ final float dy = motionEvent.getRawY() - mDownEvent.getRawY();
+
+ final float dt =
+ (motionEvent.getEventTime() - mDownEvent.getEventTime()) / 1000.0f;
+ final float yVelocity = dy / dt;
+
+ boolean handled = false;
+
+ // Vertical swipe occurred if the direction is as least mostly in the y
+ // component and has the required velocity (px/sec)
+ if ((dx == 0 || (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) &&
+ Math.abs(yVelocity) > mFlingThresholdPx) {
+ if (yVelocity < 0 && mExpanded) {
+ setFullScreenView(true, true);
+ handled = true;
+ } else if (yVelocity > 0) {
+ if (mFullScreen && yVelocity < mBigFlingThresholdPx) {
+ setFullScreenView(false, true);
+ } else {
+ setExpanded(false, true, PAGE_NOT_SET);
+ }
+ handled = true;
+ }
+ }
+
+ if (!handled) {
+ // If they didn't swipe enough, animate back to resting state
+ setDesiredHeight(getDesiredHeight(), true);
+ }
+ resetState();
+ break;
+ }
+ case MotionEvent.ACTION_DOWN: {
+ mDownHeight = getHeight();
+ mDownEvent = MotionEvent.obtain(motionEvent);
+ // If we are here and care about the return value (i.e. this is not called
+ // from onInterceptTouchEvent), then presumably no children view in the panel
+ // handles the down event. We'd like to handle future ACTION_MOVE events, so
+ // always claim ownership on this event so it doesn't fall through and gets
+ // cancelled by the framework.
+ return true;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mDownEvent == null) {
+ return mMoved;
+ }
+
+ final float dx = mDownEvent.getRawX() - motionEvent.getRawX();
+ final float dy = mDownEvent.getRawY() - motionEvent.getRawY();
+ // Don't act if the move is mostly horizontal
+ if (Math.abs(dy) > mTouchSlop &&
+ (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) {
+ setDesiredHeight((int) (mDownHeight + dy), false);
+ mMoved = true;
+ if (dy < -mTouchSlop) {
+ mMovedDown = true;
+ }
+ }
+ return mMoved;
+ }
+
+ }
+ return mMoved;
+ }
+
+ private void resetState() {
+ mDownEvent = null;
+ mDownHeight = -1;
+ mMoved = false;
+ mMovedDown = false;
+ mCanChildViewSwipeDown = false;
+ updateViewPager();
+ }
+ }
+}
+
diff --git a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java b/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java
new file mode 100644
index 0000000..7ac7871
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java
@@ -0,0 +1,127 @@
+/*
+ * 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.hardware.Camera;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
+import android.net.Uri;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.SafeAsyncTask;
+
+import java.io.FileNotFoundException;
+
+class MmsVideoRecorder extends MediaRecorder {
+ private static final float VIDEO_OVERSHOOT_SLOP = .85F;
+
+ private static final int BITS_PER_BYTE = 8;
+
+ // We think user will expect to be able to record videos at least this long
+ private static final long MIN_DURATION_LIMIT_SECONDS = 25;
+
+ /** The uri where video is being recorded to */
+ private Uri mTempVideoUri;
+
+ /** The settings used for video recording */
+ private final CamcorderProfile mCamcorderProfile;
+
+ public MmsVideoRecorder(final Camera camera, final int cameraIndex, final int orientation,
+ final int maxMessageSize)
+ throws FileNotFoundException {
+ mCamcorderProfile =
+ CamcorderProfile.get(cameraIndex, CamcorderProfile.QUALITY_LOW);
+ mTempVideoUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(
+ ContentType.getExtension(getContentType()));
+
+ // The video recorder can sometimes return a file that's larger than the max we
+ // say we can handle. Try to handle that overshoot by specifying an 85% limit.
+ final long sizeLimit = (long) (maxMessageSize * VIDEO_OVERSHOOT_SLOP);
+
+ // The QUALITY_LOW profile might not be low enough to allow for video of a reasonable
+ // minimum duration. Adjust a/v bitrates to allow at least MIN_DURATION_LIMIT video
+ // to be recorded.
+ int audioBitRate = mCamcorderProfile.audioBitRate;
+ int videoBitRate = mCamcorderProfile.videoBitRate;
+ final double initialDurationLimit = sizeLimit * BITS_PER_BYTE
+ / (double) (audioBitRate + videoBitRate);
+ if (initialDurationLimit < MIN_DURATION_LIMIT_SECONDS) {
+ // Reduce the suggested bitrates. These bitrates are only requests, if implementation
+ // can't actually hit these goals it will still record video at higher rate and stop when
+ // it hits the size limit.
+ final double bitRateAdjustmentFactor = initialDurationLimit / MIN_DURATION_LIMIT_SECONDS;
+ audioBitRate *= bitRateAdjustmentFactor;
+ videoBitRate *= bitRateAdjustmentFactor;
+ }
+
+ setCamera(camera);
+ setOrientationHint(orientation);
+ setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ setVideoSource(MediaRecorder.VideoSource.CAMERA);
+ setOutputFormat(mCamcorderProfile.fileFormat);
+ setOutputFile(
+ Factory.get().getApplicationContext().getContentResolver().openFileDescriptor(
+ mTempVideoUri, "w").getFileDescriptor());
+
+ // Copy settings from CamcorderProfile to MediaRecorder
+ setAudioEncodingBitRate(audioBitRate);
+ setAudioChannels(mCamcorderProfile.audioChannels);
+ setAudioEncoder(mCamcorderProfile.audioCodec);
+ setAudioSamplingRate(mCamcorderProfile.audioSampleRate);
+ setVideoEncodingBitRate(videoBitRate);
+ setVideoEncoder(mCamcorderProfile.videoCodec);
+ setVideoFrameRate(mCamcorderProfile.videoFrameRate);
+ setVideoSize(
+ mCamcorderProfile.videoFrameWidth, mCamcorderProfile.videoFrameHeight);
+ setMaxFileSize(sizeLimit);
+ }
+
+ Uri getVideoUri() {
+ return mTempVideoUri;
+ }
+
+ int getVideoWidth() {
+ return mCamcorderProfile.videoFrameWidth;
+ }
+
+ int getVideoHeight() {
+ return mCamcorderProfile.videoFrameHeight;
+ }
+
+ void cleanupTempFile() {
+ final Uri tempUri = mTempVideoUri;
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ Factory.get().getApplicationContext().getContentResolver().delete(
+ tempUri, null, null);
+ }
+ });
+ mTempVideoUri = null;
+ }
+
+ String getContentType() {
+ if (mCamcorderProfile.fileFormat == OutputFormat.MPEG_4) {
+ return ContentType.VIDEO_MP4;
+ } else {
+ // 3GPP is the only other video format with a constant in OutputFormat
+ return ContentType.VIDEO_3GPP;
+ }
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java b/src/com/android/messaging/ui/mediapicker/PausableChronometer.java
new file mode 100644
index 0000000..dc8f90b
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/PausableChronometer.java
@@ -0,0 +1,75 @@
+/*
+ * 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.os.SystemClock;
+import android.util.AttributeSet;
+import android.widget.Chronometer;
+
+import com.android.messaging.ui.PlaybackStateView;
+
+/**
+ * A pausable Chronometer implementation. The default Chronometer in Android only stops the UI
+ * from updating when you call stop(), but doesn't actually pause it. This implementation adds an
+ * additional timestamp that tracks the timespan for the pause and compensate for that.
+ */
+public class PausableChronometer extends Chronometer implements PlaybackStateView {
+ // Keeps track of how far long the Chronometer has been tracking when it's paused. We'd like
+ // to start from this time the next time it's resumed.
+ private long mTimeWhenPaused = 0;
+
+ public PausableChronometer(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Reset the timer and start counting from zero.
+ */
+ @Override
+ public void restart() {
+ reset();
+ start();
+ }
+
+ /**
+ * Reset the timer to zero, but don't start it.
+ */
+ @Override
+ public void reset() {
+ stop();
+ setBase(SystemClock.elapsedRealtime());
+ mTimeWhenPaused = 0;
+ }
+
+ /**
+ * Resume the timer after a previous pause.
+ */
+ @Override
+ public void resume() {
+ setBase(SystemClock.elapsedRealtime() - mTimeWhenPaused);
+ start();
+ }
+
+ /**
+ * Pause the timer.
+ */
+ @Override
+ public void pause() {
+ stop();
+ mTimeWhenPaused = SystemClock.elapsedRealtime() - getBase();
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
new file mode 100644
index 0000000..5dc3185
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
@@ -0,0 +1,114 @@
+/*
+ * 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.hardware.Camera;
+import android.os.Parcelable;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import java.io.IOException;
+
+/**
+ * A software rendered preview surface for the camera. This renders slower and causes more jank, so
+ * HardwareCameraPreview is preferred if possible.
+ *
+ * There is a significant amount of duplication between HardwareCameraPreview and
+ * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
+ * implementations of the shared methods are delegated to CameraPreview
+ */
+public class SoftwareCameraPreview extends SurfaceView implements CameraPreview.CameraPreviewHost {
+ private final CameraPreview mPreview;
+
+ public SoftwareCameraPreview(final Context context) {
+ super(context);
+ mPreview = new CameraPreview(this);
+ getHolder().addCallback(new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(final SurfaceHolder surfaceHolder) {
+ CameraManager.get().setSurface(mPreview);
+ }
+
+ @Override
+ public void surfaceChanged(final SurfaceHolder surfaceHolder, final int format, final int width,
+ final int height) {
+ CameraManager.get().setSurface(mPreview);
+ }
+
+ @Override
+ public void surfaceDestroyed(final SurfaceHolder surfaceHolder) {
+ CameraManager.get().setSurface(null);
+ }
+ });
+ }
+
+
+ @Override
+ protected void onVisibilityChanged(final View changedView, final int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ mPreview.onVisibilityChanged(visibility);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPreview.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mPreview.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Parcelable state) {
+ super.onRestoreInstanceState(state);
+ mPreview.onRestoreInstanceState();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
+ heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public boolean isValid() {
+ return getHolder() != null;
+ }
+
+ @Override
+ public void startPreview(final Camera camera) throws IOException {
+ camera.setPreviewDisplay(getHolder());
+ }
+
+ @Override
+ public void onCameraPermissionGranted() {
+ mPreview.onCameraPermissionGranted();
+ }
+}
+
+
diff --git a/src/com/android/messaging/ui/mediapicker/SoundLevels.java b/src/com/android/messaging/ui/mediapicker/SoundLevels.java
new file mode 100644
index 0000000..6f4dca6
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/SoundLevels.java
@@ -0,0 +1,212 @@
+/*
+ * 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.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.messaging.R;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * This view draws circular sound levels. By default the sound levels are black, unless
+ * otherwise defined via {@link #mPrimaryLevelPaint}.
+ */
+public class SoundLevels extends View {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final boolean DEBUG = false;
+
+ private boolean mCenterDefined;
+ private int mCenterX;
+ private int mCenterY;
+
+ // Paint for the main level meter, most closely follows the mic.
+ private final Paint mPrimaryLevelPaint;
+
+ // The minimum size of the levels as a percentage of the max, that is the size when volume is 0.
+ private final float mMinimumLevel;
+
+ // The minimum size of the levels, that is the size when volume is 0.
+ private final float mMinimumLevelSize;
+
+ // The maximum size of the levels, that is the size when volume is 100.
+ private final float mMaximumLevelSize;
+
+ // Generates clock ticks for the animation using the global animation loop.
+ private final TimeAnimator mSpeechLevelsAnimator;
+
+ private float mCurrentVolume;
+
+ // Indicates whether we should be animating the sound level.
+ private boolean mIsEnabled;
+
+ // Input level is pulled from here.
+ private AudioLevelSource mLevelSource;
+
+ public SoundLevels(final Context context) {
+ this(context, null);
+ }
+
+ public SoundLevels(final Context context, final AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SoundLevels(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+
+ // Safe source, replaced with system one when attached.
+ mLevelSource = new AudioLevelSource();
+ mLevelSource.setSpeechLevel(0);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SoundLevels,
+ defStyle, 0);
+
+ mMaximumLevelSize = a.getDimensionPixelOffset(
+ R.styleable.SoundLevels_maxLevelRadius, 0);
+ mMinimumLevelSize = a.getDimensionPixelOffset(
+ R.styleable.SoundLevels_minLevelRadius, 0);
+ mMinimumLevel = mMinimumLevelSize / mMaximumLevelSize;
+
+ mPrimaryLevelPaint = new Paint();
+ mPrimaryLevelPaint.setColor(
+ a.getColor(R.styleable.SoundLevels_primaryColor, Color.BLACK));
+ mPrimaryLevelPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+
+ a.recycle();
+
+ // This animator generates ticks that invalidate the
+ // view so that the animation is synced with the global animation loop.
+ // TODO: We could probably remove this in favor of using postInvalidateOnAnimation
+ // which might improve things further.
+ mSpeechLevelsAnimator = new TimeAnimator();
+ mSpeechLevelsAnimator.setRepeatCount(ObjectAnimator.INFINITE);
+ mSpeechLevelsAnimator.setTimeListener(new TimeListener() {
+ @Override
+ public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
+ final long deltaTime) {
+ invalidate();
+ }
+ });
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ if (!mIsEnabled) {
+ return;
+ }
+
+ if (!mCenterDefined) {
+ // One time computation here, because we can't rely on getWidth() to be computed at
+ // constructor time or in onFinishInflate :(.
+ mCenterX = getWidth() / 2;
+ mCenterY = getWidth() / 2;
+ mCenterDefined = true;
+ }
+
+ final int level = mLevelSource.getSpeechLevel();
+ // Either ease towards the target level, or decay away from it depending on whether
+ // its higher or lower than the current.
+ if (level > mCurrentVolume) {
+ mCurrentVolume = mCurrentVolume + ((level - mCurrentVolume) / 4);
+ } else {
+ mCurrentVolume = mCurrentVolume * 0.95f;
+ }
+
+ final float radius = mMinimumLevel + (1f - mMinimumLevel) * mCurrentVolume / 100;
+ mPrimaryLevelPaint.setStyle(Style.FILL);
+ canvas.drawCircle(mCenterX, mCenterY, radius * mMaximumLevelSize, mPrimaryLevelPaint);
+ }
+
+ public void setLevelSource(final AudioLevelSource source) {
+ if (DEBUG) {
+ Log.d(TAG, "Speech source set.");
+ }
+ mLevelSource = source;
+ }
+
+ private void startSpeechLevelsAnimator() {
+ if (DEBUG) {
+ Log.d(TAG, "startAnimator()");
+ }
+ if (!mSpeechLevelsAnimator.isStarted()) {
+ mSpeechLevelsAnimator.start();
+ }
+ }
+
+ private void stopSpeechLevelsAnimator() {
+ if (DEBUG) {
+ Log.d(TAG, "stopAnimator()");
+ }
+ if (mSpeechLevelsAnimator.isStarted()) {
+ mSpeechLevelsAnimator.end();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ stopSpeechLevelsAnimator();
+ }
+
+ @Override
+ public void setEnabled(final boolean enabled) {
+ if (enabled == mIsEnabled) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d("TAG", "setEnabled: " + enabled);
+ }
+ super.setEnabled(enabled);
+ mIsEnabled = enabled;
+ setKeepScreenOn(enabled);
+ updateSpeechLevelsAnimatorState();
+ }
+
+ private void updateSpeechLevelsAnimatorState() {
+ if (mIsEnabled) {
+ startSpeechLevelsAnimator();
+ } else {
+ stopSpeechLevelsAnimator();
+ }
+ }
+
+ /**
+ * This is required to make the View findable by uiautomator
+ */
+ @Override
+ public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(SoundLevels.class.getCanonicalName());
+ }
+
+ /**
+ * Set the alpha level of the sound circles.
+ */
+ public void setPrimaryColorAlpha(final int alpha) {
+ mPrimaryLevelPaint.setAlpha(alpha);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
new file mode 100644
index 0000000..92ed3c1
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
@@ -0,0 +1,24 @@
+/*
+ * 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.camerafocus;
+
+public interface FocusIndicator {
+ public void showStart();
+ public void showSuccess(boolean timeout);
+ public void showFail(boolean timeout);
+ public void clear();
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
new file mode 100644
index 0000000..e620fc2
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
@@ -0,0 +1,589 @@
+/*
+ * 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.camerafocus;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.Camera.Area;
+import android.hardware.Camera.Parameters;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/* A class that handles everything about focus in still picture mode.
+ * This also handles the metering area because it is the same as focus area.
+ *
+ * The test cases:
+ * (1) The camera has continuous autofocus. Move the camera. Take a picture when
+ * CAF is not in progress.
+ * (2) The camera has continuous autofocus. Move the camera. Take a picture when
+ * CAF is in progress.
+ * (3) The camera has face detection. Point the camera at some faces. Hold the
+ * shutter. Release to take a picture.
+ * (4) The camera has face detection. Point the camera at some faces. Single tap
+ * the shutter to take a picture.
+ * (5) The camera has autofocus. Single tap the shutter to take a picture.
+ * (6) The camera has autofocus. Hold the shutter. Release to take a picture.
+ * (7) The camera has no autofocus. Single tap the shutter and take a picture.
+ * (8) The camera has autofocus and supports focus area. Touch the screen to
+ * trigger autofocus. Take a picture.
+ * (9) The camera has autofocus and supports focus area. Touch the screen to
+ * trigger autofocus. Wait until it times out.
+ * (10) The camera has no autofocus and supports metering area. Touch the screen
+ * to change metering area.
+ */
+public class FocusOverlayManager {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final String TRUE = "true";
+ private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
+ private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED =
+ "auto-whitebalance-lock-supported";
+
+ private static final int RESET_TOUCH_FOCUS = 0;
+ private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
+
+ private int mState = STATE_IDLE;
+ private static final int STATE_IDLE = 0; // Focus is not active.
+ private static final int STATE_FOCUSING = 1; // Focus is in progress.
+ // Focus is in progress and the camera should take a picture after focus finishes.
+ private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
+ private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
+ private static final int STATE_FAIL = 4; // Focus finishes and fails.
+
+ private boolean mInitialized;
+ private boolean mFocusAreaSupported;
+ private boolean mMeteringAreaSupported;
+ private boolean mLockAeAwbNeeded;
+ private boolean mAeAwbLock;
+ private Matrix mMatrix;
+
+ private PieRenderer mPieRenderer;
+
+ private int mPreviewWidth; // The width of the preview frame layout.
+ private int mPreviewHeight; // The height of the preview frame layout.
+ private boolean mMirror; // true if the camera is front-facing.
+ private int mDisplayOrientation;
+ private List<Object> mFocusArea; // focus area in driver format
+ private List<Object> mMeteringArea; // metering area in driver format
+ private String mFocusMode;
+ private String mOverrideFocusMode;
+ private Parameters mParameters;
+ private Handler mHandler;
+ Listener mListener;
+
+ public interface Listener {
+ public void autoFocus();
+ public void cancelAutoFocus();
+ public boolean capture();
+ public void setFocusParameters();
+ }
+
+ private class MainHandler extends Handler {
+ public MainHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case RESET_TOUCH_FOCUS: {
+ cancelAutoFocus();
+ break;
+ }
+ }
+ }
+ }
+
+ public FocusOverlayManager(Listener listener, Looper looper) {
+ mHandler = new MainHandler(looper);
+ mMatrix = new Matrix();
+ mListener = listener;
+ }
+
+ public void setFocusRenderer(PieRenderer renderer) {
+ mPieRenderer = renderer;
+ mInitialized = (mMatrix != null);
+ }
+
+ public void setParameters(Parameters parameters) {
+ // parameters can only be null when onConfigurationChanged is called
+ // before camera is open. We will just return in this case, because
+ // parameters will be set again later with the right parameters after
+ // camera is open.
+ if (parameters == null) {
+ return;
+ }
+ mParameters = parameters;
+ mFocusAreaSupported = isFocusAreaSupported(parameters);
+ mMeteringAreaSupported = isMeteringAreaSupported(parameters);
+ mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) ||
+ isAutoWhiteBalanceLockSupported(mParameters));
+ }
+
+ public void setPreviewSize(int previewWidth, int previewHeight) {
+ if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
+ mPreviewWidth = previewWidth;
+ mPreviewHeight = previewHeight;
+ setMatrix();
+ }
+ }
+
+ public void setMirror(boolean mirror) {
+ mMirror = mirror;
+ setMatrix();
+ }
+
+ public void setDisplayOrientation(int displayOrientation) {
+ mDisplayOrientation = displayOrientation;
+ setMatrix();
+ }
+
+ private void setMatrix() {
+ if (mPreviewWidth != 0 && mPreviewHeight != 0) {
+ Matrix matrix = new Matrix();
+ prepareMatrix(matrix, mMirror, mDisplayOrientation,
+ mPreviewWidth, mPreviewHeight);
+ // In face detection, the matrix converts the driver coordinates to UI
+ // coordinates. In tap focus, the inverted matrix converts the UI
+ // coordinates to driver coordinates.
+ matrix.invert(mMatrix);
+ mInitialized = (mPieRenderer != null);
+ }
+ }
+
+ private void lockAeAwbIfNeeded() {
+ if (mLockAeAwbNeeded && !mAeAwbLock) {
+ mAeAwbLock = true;
+ mListener.setFocusParameters();
+ }
+ }
+
+ private void unlockAeAwbIfNeeded() {
+ if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
+ mAeAwbLock = false;
+ mListener.setFocusParameters();
+ }
+ }
+
+ public void onShutterDown() {
+ if (!mInitialized) {
+ return;
+ }
+
+ boolean autoFocusCalled = false;
+ if (needAutoFocusCall()) {
+ // Do not focus if touch focus has been triggered.
+ if (mState != STATE_SUCCESS && mState != STATE_FAIL) {
+ autoFocus();
+ autoFocusCalled = true;
+ }
+ }
+
+ if (!autoFocusCalled) {
+ lockAeAwbIfNeeded();
+ }
+ }
+
+ public void onShutterUp() {
+ if (!mInitialized) {
+ return;
+ }
+
+ if (needAutoFocusCall()) {
+ // User releases half-pressed focus key.
+ if (mState == STATE_FOCUSING || mState == STATE_SUCCESS
+ || mState == STATE_FAIL) {
+ cancelAutoFocus();
+ }
+ }
+
+ // Unlock AE and AWB after cancelAutoFocus. Camera API does not
+ // guarantee setParameters can be called during autofocus.
+ unlockAeAwbIfNeeded();
+ }
+
+ public void doSnap() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // If the user has half-pressed the shutter and focus is completed, we
+ // can take the photo right away. If the focus mode is infinity, we can
+ // also take the photo.
+ if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
+ capture();
+ } else if (mState == STATE_FOCUSING) {
+ // Half pressing the shutter (i.e. the focus button event) will
+ // already have requested AF for us, so just request capture on
+ // focus here.
+ mState = STATE_FOCUSING_SNAP_ON_FINISH;
+ } else if (mState == STATE_IDLE) {
+ // We didn't do focus. This can happen if the user press focus key
+ // while the snapshot is still in progress. The user probably wants
+ // the next snapshot as soon as possible, so we just do a snapshot
+ // without focusing again.
+ capture();
+ }
+ }
+
+ public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
+ if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
+ // Take the picture no matter focus succeeds or fails. No need
+ // to play the AF sound if we're about to play the shutter
+ // sound.
+ if (focused) {
+ mState = STATE_SUCCESS;
+ } else {
+ mState = STATE_FAIL;
+ }
+ updateFocusUI();
+ capture();
+ } else if (mState == STATE_FOCUSING) {
+ // This happens when (1) user is half-pressing the focus key or
+ // (2) touch focus is triggered. Play the focus tone. Do not
+ // take the picture now.
+ if (focused) {
+ mState = STATE_SUCCESS;
+ } else {
+ mState = STATE_FAIL;
+ }
+ updateFocusUI();
+ // If this is triggered by touch focus, cancel focus after a
+ // while.
+ if (mFocusArea != null) {
+ mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
+ }
+ if (shutterButtonPressed) {
+ // Lock AE & AWB so users can half-press shutter and recompose.
+ lockAeAwbIfNeeded();
+ }
+ } else if (mState == STATE_IDLE) {
+ // User has released the focus key before focus completes.
+ // Do nothing.
+ }
+ }
+
+ public void onAutoFocusMoving(boolean moving) {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Ignore if we have requested autofocus. This method only handles
+ // continuous autofocus.
+ if (mState != STATE_IDLE) {
+ return;
+ }
+
+ if (moving) {
+ mPieRenderer.showStart();
+ } else {
+ mPieRenderer.showSuccess(true);
+ }
+ }
+
+ private void initializeFocusAreas(int focusWidth, int focusHeight,
+ int x, int y, int previewWidth, int previewHeight) {
+ if (mFocusArea == null) {
+ mFocusArea = new ArrayList<Object>();
+ mFocusArea.add(new Area(new Rect(), 1));
+ }
+
+ // Convert the coordinates to driver format.
+ calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
+ ((Area) mFocusArea.get(0)).rect);
+ }
+
+ private void initializeMeteringAreas(int focusWidth, int focusHeight,
+ int x, int y, int previewWidth, int previewHeight) {
+ if (mMeteringArea == null) {
+ mMeteringArea = new ArrayList<Object>();
+ mMeteringArea.add(new Area(new Rect(), 1));
+ }
+
+ // Convert the coordinates to driver format.
+ // AE area is bigger because exposure is sensitive and
+ // easy to over- or underexposure if area is too small.
+ calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
+ ((Area) mMeteringArea.get(0)).rect);
+ }
+
+ public void onSingleTapUp(int x, int y) {
+ if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
+ return;
+ }
+
+ // Let users be able to cancel previous touch focus.
+ if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
+ mState == STATE_SUCCESS || mState == STATE_FAIL)) {
+ cancelAutoFocus();
+ }
+ // Initialize variables.
+ int focusWidth = mPieRenderer.getSize();
+ int focusHeight = mPieRenderer.getSize();
+ if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) {
+ return;
+ }
+ int previewWidth = mPreviewWidth;
+ int previewHeight = mPreviewHeight;
+ // Initialize mFocusArea.
+ if (mFocusAreaSupported) {
+ initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
+ }
+ // Initialize mMeteringArea.
+ if (mMeteringAreaSupported) {
+ initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
+ }
+
+ // Use margin to set the focus indicator to the touched area.
+ mPieRenderer.setFocus(x, y);
+
+ // Set the focus area and metering area.
+ mListener.setFocusParameters();
+ if (mFocusAreaSupported) {
+ autoFocus();
+ } else { // Just show the indicator in all other cases.
+ updateFocusUI();
+ // Reset the metering area in 3 seconds.
+ mHandler.removeMessages(RESET_TOUCH_FOCUS);
+ mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
+ }
+ }
+
+ public void onPreviewStarted() {
+ mState = STATE_IDLE;
+ }
+
+ public void onPreviewStopped() {
+ // If auto focus was in progress, it would have been stopped.
+ mState = STATE_IDLE;
+ resetTouchFocus();
+ updateFocusUI();
+ }
+
+ public void onCameraReleased() {
+ onPreviewStopped();
+ }
+
+ private void autoFocus() {
+ LogUtil.v(TAG, "Start autofocus.");
+ mListener.autoFocus();
+ mState = STATE_FOCUSING;
+ updateFocusUI();
+ mHandler.removeMessages(RESET_TOUCH_FOCUS);
+ }
+
+ private void cancelAutoFocus() {
+ LogUtil.v(TAG, "Cancel autofocus.");
+
+ // Reset the tap area before calling mListener.cancelAutofocus.
+ // Otherwise, focus mode stays at auto and the tap area passed to the
+ // driver is not reset.
+ resetTouchFocus();
+ mListener.cancelAutoFocus();
+ mState = STATE_IDLE;
+ updateFocusUI();
+ mHandler.removeMessages(RESET_TOUCH_FOCUS);
+ }
+
+ private void capture() {
+ if (mListener.capture()) {
+ mState = STATE_IDLE;
+ mHandler.removeMessages(RESET_TOUCH_FOCUS);
+ }
+ }
+
+ public String getFocusMode() {
+ if (mOverrideFocusMode != null) {
+ return mOverrideFocusMode;
+ }
+ List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
+
+ if (mFocusAreaSupported && mFocusArea != null) {
+ // Always use autofocus in tap-to-focus.
+ mFocusMode = Parameters.FOCUS_MODE_AUTO;
+ } else {
+ mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
+ }
+
+ if (!isSupported(mFocusMode, supportedFocusModes)) {
+ // For some reasons, the driver does not support the current
+ // focus mode. Fall back to auto.
+ if (isSupported(Parameters.FOCUS_MODE_AUTO,
+ mParameters.getSupportedFocusModes())) {
+ mFocusMode = Parameters.FOCUS_MODE_AUTO;
+ } else {
+ mFocusMode = mParameters.getFocusMode();
+ }
+ }
+ return mFocusMode;
+ }
+
+ public List getFocusAreas() {
+ return mFocusArea;
+ }
+
+ public List getMeteringAreas() {
+ return mMeteringArea;
+ }
+
+ public void updateFocusUI() {
+ if (!mInitialized) {
+ return;
+ }
+ FocusIndicator focusIndicator = mPieRenderer;
+
+ if (mState == STATE_IDLE) {
+ if (mFocusArea == null) {
+ focusIndicator.clear();
+ } else {
+ // Users touch on the preview and the indicator represents the
+ // metering area. Either focus area is not supported or
+ // autoFocus call is not required.
+ focusIndicator.showStart();
+ }
+ } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
+ focusIndicator.showStart();
+ } else {
+ if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
+ // TODO: check HAL behavior and decide if this can be removed.
+ focusIndicator.showSuccess(false);
+ } else if (mState == STATE_SUCCESS) {
+ focusIndicator.showSuccess(false);
+ } else if (mState == STATE_FAIL) {
+ focusIndicator.showFail(false);
+ }
+ }
+ }
+
+ public void resetTouchFocus() {
+ if (!mInitialized) {
+ return;
+ }
+
+ // Put focus indicator to the center. clear reset position
+ mPieRenderer.clear();
+
+ mFocusArea = null;
+ mMeteringArea = null;
+ }
+
+ private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,
+ int x, int y, int previewWidth, int previewHeight, Rect rect) {
+ int areaWidth = (int) (focusWidth * areaMultiple);
+ int areaHeight = (int) (focusHeight * areaMultiple);
+ int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
+ int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);
+
+ RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
+ mMatrix.mapRect(rectF);
+ rectFToRect(rectF, rect);
+ }
+
+ /* package */ int getFocusState() {
+ return mState;
+ }
+
+ public boolean isFocusCompleted() {
+ return mState == STATE_SUCCESS || mState == STATE_FAIL;
+ }
+
+ public boolean isFocusingSnapOnFinish() {
+ return mState == STATE_FOCUSING_SNAP_ON_FINISH;
+ }
+
+ public void removeMessages() {
+ mHandler.removeMessages(RESET_TOUCH_FOCUS);
+ }
+
+ public void overrideFocusMode(String focusMode) {
+ mOverrideFocusMode = focusMode;
+ }
+
+ public void setAeAwbLock(boolean lock) {
+ mAeAwbLock = lock;
+ }
+
+ public boolean getAeAwbLock() {
+ return mAeAwbLock;
+ }
+
+ private boolean needAutoFocusCall() {
+ String focusMode = getFocusMode();
+ return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY)
+ || focusMode.equals(Parameters.FOCUS_MODE_FIXED)
+ || focusMode.equals(Parameters.FOCUS_MODE_EDOF));
+ }
+
+ public static boolean isAutoExposureLockSupported(Parameters params) {
+ return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
+ }
+
+ public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
+ return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
+ }
+
+ public static boolean isSupported(String value, List<String> supported) {
+ return supported != null && supported.indexOf(value) >= 0;
+ }
+
+ public static boolean isMeteringAreaSupported(Parameters params) {
+ return params.getMaxNumMeteringAreas() > 0;
+ }
+
+ public static boolean isFocusAreaSupported(Parameters params) {
+ return (params.getMaxNumFocusAreas() > 0
+ && isSupported(Parameters.FOCUS_MODE_AUTO,
+ params.getSupportedFocusModes()));
+ }
+
+ public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
+ int viewWidth, int viewHeight) {
+ // Need mirror for front camera.
+ matrix.setScale(mirror ? -1 : 1, 1);
+ // This is the value for android.hardware.Camera.setDisplayOrientation.
+ matrix.postRotate(displayOrientation);
+ // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
+ // UI coordinates range from (0, 0) to (width, height).
+ matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
+ matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
+ }
+
+ public static int clamp(int x, int min, int max) {
+ Assert.isTrue(max >= min);
+ if (x > max) {
+ return max;
+ }
+ if (x < min) {
+ return min;
+ }
+ return x;
+ }
+
+ public static void rectFToRect(RectF rectF, Rect rect) {
+ rect.left = Math.round(rectF.left);
+ rect.top = Math.round(rectF.top);
+ rect.right = Math.round(rectF.right);
+ rect.bottom = Math.round(rectF.bottom);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java
new file mode 100644
index 0000000..df6734f
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java
@@ -0,0 +1,95 @@
+/*
+ * 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.camerafocus;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+
+public abstract class OverlayRenderer implements RenderOverlay.Renderer {
+
+ private static final String TAG = "CAM OverlayRenderer";
+ protected RenderOverlay mOverlay;
+
+ protected int mLeft, mTop, mRight, mBottom;
+
+ protected boolean mVisible;
+
+ public void setVisible(boolean vis) {
+ mVisible = vis;
+ update();
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ // default does not handle touch
+ @Override
+ public boolean handlesTouch() {
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ return false;
+ }
+
+ public abstract void onDraw(Canvas canvas);
+
+ public void draw(Canvas canvas) {
+ if (mVisible) {
+ onDraw(canvas);
+ }
+ }
+
+ @Override
+ public void setOverlay(RenderOverlay overlay) {
+ mOverlay = overlay;
+ }
+
+ @Override
+ public void layout(int left, int top, int right, int bottom) {
+ mLeft = left;
+ mRight = right;
+ mTop = top;
+ mBottom = bottom;
+ }
+
+ protected Context getContext() {
+ if (mOverlay != null) {
+ return mOverlay.getContext();
+ } else {
+ return null;
+ }
+ }
+
+ public int getWidth() {
+ return mRight - mLeft;
+ }
+
+ public int getHeight() {
+ return mBottom - mTop;
+ }
+
+ protected void update() {
+ if (mOverlay != null) {
+ mOverlay.update();
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java
new file mode 100644
index 0000000..c602852
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java
@@ -0,0 +1,202 @@
+/*
+ * 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.camerafocus;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pie menu item
+ */
+public class PieItem {
+
+ public static interface OnClickListener {
+ void onClick(PieItem item);
+ }
+
+ private Drawable mDrawable;
+ private int level;
+ private float mCenter;
+ private float start;
+ private float sweep;
+ private float animate;
+ private int inner;
+ private int outer;
+ private boolean mSelected;
+ private boolean mEnabled;
+ private List<PieItem> mItems;
+ private Path mPath;
+ private OnClickListener mOnClickListener;
+ private float mAlpha;
+
+ // Gray out the view when disabled
+ private static final float ENABLED_ALPHA = 1;
+ private static final float DISABLED_ALPHA = (float) 0.3;
+ private boolean mChangeAlphaWhenDisabled = true;
+
+ public PieItem(Drawable drawable, int level) {
+ mDrawable = drawable;
+ this.level = level;
+ setAlpha(1f);
+ mEnabled = true;
+ setAnimationAngle(getAnimationAngle());
+ start = -1;
+ mCenter = -1;
+ }
+
+ public boolean hasItems() {
+ return mItems != null;
+ }
+
+ public List<PieItem> getItems() {
+ return mItems;
+ }
+
+ public void addItem(PieItem item) {
+ if (mItems == null) {
+ mItems = new ArrayList<PieItem>();
+ }
+ mItems.add(item);
+ }
+
+ public void setPath(Path p) {
+ mPath = p;
+ }
+
+ public Path getPath() {
+ return mPath;
+ }
+
+ public void setChangeAlphaWhenDisabled (boolean enable) {
+ mChangeAlphaWhenDisabled = enable;
+ }
+
+ public void setAlpha(float alpha) {
+ mAlpha = alpha;
+ mDrawable.setAlpha((int) (255 * alpha));
+ }
+
+ public void setAnimationAngle(float a) {
+ animate = a;
+ }
+
+ public float getAnimationAngle() {
+ return animate;
+ }
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ if (mChangeAlphaWhenDisabled) {
+ if (mEnabled) {
+ setAlpha(ENABLED_ALPHA);
+ } else {
+ setAlpha(DISABLED_ALPHA);
+ }
+ }
+ }
+
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ public void setSelected(boolean s) {
+ mSelected = s;
+ }
+
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setGeometry(float st, float sw, int inside, int outside) {
+ start = st;
+ sweep = sw;
+ inner = inside;
+ outer = outside;
+ }
+
+ public void setFixedSlice(float center, float sweep) {
+ mCenter = center;
+ this.sweep = sweep;
+ }
+
+ public float getCenter() {
+ return mCenter;
+ }
+
+ public float getStart() {
+ return start;
+ }
+
+ public float getStartAngle() {
+ return start + animate;
+ }
+
+ public float getSweep() {
+ return sweep;
+ }
+
+ public int getInnerRadius() {
+ return inner;
+ }
+
+ public int getOuterRadius() {
+ return outer;
+ }
+
+ public void setOnClickListener(OnClickListener listener) {
+ mOnClickListener = listener;
+ }
+
+ public void performClick() {
+ if (mOnClickListener != null) {
+ mOnClickListener.onClick(this);
+ }
+ }
+
+ public int getIntrinsicWidth() {
+ return mDrawable.getIntrinsicWidth();
+ }
+
+ public int getIntrinsicHeight() {
+ return mDrawable.getIntrinsicHeight();
+ }
+
+ public void setBounds(int left, int top, int right, int bottom) {
+ mDrawable.setBounds(left, top, right, bottom);
+ }
+
+ public void draw(Canvas canvas) {
+ mDrawable.draw(canvas);
+ }
+
+ public void setImageResource(Context context, int resId) {
+ Drawable d = context.getResources().getDrawable(resId).mutate();
+ d.setBounds(mDrawable.getBounds());
+ mDrawable = d;
+ setAlpha(mAlpha);
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java
new file mode 100644
index 0000000..ce8ca00
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java
@@ -0,0 +1,825 @@
+/*
+ * 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.camerafocus;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.Message;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+import com.android.messaging.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PieRenderer extends OverlayRenderer
+ implements FocusIndicator {
+ // Sometimes continuous autofocus starts and stops several times quickly.
+ // These states are used to make sure the animation is run for at least some
+ // time.
+ private volatile int mState;
+ private ScaleAnimation mAnimation = new ScaleAnimation();
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_FOCUSING = 1;
+ private static final int STATE_FINISHING = 2;
+ private static final int STATE_PIE = 8;
+
+ private Runnable mDisappear = new Disappear();
+ private Animation.AnimationListener mEndAction = new EndAction();
+ private static final int SCALING_UP_TIME = 600;
+ private static final int SCALING_DOWN_TIME = 100;
+ private static final int DISAPPEAR_TIMEOUT = 200;
+ private static final int DIAL_HORIZONTAL = 157;
+
+ private static final long PIE_FADE_IN_DURATION = 200;
+ private static final long PIE_XFADE_DURATION = 200;
+ private static final long PIE_SELECT_FADE_DURATION = 300;
+
+ private static final int MSG_OPEN = 0;
+ private static final int MSG_CLOSE = 1;
+ private static final float PIE_SWEEP = (float) (Math.PI * 2 / 3);
+ // geometry
+ private Point mCenter;
+ private int mRadius;
+ private int mRadiusInc;
+
+ // the detection if touch is inside a slice is offset
+ // inbounds by this amount to allow the selection to show before the
+ // finger covers it
+ private int mTouchOffset;
+
+ private List<PieItem> mItems;
+
+ private PieItem mOpenItem;
+
+ private Paint mSelectedPaint;
+ private Paint mSubPaint;
+
+ // touch handling
+ private PieItem mCurrentItem;
+
+ private Paint mFocusPaint;
+ private int mSuccessColor;
+ private int mFailColor;
+ private int mCircleSize;
+ private int mFocusX;
+ private int mFocusY;
+ private int mCenterX;
+ private int mCenterY;
+
+ private int mDialAngle;
+ private RectF mCircle;
+ private RectF mDial;
+ private Point mPoint1;
+ private Point mPoint2;
+ private int mStartAnimationAngle;
+ private boolean mFocused;
+ private int mInnerOffset;
+ private int mOuterStroke;
+ private int mInnerStroke;
+ private boolean mTapMode;
+ private boolean mBlockFocus;
+ private int mTouchSlopSquared;
+ private Point mDown;
+ private boolean mOpening;
+ private LinearAnimation mXFade;
+ private LinearAnimation mFadeIn;
+ private volatile boolean mFocusCancelled;
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_OPEN:
+ if (mListener != null) {
+ mListener.onPieOpened(mCenter.x, mCenter.y);
+ }
+ break;
+ case MSG_CLOSE:
+ if (mListener != null) {
+ mListener.onPieClosed();
+ }
+ break;
+ }
+ }
+ };
+
+ private PieListener mListener;
+
+ public static interface PieListener {
+ public void onPieOpened(int centerX, int centerY);
+ public void onPieClosed();
+ }
+
+ public void setPieListener(PieListener pl) {
+ mListener = pl;
+ }
+
+ public PieRenderer(Context context) {
+ init(context);
+ }
+
+ private void init(Context ctx) {
+ setVisible(false);
+ mItems = new ArrayList<PieItem>();
+ Resources res = ctx.getResources();
+ mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
+ mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
+ mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
+ mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
+ mCenter = new Point(0, 0);
+ mSelectedPaint = new Paint();
+ mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
+ mSelectedPaint.setAntiAlias(true);
+ mSubPaint = new Paint();
+ mSubPaint.setAntiAlias(true);
+ mSubPaint.setColor(Color.argb(200, 250, 230, 128));
+ mFocusPaint = new Paint();
+ mFocusPaint.setAntiAlias(true);
+ mFocusPaint.setColor(Color.WHITE);
+ mFocusPaint.setStyle(Paint.Style.STROKE);
+ mSuccessColor = Color.GREEN;
+ mFailColor = Color.RED;
+ mCircle = new RectF();
+ mDial = new RectF();
+ mPoint1 = new Point();
+ mPoint2 = new Point();
+ mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
+ mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
+ mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
+ mState = STATE_IDLE;
+ mBlockFocus = false;
+ mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
+ mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
+ mDown = new Point();
+ }
+
+ public boolean showsItems() {
+ return mTapMode;
+ }
+
+ public void addItem(PieItem item) {
+ // add the item to the pie itself
+ mItems.add(item);
+ }
+
+ public void removeItem(PieItem item) {
+ mItems.remove(item);
+ }
+
+ public void clearItems() {
+ mItems.clear();
+ }
+
+ public void showInCenter() {
+ if ((mState == STATE_PIE) && isVisible()) {
+ mTapMode = false;
+ show(false);
+ } else {
+ if (mState != STATE_IDLE) {
+ cancelFocus();
+ }
+ mState = STATE_PIE;
+ setCenter(mCenterX, mCenterY);
+ mTapMode = true;
+ show(true);
+ }
+ }
+
+ public void hide() {
+ show(false);
+ }
+
+ /**
+ * guaranteed has center set
+ * @param show
+ */
+ private void show(boolean show) {
+ if (show) {
+ mState = STATE_PIE;
+ // ensure clean state
+ mCurrentItem = null;
+ mOpenItem = null;
+ for (PieItem item : mItems) {
+ item.setSelected(false);
+ }
+ layoutPie();
+ fadeIn();
+ } else {
+ mState = STATE_IDLE;
+ mTapMode = false;
+ if (mXFade != null) {
+ mXFade.cancel();
+ }
+ }
+ setVisible(show);
+ mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
+ }
+
+ private void fadeIn() {
+ mFadeIn = new LinearAnimation(0, 1);
+ mFadeIn.setDuration(PIE_FADE_IN_DURATION);
+ mFadeIn.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mFadeIn = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ mFadeIn.startNow();
+ mOverlay.startAnimation(mFadeIn);
+ }
+
+ public void setCenter(int x, int y) {
+ mCenter.x = x;
+ mCenter.y = y;
+ // when using the pie menu, align the focus ring
+ alignFocus(x, y);
+ }
+
+ private void layoutPie() {
+ int rgap = 2;
+ int inner = mRadius + rgap;
+ int outer = mRadius + mRadiusInc - rgap;
+ int gap = 1;
+ layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap);
+ }
+
+ private void layoutItems(List<PieItem> items, float centerAngle, int inner,
+ int outer, int gap) {
+ float emptyangle = PIE_SWEEP / 16;
+ float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size();
+ float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2;
+ // check if we have custom geometry
+ // first item we find triggers custom sweep for all
+ // this allows us to re-use the path
+ for (PieItem item : items) {
+ if (item.getCenter() >= 0) {
+ sweep = item.getSweep();
+ break;
+ }
+ }
+ Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap,
+ outer, inner, mCenter);
+ for (PieItem item : items) {
+ // shared between items
+ item.setPath(path);
+ if (item.getCenter() >= 0) {
+ angle = item.getCenter();
+ }
+ int w = item.getIntrinsicWidth();
+ int h = item.getIntrinsicHeight();
+ // move views to outer border
+ int r = inner + (outer - inner) * 2 / 3;
+ int x = (int) (r * Math.cos(angle));
+ int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2;
+ x = mCenter.x + x - w / 2;
+ item.setBounds(x, y, x + w, y + h);
+ float itemstart = angle - sweep / 2;
+ item.setGeometry(itemstart, sweep, inner, outer);
+ if (item.hasItems()) {
+ layoutItems(item.getItems(), angle, inner,
+ outer + mRadiusInc / 2, gap);
+ }
+ angle += sweep;
+ }
+ }
+
+ private Path makeSlice(float start, float end, int outer, int inner, Point center) {
+ RectF bb =
+ new RectF(center.x - outer, center.y - outer, center.x + outer,
+ center.y + outer);
+ RectF bbi =
+ new RectF(center.x - inner, center.y - inner, center.x + inner,
+ center.y + inner);
+ Path path = new Path();
+ path.arcTo(bb, start, end - start, true);
+ path.arcTo(bbi, end, start - end);
+ path.close();
+ return path;
+ }
+
+ /**
+ * converts a
+ * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
+ * @return skia angle
+ */
+ private float getDegrees(double angle) {
+ return (float) (360 - 180 * angle / Math.PI);
+ }
+
+ private void startFadeOut() {
+ mOverlay.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ deselect();
+ show(false);
+ mOverlay.setAlpha(1);
+ super.onAnimationEnd(animation);
+ }
+ }).setDuration(PIE_SELECT_FADE_DURATION);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ float alpha = 1;
+ if (mXFade != null) {
+ alpha = mXFade.getValue();
+ } else if (mFadeIn != null) {
+ alpha = mFadeIn.getValue();
+ }
+ int state = canvas.save();
+ if (mFadeIn != null) {
+ float sf = 0.9f + alpha * 0.1f;
+ canvas.scale(sf, sf, mCenter.x, mCenter.y);
+ }
+ drawFocus(canvas);
+ if (mState == STATE_FINISHING) {
+ canvas.restoreToCount(state);
+ return;
+ }
+ if ((mOpenItem == null) || (mXFade != null)) {
+ // draw base menu
+ for (PieItem item : mItems) {
+ drawItem(canvas, item, alpha);
+ }
+ }
+ if (mOpenItem != null) {
+ for (PieItem inner : mOpenItem.getItems()) {
+ drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
+ }
+ }
+ canvas.restoreToCount(state);
+ }
+
+ private void drawItem(Canvas canvas, PieItem item, float alpha) {
+ if (mState == STATE_PIE) {
+ if (item.getPath() != null) {
+ if (item.isSelected()) {
+ Paint p = mSelectedPaint;
+ int state = canvas.save();
+ float r = getDegrees(item.getStartAngle());
+ canvas.rotate(r, mCenter.x, mCenter.y);
+ canvas.drawPath(item.getPath(), p);
+ canvas.restoreToCount(state);
+ }
+ alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
+ // draw the item view
+ item.setAlpha(alpha);
+ item.draw(canvas);
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ float x = evt.getX();
+ float y = evt.getY();
+ int action = evt.getActionMasked();
+ PointF polar = getPolar(x, y, !(mTapMode));
+ if (MotionEvent.ACTION_DOWN == action) {
+ mDown.x = (int) evt.getX();
+ mDown.y = (int) evt.getY();
+ mOpening = false;
+ if (mTapMode) {
+ PieItem item = findItem(polar);
+ if ((item != null) && (mCurrentItem != item)) {
+ mState = STATE_PIE;
+ onEnter(item);
+ }
+ } else {
+ setCenter((int) x, (int) y);
+ show(true);
+ }
+ return true;
+ } else if (MotionEvent.ACTION_UP == action) {
+ if (isVisible()) {
+ PieItem item = mCurrentItem;
+ if (mTapMode) {
+ item = findItem(polar);
+ if (item != null && mOpening) {
+ mOpening = false;
+ return true;
+ }
+ }
+ if (item == null) {
+ mTapMode = false;
+ show(false);
+ } else if (!mOpening
+ && !item.hasItems()) {
+ item.performClick();
+ startFadeOut();
+ mTapMode = false;
+ }
+ return true;
+ }
+ } else if (MotionEvent.ACTION_CANCEL == action) {
+ if (isVisible() || mTapMode) {
+ show(false);
+ }
+ deselect();
+ return false;
+ } else if (MotionEvent.ACTION_MOVE == action) {
+ if (polar.y < mRadius) {
+ if (mOpenItem != null) {
+ mOpenItem = null;
+ } else {
+ deselect();
+ }
+ return false;
+ }
+ PieItem item = findItem(polar);
+ boolean moved = hasMoved(evt);
+ if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
+ // only select if we didn't just open or have moved past slop
+ mOpening = false;
+ if (moved) {
+ // switch back to swipe mode
+ mTapMode = false;
+ }
+ onEnter(item);
+ }
+ }
+ return false;
+ }
+
+ private boolean hasMoved(MotionEvent e) {
+ return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
+ + (e.getY() - mDown.y) * (e.getY() - mDown.y);
+ }
+
+ /**
+ * enter a slice for a view
+ * updates model only
+ * @param item
+ */
+ private void onEnter(PieItem item) {
+ if (mCurrentItem != null) {
+ mCurrentItem.setSelected(false);
+ }
+ if (item != null && item.isEnabled()) {
+ item.setSelected(true);
+ mCurrentItem = item;
+ if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) {
+ openCurrentItem();
+ }
+ } else {
+ mCurrentItem = null;
+ }
+ }
+
+ private void deselect() {
+ if (mCurrentItem != null) {
+ mCurrentItem.setSelected(false);
+ }
+ if (mOpenItem != null) {
+ mOpenItem = null;
+ }
+ mCurrentItem = null;
+ }
+
+ private void openCurrentItem() {
+ if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
+ mCurrentItem.setSelected(false);
+ mOpenItem = mCurrentItem;
+ mOpening = true;
+ mXFade = new LinearAnimation(1, 0);
+ mXFade.setDuration(PIE_XFADE_DURATION);
+ mXFade.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ mXFade = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ mXFade.startNow();
+ mOverlay.startAnimation(mXFade);
+ }
+ }
+
+ private PointF getPolar(float x, float y, boolean useOffset) {
+ PointF res = new PointF();
+ // get angle and radius from x/y
+ res.x = (float) Math.PI / 2;
+ x = x - mCenter.x;
+ y = mCenter.y - y;
+ res.y = (float) Math.sqrt(x * x + y * y);
+ if (x != 0) {
+ res.x = (float) Math.atan2(y, x);
+ if (res.x < 0) {
+ res.x = (float) (2 * Math.PI + res.x);
+ }
+ }
+ res.y = res.y + (useOffset ? mTouchOffset : 0);
+ return res;
+ }
+
+ /**
+ * @param polar x: angle, y: dist
+ * @return the item at angle/dist or null
+ */
+ private PieItem findItem(PointF polar) {
+ // find the matching item:
+ List<PieItem> items = (mOpenItem != null) ? mOpenItem.getItems() : mItems;
+ for (PieItem item : items) {
+ if (inside(polar, item)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ private boolean inside(PointF polar, PieItem item) {
+ return (item.getInnerRadius() < polar.y)
+ && (item.getStartAngle() < polar.x)
+ && (item.getStartAngle() + item.getSweep() > polar.x)
+ && (!mTapMode || (item.getOuterRadius() > polar.y));
+ }
+
+ @Override
+ public boolean handlesTouch() {
+ return true;
+ }
+
+ // focus specific code
+
+ public void setBlockFocus(boolean blocked) {
+ mBlockFocus = blocked;
+ if (blocked) {
+ clear();
+ }
+ }
+
+ public void setFocus(int x, int y) {
+ mFocusX = x;
+ mFocusY = y;
+ setCircle(mFocusX, mFocusY);
+ }
+
+ public void alignFocus(int x, int y) {
+ mOverlay.removeCallbacks(mDisappear);
+ mAnimation.cancel();
+ mAnimation.reset();
+ mFocusX = x;
+ mFocusY = y;
+ mDialAngle = DIAL_HORIZONTAL;
+ setCircle(x, y);
+ mFocused = false;
+ }
+
+ public int getSize() {
+ return 2 * mCircleSize;
+ }
+
+ private int getRandomRange() {
+ return (int) (-60 + 120 * Math.random());
+ }
+
+ @Override
+ public void layout(int l, int t, int r, int b) {
+ super.layout(l, t, r, b);
+ mCenterX = (r - l) / 2;
+ mCenterY = (b - t) / 2;
+ mFocusX = mCenterX;
+ mFocusY = mCenterY;
+ setCircle(mFocusX, mFocusY);
+ if (isVisible() && mState == STATE_PIE) {
+ setCenter(mCenterX, mCenterY);
+ layoutPie();
+ }
+ }
+
+ private void setCircle(int cx, int cy) {
+ mCircle.set(cx - mCircleSize, cy - mCircleSize,
+ cx + mCircleSize, cy + mCircleSize);
+ mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
+ cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
+ }
+
+ public void drawFocus(Canvas canvas) {
+ if (mBlockFocus) {
+ return;
+ }
+ mFocusPaint.setStrokeWidth(mOuterStroke);
+ canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
+ if (mState == STATE_PIE) {
+ return;
+ }
+ int color = mFocusPaint.getColor();
+ if (mState == STATE_FINISHING) {
+ mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
+ }
+ mFocusPaint.setStrokeWidth(mInnerStroke);
+ drawLine(canvas, mDialAngle, mFocusPaint);
+ drawLine(canvas, mDialAngle + 45, mFocusPaint);
+ drawLine(canvas, mDialAngle + 180, mFocusPaint);
+ drawLine(canvas, mDialAngle + 225, mFocusPaint);
+ canvas.save();
+ // rotate the arc instead of its offset to better use framework's shape caching
+ canvas.rotate(mDialAngle, mFocusX, mFocusY);
+ canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
+ canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
+ canvas.restore();
+ mFocusPaint.setColor(color);
+ }
+
+ private void drawLine(Canvas canvas, int angle, Paint p) {
+ convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
+ convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
+ canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
+ mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
+ }
+
+ private static void convertCart(int angle, int radius, Point out) {
+ double a = 2 * Math.PI * (angle % 360) / 360;
+ out.x = (int) (radius * Math.cos(a) + 0.5);
+ out.y = (int) (radius * Math.sin(a) + 0.5);
+ }
+
+ @Override
+ public void showStart() {
+ if (mState == STATE_PIE) {
+ return;
+ }
+ cancelFocus();
+ mStartAnimationAngle = 67;
+ int range = getRandomRange();
+ startAnimation(SCALING_UP_TIME,
+ false, mStartAnimationAngle, mStartAnimationAngle + range);
+ mState = STATE_FOCUSING;
+ }
+
+ @Override
+ public void showSuccess(boolean timeout) {
+ if (mState == STATE_FOCUSING) {
+ startAnimation(SCALING_DOWN_TIME,
+ timeout, mStartAnimationAngle);
+ mState = STATE_FINISHING;
+ mFocused = true;
+ }
+ }
+
+ @Override
+ public void showFail(boolean timeout) {
+ if (mState == STATE_FOCUSING) {
+ startAnimation(SCALING_DOWN_TIME,
+ timeout, mStartAnimationAngle);
+ mState = STATE_FINISHING;
+ mFocused = false;
+ }
+ }
+
+ private void cancelFocus() {
+ mFocusCancelled = true;
+ mOverlay.removeCallbacks(mDisappear);
+ if (mAnimation != null) {
+ mAnimation.cancel();
+ }
+ mFocusCancelled = false;
+ mFocused = false;
+ mState = STATE_IDLE;
+ }
+
+ @Override
+ public void clear() {
+ if (mState == STATE_PIE) {
+ return;
+ }
+ cancelFocus();
+ mOverlay.post(mDisappear);
+ }
+
+ private void startAnimation(long duration, boolean timeout,
+ float toScale) {
+ startAnimation(duration, timeout, mDialAngle,
+ toScale);
+ }
+
+ private void startAnimation(long duration, boolean timeout,
+ float fromScale, float toScale) {
+ setVisible(true);
+ mAnimation.reset();
+ mAnimation.setDuration(duration);
+ mAnimation.setScale(fromScale, toScale);
+ mAnimation.setAnimationListener(timeout ? mEndAction : null);
+ mOverlay.startAnimation(mAnimation);
+ update();
+ }
+
+ private class EndAction implements Animation.AnimationListener {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // Keep the focus indicator for some time.
+ if (!mFocusCancelled) {
+ mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+ }
+
+ private class Disappear implements Runnable {
+ @Override
+ public void run() {
+ if (mState == STATE_PIE) {
+ return;
+ }
+ setVisible(false);
+ mFocusX = mCenterX;
+ mFocusY = mCenterY;
+ mState = STATE_IDLE;
+ setCircle(mFocusX, mFocusY);
+ mFocused = false;
+ }
+ }
+
+ private class ScaleAnimation extends Animation {
+ private float mFrom = 1f;
+ private float mTo = 1f;
+
+ public ScaleAnimation() {
+ setFillAfter(true);
+ }
+
+ public void setScale(float from, float to) {
+ mFrom = from;
+ mTo = to;
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ mDialAngle = (int) (mFrom + (mTo - mFrom) * interpolatedTime);
+ }
+ }
+
+
+ private class LinearAnimation extends Animation {
+ private float mFrom;
+ private float mTo;
+ private float mValue;
+
+ public LinearAnimation(float from, float to) {
+ setFillAfter(true);
+ setInterpolator(new LinearInterpolator());
+ mFrom = from;
+ mTo = to;
+ }
+
+ public float getValue() {
+ return mValue;
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ mValue = (mFrom + (mTo - mFrom) * interpolatedTime);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt b/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt
new file mode 100644
index 0000000..ed4e783
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt
@@ -0,0 +1,3 @@
+The files in this package were copied from the android-4.4.4_r1 branch of ASOP from the folders
+com/android/camera/ and com/android/camera/ui from files with the same name. Some modifications
+have been made to remove unneeded features and adjust to our needs. \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java b/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java
new file mode 100644
index 0000000..95cddc4
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java
@@ -0,0 +1,178 @@
+/*
+ * 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.camerafocus;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RenderOverlay extends FrameLayout {
+
+ interface Renderer {
+
+ public boolean handlesTouch();
+ public boolean onTouchEvent(MotionEvent evt);
+ public void setOverlay(RenderOverlay overlay);
+ public void layout(int left, int top, int right, int bottom);
+ public void draw(Canvas canvas);
+
+ }
+
+ private RenderView mRenderView;
+ private List<Renderer> mClients;
+
+ // reverse list of touch clients
+ private List<Renderer> mTouchClients;
+ private int[] mPosition = new int[2];
+
+ public RenderOverlay(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mRenderView = new RenderView(context);
+ addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ mClients = new ArrayList<Renderer>(10);
+ mTouchClients = new ArrayList<Renderer>(10);
+ setWillNotDraw(false);
+
+ addRenderer(new PieRenderer(context));
+ }
+
+ public PieRenderer getPieRenderer() {
+ for (Renderer renderer : mClients) {
+ if (renderer instanceof PieRenderer) {
+ return (PieRenderer) renderer;
+ }
+ }
+ return null;
+ }
+
+ public void addRenderer(Renderer renderer) {
+ mClients.add(renderer);
+ renderer.setOverlay(this);
+ if (renderer.handlesTouch()) {
+ mTouchClients.add(0, renderer);
+ }
+ renderer.layout(getLeft(), getTop(), getRight(), getBottom());
+ }
+
+ public void addRenderer(int pos, Renderer renderer) {
+ mClients.add(pos, renderer);
+ renderer.setOverlay(this);
+ renderer.layout(getLeft(), getTop(), getRight(), getBottom());
+ }
+
+ public void remove(Renderer renderer) {
+ mClients.remove(renderer);
+ renderer.setOverlay(null);
+ }
+
+ public int getClientSize() {
+ return mClients.size();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent m) {
+ return false;
+ }
+
+ public boolean directDispatchTouch(MotionEvent m, Renderer target) {
+ mRenderView.setTouchTarget(target);
+ boolean res = super.dispatchTouchEvent(m);
+ mRenderView.setTouchTarget(null);
+ return res;
+ }
+
+ private void adjustPosition() {
+ getLocationInWindow(mPosition);
+ }
+
+ public int getWindowPositionX() {
+ return mPosition[0];
+ }
+
+ public int getWindowPositionY() {
+ return mPosition[1];
+ }
+
+ public void update() {
+ mRenderView.invalidate();
+ }
+
+ private class RenderView extends View {
+
+ private Renderer mTouchTarget;
+
+ public RenderView(Context context) {
+ super(context);
+ setWillNotDraw(false);
+ }
+
+ public void setTouchTarget(Renderer target) {
+ mTouchTarget = target;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent evt) {
+ if (mTouchTarget != null) {
+ return mTouchTarget.onTouchEvent(evt);
+ }
+ if (mTouchClients != null) {
+ boolean res = false;
+ for (Renderer client : mTouchClients) {
+ res |= client.onTouchEvent(evt);
+ }
+ return res;
+ }
+ return false;
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ adjustPosition();
+ super.onLayout(changed, left, top, right, bottom);
+ if (mClients == null) {
+ return;
+ }
+ for (Renderer renderer : mClients) {
+ renderer.layout(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mClients == null) {
+ return;
+ }
+ boolean redraw = false;
+ for (Renderer renderer : mClients) {
+ renderer.draw(canvas);
+ redraw = redraw || ((OverlayRenderer) renderer).isVisible();
+ }
+ if (redraw) {
+ invalidate();
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java
new file mode 100644
index 0000000..d139a38
--- /dev/null
+++ b/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java
@@ -0,0 +1,192 @@
+/*
+ * 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.photoviewer;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.rastermill.FrameSequenceDrawable;
+import android.support.v4.content.AsyncTaskLoader;
+
+import com.android.ex.photo.PhotoViewController;
+import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface;
+import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
+import com.android.messaging.datamodel.media.ImageRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageResource;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.util.ImageUtils;
+
+/**
+ * Loader for the bitmap of a photo.
+ */
+public class BuglePhotoBitmapLoader extends AsyncTaskLoader<BitmapResult>
+ implements PhotoBitmapLoaderInterface {
+ private String mPhotoUri;
+ private ImageResource mImageResource;
+ // The drawable that is currently "in use" and being presented to the user. This drawable
+ // should never exist without the image resource backing it.
+ private Drawable mDrawable;
+
+ public BuglePhotoBitmapLoader(Context context, String photoUri) {
+ super(context);
+ mPhotoUri = photoUri;
+ }
+
+ @Override
+ public void setPhotoUri(String photoUri) {
+ mPhotoUri = photoUri;
+ }
+
+ @Override
+ public BitmapResult loadInBackground() {
+ final BitmapResult result = new BitmapResult();
+ final Context context = getContext();
+ if (context != null && mPhotoUri != null) {
+ final ImageRequestDescriptor descriptor =
+ new UriImageRequestDescriptor(Uri.parse(mPhotoUri),
+ PhotoViewController.sMaxPhotoSize, PhotoViewController.sMaxPhotoSize,
+ true /* allowCompression */, false /* isStatic */,
+ false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
+ final MediaRequest<ImageResource> imageRequest =
+ descriptor.buildSyncMediaRequest(context);
+ final ImageResource imageResource =
+ MediaResourceManager.get().requestMediaResourceSync(imageRequest);
+ if (imageResource != null) {
+ setImageResource(imageResource);
+ result.status = BitmapResult.STATUS_SUCCESS;
+ result.drawable = mImageResource.getDrawable(context.getResources());
+ } else {
+ releaseImageResource();
+ result.status = BitmapResult.STATUS_EXCEPTION;
+ }
+ } else {
+ result.status = BitmapResult.STATUS_EXCEPTION;
+ }
+ return result;
+ }
+
+ /**
+ * Called when there is new data to deliver to the client. The super class will take care of
+ * delivering it; the implementation here just adds a little more logic.
+ */
+ @Override
+ public void deliverResult(BitmapResult result) {
+ final Drawable drawable = result != null ? result.drawable : null;
+ if (isReset()) {
+ // An async query came in while the loader is stopped. We don't need the result.
+ releaseDrawable(drawable);
+ return;
+ }
+
+ // We are now going to display this drawable so set to mDrawable
+ mDrawable = drawable;
+
+ if (isStarted()) {
+ // If the Loader is currently started, we can immediately deliver its results.
+ super.deliverResult(result);
+ }
+ }
+
+ /**
+ * Handles a request to start the Loader.
+ */
+ @Override
+ protected void onStartLoading() {
+ if (mDrawable != null) {
+ // If we currently have a result available, deliver it
+ // immediately.
+ final BitmapResult result = new BitmapResult();
+ result.status = BitmapResult.STATUS_SUCCESS;
+ result.drawable = mDrawable;
+ deliverResult(result);
+ }
+
+ if (takeContentChanged() || (mImageResource == null)) {
+ // If the data has changed since the last time it was loaded
+ // or is not currently available, start a load.
+ forceLoad();
+ }
+ }
+
+ /**
+ * Handles a request to stop the Loader.
+ */
+ @Override
+ protected void onStopLoading() {
+ // Attempt to cancel the current load task if possible.
+ cancelLoad();
+ }
+
+ /**
+ * Handles a request to cancel a load.
+ */
+ @Override
+ public void onCanceled(BitmapResult result) {
+ super.onCanceled(result);
+
+ // At this point we can release the resources associated with 'drawable' if needed.
+ if (result != null) {
+ releaseDrawable(result.drawable);
+ }
+ }
+
+ /**
+ * Handles a request to completely reset the Loader.
+ */
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ releaseImageResource();
+ }
+
+ private void releaseDrawable(Drawable drawable) {
+ if (drawable != null && drawable instanceof FrameSequenceDrawable
+ && !((FrameSequenceDrawable) drawable).isDestroyed()) {
+ ((FrameSequenceDrawable) drawable).destroy();
+ }
+
+ }
+
+ private void setImageResource(final ImageResource resource) {
+ if (mImageResource != resource) {
+ // Clear out any information for what is currently used
+ releaseImageResource();
+ mImageResource = resource;
+ // No need to add ref since a ref is already reserved as a result of
+ // requestMediaResourceSync.
+ }
+ }
+
+ private void releaseImageResource() {
+ // If we are getting rid of the imageResource backing the drawable, we must also
+ // destroy the drawable before releasing it.
+ releaseDrawable(mDrawable);
+ mDrawable = null;
+
+ if (mImageResource != null) {
+ mImageResource.release();
+ }
+ mImageResource = null;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java
new file mode 100644
index 0000000..52498c7
--- /dev/null
+++ b/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.photoviewer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.support.v4.app.FragmentManager;
+
+import com.android.ex.photo.adapters.PhotoPagerAdapter;
+import com.android.ex.photo.fragments.PhotoViewFragment;
+
+public class BuglePhotoPageAdapter extends PhotoPagerAdapter {
+
+ public BuglePhotoPageAdapter(Context context, FragmentManager fm, Cursor c, float maxScale,
+ boolean thumbsFullScreen) {
+ super(context, fm, c, maxScale, thumbsFullScreen);
+ }
+
+ @Override
+ protected PhotoViewFragment createPhotoViewFragment(Intent intent, int position,
+ boolean onlyShowSpinner) {
+ return BuglePhotoViewFragment.newInstance(intent, position, onlyShowSpinner);
+ }
+}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java
new file mode 100644
index 0000000..1924a96
--- /dev/null
+++ b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.photoviewer;
+
+import com.android.ex.photo.PhotoViewActivity;
+import com.android.ex.photo.PhotoViewController;
+
+/**
+ * Activity to display the conversation images in full-screen. Most of the customization is in
+ * {@link BuglePhotoViewController}.
+ */
+public class BuglePhotoViewActivity extends PhotoViewActivity {
+ @Override
+ public PhotoViewController createController() {
+ return new BuglePhotoViewController(this);
+ }
+}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java
new file mode 100644
index 0000000..eb39886
--- /dev/null
+++ b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java
@@ -0,0 +1,179 @@
+/*
+ * 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.photoviewer;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.content.Loader;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ShareActionProvider;
+import android.widget.Toast;
+
+import com.android.ex.photo.PhotoViewController;
+import com.android.ex.photo.adapters.PhotoPagerAdapter;
+import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.ConversationImagePartsView.PhotoViewQuery;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.ui.conversation.ConversationFragment;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Customizations for the photoviewer to display conversation images in full screen.
+ */
+public class BuglePhotoViewController extends PhotoViewController {
+ private ShareActionProvider mShareActionProvider;
+ private MenuItem mShareItem;
+ private MenuItem mSaveItem;
+
+ public BuglePhotoViewController(final ActivityInterface activity) {
+ super(activity);
+ }
+
+ @Override
+ public Loader<BitmapResult> onCreateBitmapLoader(
+ final int id, final Bundle args, final String uri) {
+ switch (id) {
+ case BITMAP_LOADER_AVATAR:
+ case BITMAP_LOADER_THUMBNAIL:
+ case BITMAP_LOADER_PHOTO:
+ return new BuglePhotoBitmapLoader(getActivity().getContext(), uri);
+ default:
+ LogUtil.e(LogUtil.BUGLE_TAG,
+ "Photoviewer unable to open bitmap loader with unknown id: " + id);
+ return null;
+ }
+ }
+
+ @Override
+ public void updateActionBar() {
+ final Cursor cursor = getCursorAtProperPosition();
+
+ if (mSaveItem == null || cursor == null) {
+ // Load not finished, called from framework code before ready
+ return;
+ }
+ // Show the name as the title
+ mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_SENDER_FULL_NAME);
+ if (TextUtils.isEmpty(mActionBarTitle)) {
+ // If the name is not known, fall back to the phone number
+ mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_DISPLAY_DESTINATION);
+ }
+
+ // Show the timestamp as the subtitle
+ final long receivedTimestamp = cursor.getLong(PhotoViewQuery.INDEX_RECEIVED_TIMESTAMP);
+ mActionBarSubtitle = Dates.getMessageTimeString(receivedTimestamp).toString();
+
+ setActionBarTitles(getActivity().getActionBarInterface());
+ mSaveItem.setVisible(!isTempFile());
+
+ updateShareActionProvider();
+ }
+
+ private void updateShareActionProvider() {
+ final PhotoPagerAdapter adapter = getAdapter();
+ final Cursor cursor = getCursorAtProperPosition();
+ if (mShareActionProvider == null || mShareItem == null || adapter == null ||
+ cursor == null) {
+ // Not enough stuff loaded to update the share action
+ return;
+ }
+ final String photoUri = adapter.getPhotoUri(cursor);
+ if (isTempFile()) {
+ mShareItem.setVisible(false);
+ return;
+ }
+ final String contentType = adapter.getContentType(cursor);
+
+ final Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.setType(contentType);
+ shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(photoUri));
+ mShareActionProvider.setShareIntent(shareIntent);
+ mShareItem.setVisible(true);
+ }
+
+ /**
+ * Checks whether the current photo is a temp file. A temp file can be deleted at any time, so
+ * we need to disable share and save options because the file may no longer be there.
+ */
+ private boolean isTempFile() {
+ final Cursor cursor = getCursorAtProperPosition();
+ final Uri photoUri = Uri.parse(getAdapter().getPhotoUri(cursor));
+ return MediaScratchFileProvider.isMediaScratchSpaceUri(photoUri);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ ((Activity) getActivity()).getMenuInflater().inflate(R.menu.photo_view_menu, menu);
+
+ // Get the ShareActionProvider
+ mShareItem = menu.findItem(R.id.action_share);
+ mShareActionProvider = (ShareActionProvider) mShareItem.getActionProvider();
+ updateShareActionProvider();
+
+ mSaveItem = menu.findItem(R.id.action_save);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(final Menu menu) {
+ return !mIsEmpty;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.action_save) {
+ if (OsUtil.hasStoragePermission()) {
+ final PhotoPagerAdapter adapter = getAdapter();
+ final Cursor cursor = getCursorAtProperPosition();
+ if (cursor == null) {
+ final Context context = getActivity().getContext();
+ final String error = context.getResources().getQuantityString(
+ R.plurals.attachment_save_error, 1, 1);
+ Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ final String photoUri = adapter.getPhotoUri(cursor);
+ new ConversationFragment.SaveAttachmentTask(((Activity) getActivity()),
+ Uri.parse(photoUri), adapter.getContentType(cursor)).executeOnThreadPool();
+ } else {
+ ((Activity)getActivity()).requestPermissions(
+ new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
+ }
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public PhotoPagerAdapter createPhotoPagerAdapter(final Context context,
+ final FragmentManager fm, final Cursor c, final float maxScale) {
+ return new BuglePhotoPageAdapter(context, fm, c, maxScale, mDisplayThumbsFullScreen);
+ }
+}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java
new file mode 100644
index 0000000..698c510
--- /dev/null
+++ b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java
@@ -0,0 +1,89 @@
+/*
+ * 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.photoviewer;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.support.rastermill.FrameSequenceDrawable;
+import android.support.v4.content.Loader;
+
+import com.android.ex.photo.PhotoViewCallbacks;
+import com.android.ex.photo.fragments.PhotoViewFragment;
+import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
+
+public class BuglePhotoViewFragment extends PhotoViewFragment {
+
+ /** Public no-arg constructor for allowing the framework to handle orientation changes */
+ public BuglePhotoViewFragment() {
+ // Do nothing.
+ }
+
+ public static PhotoViewFragment newInstance(Intent intent, int position,
+ boolean onlyShowSpinner) {
+ final PhotoViewFragment f = new BuglePhotoViewFragment();
+ initializeArguments(intent, position, onlyShowSpinner, f);
+ return f;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<BitmapResult> loader, BitmapResult result) {
+ super.onLoadFinished(loader, result);
+ // Need to check for the first time when we load the photos
+ if (PhotoViewCallbacks.BITMAP_LOADER_PHOTO == loader.getId()
+ && result.status == BitmapResult.STATUS_SUCCESS
+ && mCallback.isFragmentActive(this)) {
+ startGif();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ startGif();
+ }
+
+ @Override
+ public void onPause() {
+ stopGif();
+ super.onPause();
+ }
+
+ @Override
+ public void onViewActivated() {
+ super.onViewActivated();
+ startGif();
+ }
+
+ @Override
+ public void resetViews() {
+ super.resetViews();
+ stopGif();
+ }
+
+ private void stopGif() {
+ final Drawable drawable = getDrawable();
+ if (drawable != null && drawable instanceof FrameSequenceDrawable) {
+ ((FrameSequenceDrawable) drawable).stop();
+ }
+ }
+
+ private void startGif() {
+ final Drawable drawable = getDrawable();
+ if (drawable != null && drawable instanceof FrameSequenceDrawable) {
+ ((FrameSequenceDrawable) drawable).start();
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/AccessibilityUtil.java b/src/com/android/messaging/util/AccessibilityUtil.java
new file mode 100644
index 0000000..f6c64a9
--- /dev/null
+++ b/src/com/android/messaging/util/AccessibilityUtil.java
@@ -0,0 +1,170 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+
+import javax.annotation.Nullable;
+
+public class AccessibilityUtil {
+ public static String sContentDescriptionDivider;
+
+ public static boolean isTouchExplorationEnabled(final Context context) {
+ final AccessibilityManager accessibilityManager = (AccessibilityManager)
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ return accessibilityManager.isTouchExplorationEnabled();
+ }
+
+ public static StringBuilder appendContentDescription(final Context context,
+ final StringBuilder contentDescription, final String val) {
+ if (sContentDescriptionDivider == null) {
+ sContentDescriptionDivider =
+ context.getResources().getString(R.string.enumeration_comma);
+ }
+ if (contentDescription.length() != 0) {
+ contentDescription.append(sContentDescriptionDivider);
+ }
+ contentDescription.append(val);
+ return contentDescription;
+ }
+
+ public static void announceForAccessibilityCompat(
+ final View view, @Nullable final AccessibilityManager accessibilityManager,
+ final int textResourceId) {
+ final String text = Factory.get().getApplicationContext().getResources().getString(
+ textResourceId);
+ announceForAccessibilityCompat(view, accessibilityManager, text);
+ }
+
+ public static void announceForAccessibilityCompat(
+ final View view, @Nullable AccessibilityManager accessibilityManager,
+ final CharSequence text) {
+ final Context context = view.getContext().getApplicationContext();
+ if (accessibilityManager == null) {
+ accessibilityManager = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ }
+
+ if (!accessibilityManager.isEnabled()) {
+ return;
+ }
+
+ // Jelly Bean added support for speaking text verbatim
+ final int eventType = OsUtil.isAtLeastJB() ? AccessibilityEvent.TYPE_ANNOUNCEMENT
+ : AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
+ // Construct an accessibility event with the minimum recommended
+ // attributes. An event without a class name or package may be dropped.
+ final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.getText().add(text);
+ event.setEnabled(view.isEnabled());
+ event.setClassName(view.getClass().getName());
+ event.setPackageName(context.getPackageName());
+
+ // JellyBean MR1 requires a source view to set the window ID.
+ final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
+ record.setSource(view);
+
+ // Sends the event directly through the accessibility manager. If we only supported SDK 14+
+ // we could have done:
+ // getParent().requestSendAccessibilityEvent(this, event);
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Check to see if the current layout is Right-to-Left. This check is only supported for
+ * API 17+.
+ * For earlier versions, this method will just return false.
+ * @return boolean Boolean indicating whether the currently locale is RTL.
+ */
+ public static boolean isLayoutRtl(final View view) {
+ if (OsUtil.isAtLeastJB_MR1()) {
+ return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
+ } else {
+ return false;
+ }
+ }
+
+ public static String getVocalizedPhoneNumber(final Resources res, final String phoneNumber) {
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return "";
+ }
+ final StringBuilder vocalizedPhoneNumber = new StringBuilder();
+ for (final char c : phoneNumber.toCharArray()) {
+ getVocalizedNumber(res, c, vocalizedPhoneNumber);
+ }
+ return vocalizedPhoneNumber.toString();
+ }
+
+ public static void getVocalizedNumber(final Resources res, final char c,
+ final StringBuilder builder) {
+ switch (c) {
+ case '0':
+ builder.append(res.getString(R.string.content_description_for_number_zero));
+ builder.append(" ");
+ return;
+ case '1':
+ builder.append(res.getString(R.string.content_description_for_number_one));
+ builder.append(" ");
+ return;
+ case '2':
+ builder.append(res.getString(R.string.content_description_for_number_two));
+ builder.append(" ");
+ return;
+ case '3':
+ builder.append(res.getString(R.string.content_description_for_number_three));
+ builder.append(" ");
+ return;
+ case '4':
+ builder.append(res.getString(R.string.content_description_for_number_four));
+ builder.append(" ");
+ return;
+ case '5':
+ builder.append(res.getString(R.string.content_description_for_number_five));
+ builder.append(" ");
+ return;
+ case '6':
+ builder.append(res.getString(R.string.content_description_for_number_six));
+ builder.append(" ");
+ return;
+ case '7':
+ builder.append(res.getString(R.string.content_description_for_number_seven));
+ builder.append(" ");
+ return;
+ case '8':
+ builder.append(res.getString(R.string.content_description_for_number_eight));
+ builder.append(" ");
+ return;
+ case '9':
+ builder.append(res.getString(R.string.content_description_for_number_nine));
+ builder.append(" ");
+ return;
+ default:
+ builder.append(c);
+ return;
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/Assert.java b/src/com/android/messaging/util/Assert.java
new file mode 100644
index 0000000..437965c
--- /dev/null
+++ b/src/com/android/messaging/util/Assert.java
@@ -0,0 +1,214 @@
+/*
+ * 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.util;
+
+import android.os.Looper;
+
+import java.util.Arrays;
+
+public final class Assert {
+ public static @interface RunsOnMainThread {}
+ public static @interface DoesNotRunOnMainThread {}
+ public static @interface RunsOnAnyThread {}
+
+ private static final String TEST_THREAD_SUBSTRING = "test";
+
+ private static boolean sIsEngBuild;
+ private static boolean sShouldCrash;
+
+ // Private constructor so no one creates this class.
+ private Assert() {
+ }
+
+ // The proguard rules will strip this method out on user/userdebug builds.
+ // If you change the method signature you MUST edit proguard-release.flags.
+ private static void setIfEngBuild() {
+ sShouldCrash = sIsEngBuild = true;
+ }
+
+ private static void refreshGservices(final BugleGservices gservices) {
+ sShouldCrash = sIsEngBuild;
+ if (!sShouldCrash) {
+ sShouldCrash = gservices.getBoolean(
+ BugleGservicesKeys.ASSERTS_FATAL,
+ BugleGservicesKeys.ASSERTS_FATAL_DEFAULT);
+ }
+ }
+
+ // Static initializer block to find out if we're running an eng or
+ // release build.
+ static {
+ setIfEngBuild();
+ }
+
+ // This is called from FactoryImpl once the Gservices class is initialized.
+ public static void initializeGservices (final BugleGservices gservices) {
+ gservices.registerForChanges(new Runnable() {
+ @Override
+ public void run() {
+ refreshGservices(gservices);
+ }
+ });
+ refreshGservices(gservices);
+ }
+
+ /**
+ * Halt execution if this is not an eng build.
+ * <p>Intended for use in code paths that should be run only for tests and never on
+ * a real build.
+ * <p>Note that this will crash on a user build even though asserts don't normally
+ * crash on a user build.
+ */
+ public static void isEngBuild() {
+ isTrueReleaseCheck(sIsEngBuild);
+ }
+
+ /**
+ * Halt execution if this isn't the case.
+ */
+ public static void isTrue(final boolean condition) {
+ if (!condition) {
+ fail("Expected condition to be true", false);
+ }
+ }
+
+ /**
+ * Halt execution if this isn't the case.
+ */
+ public static void isFalse(final boolean condition) {
+ if (condition) {
+ fail("Expected condition to be false", false);
+ }
+ }
+
+ /**
+ * Halt execution even in release builds if this isn't the case.
+ */
+ public static void isTrueReleaseCheck(final boolean condition) {
+ if (!condition) {
+ fail("Expected condition to be true", true);
+ }
+ }
+
+ public static void equals(final int expected, final int actual) {
+ if (expected != actual) {
+ fail("Expected " + expected + " but got " + actual, false);
+ }
+ }
+
+ public static void equals(final long expected, final long actual) {
+ if (expected != actual) {
+ fail("Expected " + expected + " but got " + actual, false);
+ }
+ }
+
+ public static void equals(final Object expected, final Object actual) {
+ if (expected != actual
+ && (expected == null || actual == null || !expected.equals(actual))) {
+ fail("Expected " + expected + " but got " + actual, false);
+ }
+ }
+
+ public static void oneOf(final int actual, final int ...expected) {
+ for (int value : expected) {
+ if (actual == value) {
+ return;
+ }
+ }
+ fail("Expected value to be one of " + Arrays.toString(expected) + " but was " + actual);
+ }
+
+ public static void inRange(
+ final int val, final int rangeMinInclusive, final int rangeMaxInclusive) {
+ if (val < rangeMinInclusive || val > rangeMaxInclusive) {
+ fail("Expected value in range [" + rangeMinInclusive + ", " +
+ rangeMaxInclusive + "], but was " + val, false);
+ }
+ }
+
+ public static void inRange(
+ final long val, final long rangeMinInclusive, final long rangeMaxInclusive) {
+ if (val < rangeMinInclusive || val > rangeMaxInclusive) {
+ fail("Expected value in range [" + rangeMinInclusive + ", " +
+ rangeMaxInclusive + "], but was " + val, false);
+ }
+ }
+
+ public static void isMainThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()
+ && !Thread.currentThread().getName().contains(TEST_THREAD_SUBSTRING)) {
+ fail("Expected to run on main thread", false);
+ }
+ }
+
+ public static void isNotMainThread() {
+ if (Looper.myLooper() == Looper.getMainLooper()
+ && !Thread.currentThread().getName().contains(TEST_THREAD_SUBSTRING)) {
+ fail("Not expected to run on main thread", false);
+ }
+ }
+
+ /**
+ * Halt execution if the value passed in is not null
+ * @param obj The object to check
+ */
+ public static void isNull(final Object obj) {
+ if (obj != null) {
+ fail("Expected object to be null", false);
+ }
+ }
+
+ /**
+ * Halt execution if the value passed in is not null
+ * @param obj The object to check
+ * @param failureMessage message to print when halting execution
+ */
+ public static void isNull(final Object obj, final String failureMessage) {
+ if (obj != null) {
+ fail(failureMessage, false);
+ }
+ }
+
+ /**
+ * Halt execution if the value passed in is null
+ * @param obj The object to check
+ */
+ public static void notNull(final Object obj) {
+ if (obj == null) {
+ fail("Expected value to be non-null", false);
+ }
+ }
+
+ public static void fail(final String message) {
+ fail("Assert.fail() called: " + message, false);
+ }
+
+ private static void fail(final String message, final boolean crashRelease) {
+ LogUtil.e(LogUtil.BUGLE_TAG, message);
+ if (crashRelease || sShouldCrash) {
+ throw new AssertionError(message);
+ } else {
+ // Find the method whose assertion failed. We're using a depth of 2, because all public
+ // Assert methods delegate to this one (see javadoc on getCaller() for details).
+ StackTraceElement caller = DebugUtils.getCaller(2);
+ if (caller != null) {
+ // This log message can be de-obfuscated by the Proguard retrace tool, just like a
+ // full stack trace from a crash.
+ LogUtil.e(LogUtil.BUGLE_TAG, "\tat " + caller.toString());
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/AvatarUriUtil.java b/src/com/android/messaging/util/AvatarUriUtil.java
new file mode 100644
index 0000000..df7d085
--- /dev/null
+++ b/src/com/android/messaging/util/AvatarUriUtil.java
@@ -0,0 +1,320 @@
+/*
+ * 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.util;
+
+import android.graphics.Color;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.data.ParticipantData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper utility for creating {@link android.net.Uri}s to describe what avatar to fetch or
+ * generate and will help verify and extract information from avatar {@link android.net.Uri}s.
+ *
+ * There are three types of avatar {@link android.net.Uri}.
+ *
+ * 1) Group Avatars - These are avatars which are used to represent a group conversation. Group
+ * avatars uris are basically multiple avatar uri which can be any of the below types but not
+ * another group avatar. The group avatars can hold anywhere from two to four avatars uri and can
+ * be in any of the following format
+ * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>
+ * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>
+ * messaging://avatar/g?p=<avatarUri>&p=<avatarUri2>&p=<avatarUri3>&p=<avatarUri4>
+ *
+ * 2) Local Resource - A local resource avatar is use when there is a profile photo for the
+ * participant. This can be any local resource.
+ *
+ * 3) Letter Tile - A letter tile is used when a participant has a name but no profile photo. A
+ * letter tile will contain the first code point of the participant's name and a background color
+ * based on the hash of the participant's full name. Letter tiles will be in the following format.
+ * messaging://avatar/l?n=<fullName>
+ *
+ * 4) Default Avatars - These are avatars are used when the participant has no profile photo or
+ * name. In these cases we use the default person icon with a color background. The color
+ * background is based on a hash of the normalized phone number.
+ *
+ * 5) Default Background Avatars - This is a special case for Default Avatars where we use the
+ * default background color for the default avatar.
+ *
+ * 6) SIM Selector Avatars - These are avatars used in the SIM selector. This may either be a
+ * regular local resource avatar (2) or an avatar with a SIM identifier (i.e. SIM background with
+ * a letter or a slot number).
+ */
+public class AvatarUriUtil {
+ private static final int MAX_GROUP_PARTICIPANTS = 4;
+
+ public static final String TYPE_GROUP_URI = "g";
+ public static final String TYPE_LOCAL_RESOURCE_URI = "r";
+ public static final String TYPE_LETTER_TILE_URI = "l";
+ public static final String TYPE_DEFAULT_URI = "d";
+ public static final String TYPE_DEFAULT_BACKGROUND_URI = "b";
+ public static final String TYPE_SIM_SELECTOR_URI = "s";
+
+ private static final String SCHEME = "messaging";
+ private static final String AUTHORITY = "avatar";
+ private static final String PARAM_NAME = "n";
+ private static final String PARAM_PRIMARY_URI = "m";
+ private static final String PARAM_FALLBACK_URI = "f";
+ private static final String PARAM_PARTICIPANT = "p";
+ private static final String PARAM_IDENTIFIER = "i";
+ private static final String PARAM_SIM_COLOR = "c";
+ private static final String PARAM_SIM_SELECTED = "s";
+ private static final String PARAM_SIM_INCOMING = "g";
+
+ public static final Uri DEFAULT_BACKGROUND_AVATAR = new Uri.Builder().scheme(SCHEME)
+ .authority(AUTHORITY).appendPath(TYPE_DEFAULT_BACKGROUND_URI).build();
+
+ private static final Uri BLANK_SIM_INDICATOR_INCOMING_URI = createSimIconUri("",
+ false /* selected */, Color.TRANSPARENT, true /* incoming */);
+ private static final Uri BLANK_SIM_INDICATOR_OUTGOING_URI = createSimIconUri("",
+ false /* selected */, Color.TRANSPARENT, false /* incoming */);
+
+ /**
+ * Creates an avatar uri based on a list of ParticipantData. The list of participants may not
+ * be null or empty. Depending on the size of the list either a group avatar uri will be create
+ * or an individual's avatar will be created. This will never return a null uri.
+ */
+ public static Uri createAvatarUri(@NonNull final List<ParticipantData> participants) {
+ Assert.notNull(participants);
+ Assert.isTrue(!participants.isEmpty());
+
+ if (participants.size() == 1) {
+ return createAvatarUri(participants.get(0));
+ }
+
+ final int numParticipants = Math.min(participants.size(), MAX_GROUP_PARTICIPANTS);
+ final ArrayList<Uri> avatarUris = new ArrayList<Uri>(numParticipants);
+ for (int i = 0; i < numParticipants; i++) {
+ avatarUris.add(createAvatarUri(participants.get(i)));
+ }
+ return AvatarUriUtil.joinAvatarUriToGroup(avatarUris);
+ }
+
+ /**
+ * Joins together a list of valid avatar uri into a group uri.The list of participants may not
+ * be null or empty. If a lit of one is given then the first element will be return back
+ * instead of a group avatar uri. All uris in the list must be a valid avatar uri. This will
+ * never return a null uri.
+ */
+ public static Uri joinAvatarUriToGroup(@NonNull final List<Uri> avatarUris) {
+ Assert.notNull(avatarUris);
+ Assert.isTrue(!avatarUris.isEmpty());
+
+ if (avatarUris.size() == 1) {
+ final Uri firstAvatar = avatarUris.get(0);
+ Assert.isTrue(AvatarUriUtil.isAvatarUri(firstAvatar));
+ return firstAvatar;
+ }
+
+ final Builder builder = new Builder();
+ builder.scheme(SCHEME);
+ builder.authority(AUTHORITY);
+ builder.appendPath(TYPE_GROUP_URI);
+ final int numParticipants = Math.min(avatarUris.size(), MAX_GROUP_PARTICIPANTS);
+ for (int i = 0; i < numParticipants; i++) {
+ final Uri uri = avatarUris.get(i);
+ Assert.notNull(uri);
+ Assert.isTrue(UriUtil.isLocalResourceUri(uri) || AvatarUriUtil.isAvatarUri(uri));
+ builder.appendQueryParameter(PARAM_PARTICIPANT, uri.toString());
+ }
+ return builder.build();
+ }
+
+ /**
+ * Creates an avatar uri based on ParticipantData which may not be null and expected to have
+ * profilePhotoUri, fullName and normalizedDestination populated. This will never return a null
+ * uri.
+ */
+ public static Uri createAvatarUri(@NonNull final ParticipantData participant) {
+ Assert.notNull(participant);
+ final String photoUriString = participant.getProfilePhotoUri();
+ final Uri profilePhotoUri = (photoUriString == null) ? null : Uri.parse(photoUriString);
+ final String name = participant.getFullName();
+ final String destination = participant.getNormalizedDestination();
+ final String contactLookupKey = participant.getLookupKey();
+ return createAvatarUri(profilePhotoUri, name, destination, contactLookupKey);
+ }
+
+ /**
+ * Creates an avatar uri based on a the input data.
+ */
+ public static Uri createAvatarUri(final Uri profilePhotoUri, final CharSequence name,
+ final String defaultIdentifier, final String contactLookupKey) {
+ Uri generatedUri;
+ if (!TextUtils.isEmpty(name) && isValidFirstCharacter(name)) {
+ generatedUri = AvatarUriUtil.fromName(name, contactLookupKey);
+ } else {
+ final String identifier = TextUtils.isEmpty(contactLookupKey)
+ ? defaultIdentifier : contactLookupKey;
+ generatedUri = AvatarUriUtil.fromIdentifier(identifier);
+ }
+
+ if (profilePhotoUri != null) {
+ if (UriUtil.isLocalResourceUri(profilePhotoUri)) {
+ return fromLocalResourceWithFallback(profilePhotoUri, generatedUri);
+ } else {
+ return profilePhotoUri;
+ }
+ } else {
+ return generatedUri;
+ }
+ }
+
+ public static boolean isValidFirstCharacter(final CharSequence name) {
+ final char c = name.charAt(0);
+ return c != '+';
+ }
+
+ /**
+ * Creates an avatar URI used for the SIM selector.
+ * @param participantData the self participant data for an <i>active</i> SIM
+ * @param slotIdentifier when null, this will simply use a regular avatar; otherwise, the
+ * first letter of slotIdentifier will be used for the icon.
+ * @param selected is this the currently selected SIM?
+ * @param incoming is this for an incoming message or outgoing message?
+ */
+ public static Uri createAvatarUri(@NonNull final ParticipantData participantData,
+ @Nullable final String slotIdentifier, final boolean selected, final boolean incoming) {
+ Assert.notNull(participantData);
+ Assert.isTrue(participantData.isActiveSubscription());
+ Assert.isTrue(!TextUtils.isEmpty(slotIdentifier) ||
+ !TextUtils.isEmpty(participantData.getProfilePhotoUri()));
+ if (TextUtils.isEmpty(slotIdentifier)) {
+ return createAvatarUri(participantData);
+ }
+
+ return createSimIconUri(slotIdentifier, selected, participantData.getSubscriptionColor(),
+ incoming);
+ }
+
+ private static Uri createSimIconUri(final String slotIdentifier, final boolean selected,
+ final int subColor, final boolean incoming) {
+ final Builder builder = new Builder();
+ builder.scheme(SCHEME);
+ builder.authority(AUTHORITY);
+ builder.appendPath(TYPE_SIM_SELECTOR_URI);
+ builder.appendQueryParameter(PARAM_IDENTIFIER, slotIdentifier);
+ builder.appendQueryParameter(PARAM_SIM_COLOR, String.valueOf(subColor));
+ builder.appendQueryParameter(PARAM_SIM_SELECTED, String.valueOf(selected));
+ builder.appendQueryParameter(PARAM_SIM_INCOMING, String.valueOf(incoming));
+ return builder.build();
+ }
+
+ public static Uri getBlankSimIndicatorUri(final boolean incoming) {
+ return incoming ? BLANK_SIM_INDICATOR_INCOMING_URI : BLANK_SIM_INDICATOR_OUTGOING_URI;
+ }
+
+ /**
+ * Creates an avatar uri from the given local resource Uri, followed by a fallback Uri in case
+ * the local resource one could not be loaded.
+ */
+ private static Uri fromLocalResourceWithFallback(@NonNull final Uri profilePhotoUri,
+ @NonNull Uri fallbackUri) {
+ Assert.notNull(profilePhotoUri);
+ Assert.notNull(fallbackUri);
+ final Builder builder = new Builder();
+ builder.scheme(SCHEME);
+ builder.authority(AUTHORITY);
+ builder.appendPath(TYPE_LOCAL_RESOURCE_URI);
+ builder.appendQueryParameter(PARAM_PRIMARY_URI, profilePhotoUri.toString());
+ builder.appendQueryParameter(PARAM_FALLBACK_URI, fallbackUri.toString());
+ return builder.build();
+ }
+
+ private static Uri fromName(@NonNull final CharSequence name, final String contactLookupKey) {
+ Assert.notNull(name);
+ final Builder builder = new Builder();
+ builder.scheme(SCHEME);
+ builder.authority(AUTHORITY);
+ builder.appendPath(TYPE_LETTER_TILE_URI);
+ final String nameString = String.valueOf(name);
+ builder.appendQueryParameter(PARAM_NAME, nameString);
+ final String identifier =
+ TextUtils.isEmpty(contactLookupKey) ? nameString : contactLookupKey;
+ builder.appendQueryParameter(PARAM_IDENTIFIER, identifier);
+ return builder.build();
+ }
+
+ private static Uri fromIdentifier(@NonNull final String identifier) {
+ final Builder builder = new Builder();
+ builder.scheme(SCHEME);
+ builder.authority(AUTHORITY);
+ builder.appendPath(TYPE_DEFAULT_URI);
+ builder.appendQueryParameter(PARAM_IDENTIFIER, identifier);
+ return builder.build();
+ }
+
+ public static boolean isAvatarUri(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return uri != null && TextUtils.equals(SCHEME, uri.getScheme()) &&
+ TextUtils.equals(AUTHORITY, uri.getAuthority());
+ }
+
+ public static String getAvatarType(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ final List<String> path = uri.getPathSegments();
+ return path.isEmpty() ? null : path.get(0);
+ }
+
+ public static String getIdentifier(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return uri.getQueryParameter(PARAM_IDENTIFIER);
+ }
+
+ public static String getName(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return uri.getQueryParameter(PARAM_NAME);
+ }
+
+ public static List<String> getGroupParticipantUris(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return uri.getQueryParameters(PARAM_PARTICIPANT);
+ }
+
+ public static int getSimColor(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return Integer.valueOf(uri.getQueryParameter(PARAM_SIM_COLOR));
+ }
+
+ public static boolean getSimSelected(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_SELECTED));
+ }
+
+ public static boolean getSimIncoming(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return Boolean.valueOf(uri.getQueryParameter(PARAM_SIM_INCOMING));
+ }
+
+ public static Uri getPrimaryUri(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ final String primaryUriString = uri.getQueryParameter(PARAM_PRIMARY_URI);
+ return primaryUriString == null ? null : Uri.parse(primaryUriString);
+ }
+
+ public static Uri getFallbackUri(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ final String fallbackUriString = uri.getQueryParameter(PARAM_FALLBACK_URI);
+ return fallbackUriString == null ? null : Uri.parse(fallbackUriString);
+ }
+}
diff --git a/src/com/android/messaging/util/BugleActivityUtil.java b/src/com/android/messaging/util/BugleActivityUtil.java
new file mode 100644
index 0000000..7f722fd
--- /dev/null
+++ b/src/com/android/messaging/util/BugleActivityUtil.java
@@ -0,0 +1,88 @@
+/*
+ * 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.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.ui.conversation.ConversationActivity;
+import com.android.messaging.ui.conversationlist.ConversationListActivity;
+
+/**
+ * Utility class including logic to verify requirements to run Bugle and other activity startup
+ * logic. Called from base Bugle activity classes.
+ */
+public class BugleActivityUtil {
+
+ private static final int REQUEST_GOOGLE_PLAY_SERVICES = 0;
+
+ /**
+ * Determine if the requirements for the app to run are met. Log any Activity startup
+ * analytics.
+ * @param context
+ * @param activity is used to launch an error Dialog if necessary
+ * @return true if resume should continue normally. Returns false if some requirements to run
+ * are not met.
+ */
+ public static boolean onActivityResume(Context context, Activity activity) {
+ DataModel.get().onActivityResume();
+ Factory.get().onActivityResume();
+
+ // Validate all requirements to run are met
+ return checkHasSmsPermissionsForUser(context, activity);
+ }
+
+ /**
+ * Determine if the user doesn't have SMS permissions. This can happen if you are not the phone
+ * owner and the owner has disabled your SMS permissions.
+ * @param context is the Context used to resolve the user permissions
+ * @param activity is the Activity used to launch an error Dialog if necessary
+ * @return true if the user has SMS permissions, otherwise false.
+ */
+ private static boolean checkHasSmsPermissionsForUser(Context context, Activity activity) {
+ if (!OsUtil.isAtLeastL()) {
+ // UserManager.DISALLOW_SMS added in L. No multiuser phones before this
+ return true;
+ }
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_SMS)) {
+ new AlertDialog.Builder(activity)
+ .setMessage(R.string.requires_sms_permissions_message)
+ .setCancelable(false)
+ .setNegativeButton(R.string.requires_sms_permissions_close_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog,
+ final int button) {
+ System.exit(0);
+ }
+ })
+ .show();
+ return false;
+ }
+ return true;
+ }
+}
+
diff --git a/src/com/android/messaging/util/BugleApplicationPrefs.java b/src/com/android/messaging/util/BugleApplicationPrefs.java
new file mode 100644
index 0000000..e9fceb4
--- /dev/null
+++ b/src/com/android/messaging/util/BugleApplicationPrefs.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.util;
+
+import android.content.Context;
+
+/**
+ * Provides interface to access application-wide shared preferences. This includes both the user
+ * visible preferences (e.g. the general settings in the settings page), and internal preferences
+ * under {@link BuglePrefsKeys}.
+ */
+public class BugleApplicationPrefs extends BuglePrefsImpl {
+ public BugleApplicationPrefs(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getSharedPreferencesName() {
+ return SHARED_PREFERENCES_NAME;
+ }
+
+ @Override
+ protected void validateKey(String key) {
+ super.validateKey(key);
+ // Callers shouldn't try to access per-subscription preferences from this class
+ Assert.isFalse(key.startsWith(SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX));
+ }
+
+ @Override
+ public void onUpgrade(int oldVersion, int newVersion) {
+ }
+}
diff --git a/src/com/android/messaging/util/BugleGservices.java b/src/com/android/messaging/util/BugleGservices.java
new file mode 100644
index 0000000..2e095de
--- /dev/null
+++ b/src/com/android/messaging/util/BugleGservices.java
@@ -0,0 +1,72 @@
+/*
+ * 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.util;
+
+import com.android.messaging.Factory;
+
+/**
+ * A thin wrapper for getting GServices value. During constructor time a one time background thread
+ * will cache all GServices key with the prefix of "bugle_". All get calls will wait for Gservices
+ * to finish caching the first time. In practice, the background thread will finish before any get
+ * request.
+ */
+public abstract class BugleGservices {
+ static final String BUGLE_GSERVICES_PREFIX = "bugle_";
+
+ public static BugleGservices get() {
+ return Factory.get().getBugleGservices();
+ }
+
+ public abstract void registerForChanges(final Runnable r);
+
+ /**
+ * @param key The key to look up in GServices
+ * @param defaultValue The default value if value in GServices is null or if
+ * NumberFormatException is caught.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract long getLong(final String key, final long defaultValue);
+
+ /**
+ * @param key The key to look up in GServices
+ * @param defaultValue The default value if value in GServices is null or if
+ * NumberFormatException is caught.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract int getInt(final String key, final int defaultValue);
+
+ /**
+ * @param key The key to look up in GServices
+ * @param defaultValue The default value if value in GServices is null.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract boolean getBoolean(final String key, final boolean defaultValue);
+
+ /**
+ * @param key The key to look up in GServices
+ * @param defaultValue The default value if value in GServices is null.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract String getString(final String key, final String defaultValue);
+
+ /**
+ * @param key The key to look up in GServices
+ * @param defaultValue The default value if value in GServices is null.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract float getFloat(final String key, final float defaultValue);
+}
diff --git a/src/com/android/messaging/util/BugleGservicesImpl.java b/src/com/android/messaging/util/BugleGservicesImpl.java
new file mode 100644
index 0000000..5ef0898
--- /dev/null
+++ b/src/com/android/messaging/util/BugleGservicesImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+
+/**
+ * A thin wrapper for getting GServices value.
+ */
+public class BugleGservicesImpl extends BugleGservices {
+ public BugleGservicesImpl(final Context context) {
+ }
+
+ @Override
+ public void registerForChanges(final Runnable r) {
+ }
+
+ /**
+ * Asserts that the key has the expected prefix.
+ */
+ private void assertKeyAndWaitForGservices(final String key) {
+ Assert.isTrue(key.startsWith(BUGLE_GSERVICES_PREFIX));
+ }
+
+ @Override
+ public long getLong(final String key, final long defaultValue) {
+ assertKeyAndWaitForGservices(key);
+ return defaultValue;
+ }
+
+ @Override
+ public int getInt(final String key, final int defaultValue) {
+ assertKeyAndWaitForGservices(key);
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getBoolean(final String key, final boolean defaultValue) {
+ assertKeyAndWaitForGservices(key);
+ return defaultValue;
+ }
+
+ @Override
+ public String getString(final String key, final String defaultValue) {
+ assertKeyAndWaitForGservices(key);
+ return defaultValue;
+ }
+
+ @Override
+ public float getFloat(final String key, final float defaultValue) {
+ assertKeyAndWaitForGservices(key);
+ return defaultValue;
+ }
+}
diff --git a/src/com/android/messaging/util/BugleGservicesKeys.java b/src/com/android/messaging/util/BugleGservicesKeys.java
new file mode 100644
index 0000000..f36dd7f
--- /dev/null
+++ b/src/com/android/messaging/util/BugleGservicesKeys.java
@@ -0,0 +1,298 @@
+/*
+ * 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.util;
+
+
+/**
+ * List of gservices keys and default values which are in use.
+ */
+public final class BugleGservicesKeys {
+ private BugleGservicesKeys() {} // do not instantiate
+
+ /**
+ * Whether to enable extra debugging features on the client. Default is
+ * {@value #ENABLE_DEBUGGING_FEATURES_DEFAULT}.
+ */
+ public static final String ENABLE_DEBUGGING_FEATURES
+ = "bugle_debugging";
+ public static final boolean ENABLE_DEBUGGING_FEATURES_DEFAULT
+ = false;
+
+ /**
+ * Whether to enable saving extra logs. Default is {@value #ENABLE_LOG_SAVER_DEFAULT}.
+ */
+ public static final String ENABLE_LOG_SAVER = "bugle_logsaver";
+ public static final boolean ENABLE_LOG_SAVER_DEFAULT = false;
+
+ /**
+ * Time in milliseconds of initial (attempt 1) resend backoff for failing messages
+ */
+ public static final String INITIAL_MESSAGE_RESEND_DELAY_MS = "bugle_resend_delay_in_millis";
+ public static final long INITIAL_MESSAGE_RESEND_DELAY_MS_DEFAULT = 5 * 1000L;
+
+ /**
+ * Time in milliseconds of max resend backoff for failing messages
+ */
+ public static final String MAX_MESSAGE_RESEND_DELAY_MS = "bugle_max_resend_delay_in_millis";
+ public static final long MAX_MESSAGE_RESEND_DELAY_MS_DEFAULT = 2 * 60 * 60 * 1000L;
+
+ /**
+ * Time in milliseconds of resend window for unsent messages
+ */
+ public static final String MESSAGE_RESEND_TIMEOUT_MS = "bugle_resend_timeout_in_millis";
+ public static final long MESSAGE_RESEND_TIMEOUT_MS_DEFAULT = 20 * 60 * 1000L;
+
+ /**
+ * Time in milliseconds of download window for new mms notifications
+ */
+ public static final String MESSAGE_DOWNLOAD_TIMEOUT_MS = "bugle_download_timeout_in_millis";
+ public static final long MESSAGE_DOWNLOAD_TIMEOUT_MS_DEFAULT = 20 * 60 * 1000L;
+
+ /**
+ * Time in milliseconds for SMS send timeout
+ */
+ public static final String SMS_SEND_TIMEOUT_IN_MILLIS = "bugle_sms_send_timeout";
+ public static final long SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT = 5 * 60 * 1000L;
+
+ /**
+ * Keys to control the SMS sync batch size. The batch size is defined by the number
+ * of messages that incur local database change, e.g. importing messages and
+ * deleting messages.
+ *
+ * 1. The minimum size for a batch and
+ * 2. The maximum size for a batch.
+ * The first batch uses the minimum size for probing. Set this to a small number for the
+ * first sync batch to make sure the user sees SMS showing up in conversations quickly
+ * Use these two settings to limit the number of messages to sync in each batch.
+ * The minimum is to make sure we always make progress during sync. The maximum is
+ * to limit the sync batch size within a reasonable range (needs to fit in an intent).
+ * 3. The time limit controls the limit of time duration of a sync batch. We can
+ * not control this directly due to the batching nature of sync. So this provides
+ * heuristics. We may sometime exceeds the limit if our calculation is off due to
+ * whatever reasons. Keeping this low ensures responsiveness of the application.
+ * 4. The limit on number of total messages to scan in one batch.
+ */
+ public static final String SMS_SYNC_BATCH_SIZE_MIN =
+ "bugle_sms_sync_batch_size_min";
+ public static final int SMS_SYNC_BATCH_SIZE_MIN_DEFAULT = 80;
+ public static final String SMS_SYNC_BATCH_SIZE_MAX =
+ "bugle_sms_sync_batch_size_max";
+ public static final int SMS_SYNC_BATCH_SIZE_MAX_DEFAULT = 1000;
+ public static final String SMS_SYNC_BATCH_TIME_LIMIT_MILLIS =
+ "bugle_sms_sync_batch_time_limit";
+ public static final long SMS_SYNC_BATCH_TIME_LIMIT_MILLIS_DEFAULT = 400;
+ public static final String SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN =
+ "bugle_sms_sync_batch_max_messages_to_scan";
+ public static final int SMS_SYNC_BATCH_MAX_MESSAGES_TO_SCAN_DEFAULT =
+ SMS_SYNC_BATCH_SIZE_MAX_DEFAULT * 4;
+
+ /**
+ * Time in ms for sync to backoff from "now" to the latest message that will be sync'd.
+ *
+ * This controls the best case for how out of date the application will appear to be
+ * when bringing in changes made outside the application. It also represents a buffer
+ * to ensure that sync doesn't trigger based on changes made within the application.
+ */
+ public static final String SMS_SYNC_BACKOFF_TIME_MILLIS =
+ "bugle_sms_sync_backoff_time";
+ public static final long SMS_SYNC_BACKOFF_TIME_MILLIS_DEFAULT = 5000L;
+
+ /**
+ * Just in case if we fall into a loop of full sync -> still not synchronized -> full sync ...
+ * This forces a backoff time so that we at most do full sync once a while (an hour by default)
+ */
+ public static final String SMS_FULL_SYNC_BACKOFF_TIME_MILLIS =
+ "bugle_sms_full_sync_backoff_time";
+ public static final long SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT = 60 * 60 * 1000;
+
+ /**
+ * Time duration to retain the most recent SMS messages for SMS storage purging
+ *
+ * Format:
+ * <number>(w|m|y)
+ * Examples:
+ * "1y" -- a year
+ * "2w" -- two weeks
+ * "6m" -- six months
+ */
+ public static final String SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION =
+ "bugle_sms_storage_purging_message_retaining_duration";
+ public static final String SMS_STORAGE_PURGING_MESSAGE_RETAINING_DURATION_DEFAULT = "1m";
+
+ /**
+ * MMS UA profile url.
+ *
+ * This is used on all Android devices running Hangout, so cannot just host the profile of the
+ * latest and greatest phones. However, if we're on KitKat or below we can't get the phone's
+ * UA profile and thus we need to send them the default url.
+ */
+ public static final String MMS_UA_PROFILE_URL =
+ "bugle_mms_uaprofurl";
+ public static final String MMS_UA_PROFILE_URL_DEFAULT =
+ "http://www.gstatic.com/android/sms/mms_ua_profile.xml";
+
+ /**
+ * MMS apn mmsc
+ */
+ public static final String MMS_MMSC =
+ "bugle_mms_mmsc";
+
+ /**
+ * MMS apn proxy ip address
+ */
+ public static final String MMS_PROXY_ADDRESS =
+ "bugle_mms_proxy_address";
+
+ /**
+ * MMS apn proxy port
+ */
+ public static final String MMS_PROXY_PORT =
+ "bugle_mms_proxy_port";
+
+ /**
+ * List of known SMS system messages that we will ignore (no deliver, no abort) so that the
+ * user doesn't see them and the appropriate app is able to handle them. We are delivering
+ * these as a \n delimited list of patterns, however we should eventually move to storing
+ * them with the per-carrier mms config xml file.
+ */
+ public static final String SMS_IGNORE_MESSAGE_REGEX =
+ "bugle_sms_ignore_message_regex";
+ public static final String SMS_IGNORE_MESSAGE_REGEX_DEFAULT = "";
+
+ /**
+ * When receiving or importing an mms, limit the length of text to this limit. Huge blocks
+ * of text can cause the app to hang/ANR/or crash in native text code..
+ */
+ public static final String MMS_TEXT_LIMIT = "bugle_mms_text_limit";
+ public static final int MMS_TEXT_LIMIT_DEFAULT = 2000;
+
+ /**
+ * Max number of attachments the user may add to a single message.
+ */
+ public static final String MMS_ATTACHMENT_LIMIT = "bugle_mms_attachment_limit";
+ public static final int MMS_ATTACHMENT_LIMIT_DEFAULT = 10;
+
+ /**
+ * The max number of messages to show in a single conversation notification. We always show
+ * the most recent message. If this value is >1, we may also include prior messages as well.
+ */
+ public static final String MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION =
+ "bugle_max_messages_in_conversation_notification";
+ public static final int MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_DEFAULT = 7;
+
+ /**
+ * Time (in seconds) between notification ringing for incoming messages of the same
+ * conversation. We won't ding more often than this value for messages coming in at a high rate.
+ */
+ public static final String NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS
+ = "bugle_notification_time_between_rings_seconds";
+ public static final int NOTIFICATION_TIME_BETWEEN_RINGS_SECONDS_DEFAULT = 10;
+
+ /**
+ * The max number of messages to show in a single conversation notification, when a wearable
+ * device (i.e. smartwatch) is paired with the phone. Watches have a different UX model and
+ * less screen real estate, so we may want to optimize for that case. Note that if a wearable
+ * is paired, this value will apply to notifications as shown both on the watch and the phone.
+ */
+ public static final String MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE =
+ "bugle_max_messages_in_conversation_notification_with_wearable";
+ public static final int MAX_MESSAGES_IN_CONVERSATION_NOTIFICATION_WITH_WEARABLE_DEFAULT = 1;
+
+ /**
+ * Regular expression to match against query. If it matches then display
+ * the query plan for this query.
+ */
+ public static final String EXPLAIN_QUERY_PLAN_REGEXP = "bugle_query_plan_regexp";
+
+ /**
+ * Whether asserts are fatal on user/userdebug builds.
+ * Default is {@value #ASSERTS_FATAL_DEFAULT}.
+ */
+ public static final String ASSERTS_FATAL = "bugle_asserts_fatal";
+ public static final boolean ASSERTS_FATAL_DEFAULT = false;
+
+ /**
+ * Whether to use API for sending/downloading MMS (if present, true for L).
+ * Default is {@value #USE_MMS_API_IF_PRESENT_DEFAULT}.
+ */
+ public static final String USE_MMS_API_IF_PRESENT = "bugle_use_mms_api";
+ public static final boolean USE_MMS_API_IF_PRESENT_DEFAULT = true;
+
+ /**
+ * Whether to always auto-complete email addresses for sending MMS. By default, Bugle starts
+ * to auto-complete after the user has typed the "@" character.
+ * Default is (@value ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT}.
+ */
+ public static final String ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS =
+ "bugle_always_autocomplete_email_address";
+ public static final boolean ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT = false;
+
+ // We typically request an aspect ratio close the the screen size, but some cameras can be
+ // flaky and not work well in certain aspect ratios. This allows us to guide the CameraManager
+ // to pick a more reliable aspect ratio. The value is a float like 1.333f or 1.777f. There is
+ // no hard coded default because the default is the screen aspect ratio.
+ public static final String CAMERA_ASPECT_RATIO = "bugle_camera_aspect_ratio";
+
+ /**
+ * The recent time range within which we should check MMS WAP Push duplication
+ * If the value is 0, it signals that we should use old dedup algorithm for wap push
+ */
+ public static final String MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS =
+ "bugle_mms_wap_push_dedup_time_limit_secs";
+ public static final long MMS_WAP_PUSH_DEDUP_TIME_LIMIT_SECS_DEFAULT = 7 * 24 * 3600; // 7 days
+
+ /**
+ * Whether to use persistent, on-disk LogSaver
+ */
+ public static final String PERSISTENT_LOGSAVER = "bugle_persistent_logsaver";
+ public static final boolean PERSISTENT_LOGSAVER_DEFAULT = false;
+
+ /**
+ * For in-memory LogSaver, what's the size of memory buffer in number of records
+ */
+ public static final String IN_MEMORY_LOGSAVER_RECORD_COUNT =
+ "bugle_in_memory_logsaver_record_count";
+ public static final int IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT = 500;
+
+ /**
+ * For on-disk LogSaver, what's the size of file rotation set
+ */
+ public static final String PERSISTENT_LOGSAVER_ROTATION_SET_SIZE =
+ "bugle_persistent_logsaver_rotation_set_size";
+ public static final int PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT = 8;
+
+ /**
+ * For on-disk LogSaver, what's the byte limit of a single log file
+ */
+ public static final String PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES =
+ "bugle_persistent_logsaver_file_limit";
+ public static final int PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT = 256 * 1024; // 256KB
+
+ /**
+ * We concatenate all text parts in an MMS to form the message text. This specifies
+ * the separator between the combinated text parts. Default is ' ' (space).
+ */
+ public static final String MMS_TEXT_CONCAT_SEPARATOR = "bugle_mms_text_concat_separator";
+ public static final String MMS_TEXT_CONCAT_SEPARATOR_DEFAULT = " ";
+
+ /**
+ * Whether to enable transcoding GIFs. We sometimes need to compress GIFs to make them small
+ * enough to send via MMS (which often limits messages to 1 MB in size).
+ */
+ public static final String ENABLE_GIF_TRANSCODING = "bugle_gif_transcoding";
+ public static final boolean ENABLE_GIF_TRANSCODING_DEFAULT = true;
+}
diff --git a/src/com/android/messaging/util/BuglePrefs.java b/src/com/android/messaging/util/BuglePrefs.java
new file mode 100644
index 0000000..74a0d46
--- /dev/null
+++ b/src/com/android/messaging/util/BuglePrefs.java
@@ -0,0 +1,140 @@
+/*
+ * 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.util;
+
+import com.android.messaging.Factory;
+
+/**
+ * Thin wrapper to get/set shared prefs values.
+ */
+public abstract class BuglePrefs {
+ /**
+ * Shared preferences name for preferences applicable to the entire app.
+ */
+ public static final String SHARED_PREFERENCES_NAME = "bugle";
+
+ /**
+ * Shared preferences name for subscription-specific preferences.
+ * Note: for all subscription-specific preferences, please prefix the shared preference keys
+ * with "buglesub_", so that Bugle may perform runtime validations on preferences to make sure
+ * you don't accidentally write per-subscription settings into the general pref file, and vice
+ * versa.
+ */
+ public static final String SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX = "buglesub_";
+
+ /**
+ * A placeholder base version for Bugle builds where no shared pref version was defined.
+ */
+ public static final int NO_SHARED_PREFERENCES_VERSION = -1;
+
+ /**
+ * Returns the shared preferences file name to use.
+ * Subclasses should override and return the shared preferences file.
+ */
+ public abstract String getSharedPreferencesName();
+
+
+ /**
+ * Handles pref version upgrade.
+ */
+ public abstract void onUpgrade(final int oldVersion, final int newVersion);
+
+ /**
+ * Gets the SharedPreferences accessor to the application-wide preferences.
+ */
+ public static BuglePrefs getApplicationPrefs() {
+ return Factory.get().getApplicationPrefs();
+ }
+
+ /**
+ * Gets the SharedPreferences accessor to the subscription-specific preferences.
+ */
+ public static BuglePrefs getSubscriptionPrefs(final int subId) {
+ return Factory.get().getSubscriptionPrefs(subId);
+ }
+
+ /**
+ * @param key The key to look up in shared prefs
+ * @param defaultValue The default value if value in shared prefs is null or if
+ * NumberFormatException is caught.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract int getInt(final String key, final int defaultValue);
+
+ /**
+ * @param key The key to look up in shared prefs
+ * @param defaultValue The default value if value in shared prefs is null or if
+ * NumberFormatException is caught.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract long getLong(final String key, final long defaultValue);
+
+ /**
+ * @param key The key to look up in shared prefs
+ * @param defaultValue The default value if value in shared prefs is null.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract boolean getBoolean(final String key, final boolean defaultValue);
+
+ /**
+ * @param key The key to look up in shared prefs
+ * @param defaultValue The default value if value in shared prefs is null.
+ * @return The corresponding value, or the default value.
+ */
+ public abstract String getString(final String key, final String defaultValue);
+
+ /**
+ * @param key The key to look up in shared prefs
+ * @return The corresponding value, or null if not found.
+ */
+ public abstract byte[] getBytes(final String key);
+
+ /**
+ * @param key The key to set in shared prefs
+ * @param value The value to assign to the key
+ */
+ public abstract void putInt(final String key, final int value);
+
+ /**
+ * @param key The key to set in shared prefs
+ * @param value The value to assign to the key
+ */
+ public abstract void putLong(final String key, final long value);
+
+ /**
+ * @param key The key to set in shared prefs
+ * @param value The value to assign to the key
+ */
+ public abstract void putBoolean(final String key, final boolean value);
+
+ /**
+ * @param key The key to set in shared prefs
+ * @param value The value to assign to the key
+ */
+ public abstract void putString(final String key, final String value);
+
+ /**
+ * @param key The key to set in shared prefs
+ * @param value The value to assign to the key
+ */
+ public abstract void putBytes(final String key, final byte[] value);
+
+ /**
+ * @param key The key to remove from shared prefs
+ */
+ public abstract void remove(String key);
+}
diff --git a/src/com/android/messaging/util/BuglePrefsImpl.java b/src/com/android/messaging/util/BuglePrefsImpl.java
new file mode 100644
index 0000000..7563040
--- /dev/null
+++ b/src/com/android/messaging/util/BuglePrefsImpl.java
@@ -0,0 +1,135 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Base64;
+
+/**
+ * Thin wrapper to get/set shared prefs values.
+ */
+public abstract class BuglePrefsImpl extends BuglePrefs {
+
+ private final Context mContext;
+
+ public BuglePrefsImpl(final Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Validate the prefs key passed in. Subclasses should override this as needed to perform
+ * runtime checks (such as making sure per-subscription settings don't sneak into application-
+ * wide settings).
+ */
+ protected void validateKey(String key) {
+ }
+
+ @Override
+ public int getInt(final String key, final int defaultValue) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ return prefs.getInt(key, defaultValue);
+ }
+
+ @Override
+ public long getLong(final String key, final long defaultValue) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ return prefs.getLong(key, defaultValue);
+ }
+
+ @Override
+ public boolean getBoolean(final String key, final boolean defaultValue) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ return prefs.getBoolean(key, defaultValue);
+ }
+
+ @Override
+ public String getString(final String key, final String defaultValue) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ return prefs.getString(key, defaultValue);
+ }
+
+ @Override
+ public byte[] getBytes(String key) {
+ final String byteValue = getString(key, null);
+ return byteValue == null ? null : Base64.decode(byteValue, Base64.DEFAULT);
+ }
+
+ @Override
+ public void putInt(final String key, final int value) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.putInt(key, value);
+ editor.apply();
+ }
+
+ @Override
+ public void putLong(final String key, final long value) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(key, value);
+ editor.apply();
+ }
+
+ @Override
+ public void putBoolean(final String key, final boolean value) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(key, value);
+ editor.apply();
+ }
+
+ @Override
+ public void putString(final String key, final String value) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(key, value);
+ editor.apply();
+ }
+
+ @Override
+ public void putBytes(String key, byte[] value) {
+ final String encodedBytes = Base64.encodeToString(value, Base64.DEFAULT);
+ putString(key, encodedBytes);
+ }
+
+ @Override
+ public void remove(final String key) {
+ validateKey(key);
+ final SharedPreferences prefs = mContext.getSharedPreferences(
+ getSharedPreferencesName(), Context.MODE_PRIVATE);
+ final SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(key);
+ editor.apply();
+ }
+}
diff --git a/src/com/android/messaging/util/BuglePrefsKeys.java b/src/com/android/messaging/util/BuglePrefsKeys.java
new file mode 100644
index 0000000..ae409bc
--- /dev/null
+++ b/src/com/android/messaging/util/BuglePrefsKeys.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util;
+
+/**
+ * List of shared preferences keys and default values. These are all internal
+ * (not user-visible) preferences. Preferences that are exposed via the Settings
+ * activity should be defined in the constants.xml resource file instead.
+ */
+public final class BuglePrefsKeys {
+ private BuglePrefsKeys() {} // do not instantiate
+
+ /**
+ * Bugle's shared preferences version
+ */
+ public static final String SHARED_PREFERENCES_VERSION =
+ "shared_preferences_version";
+ public static final int SHARED_PREFERENCES_VERSION_DEFAULT =
+ BuglePrefs.NO_SHARED_PREFERENCES_VERSION;
+
+ /**
+ * Last time that we ran a a sync (in millis)
+ */
+ public static final String LAST_SYNC_TIME
+ = "last_sync_time_millis";
+ public static final long LAST_SYNC_TIME_DEFAULT
+ = -1;
+
+ /**
+ * Last time that we ran a full sync (in millis)
+ */
+ public static final String LAST_FULL_SYNC_TIME
+ = "last_full_sync_time_millis";
+ public static final long LAST_FULL_SYNC_TIME_DEFAULT
+ = -1;
+
+ /**
+ * Timestamp of the message for which we last did a message notification.
+ */
+ public static final String LATEST_NOTIFICATION_MESSAGE_TIMESTAMP
+ = "latest_notification_message_timestamp";
+
+ /**
+ * The last selected chooser index in the media picker.
+ */
+ public static final String SELECTED_MEDIA_PICKER_CHOOSER_INDEX
+ = "selected_media_picker_chooser_index";
+ public static final int SELECTED_MEDIA_PICKER_CHOOSER_INDEX_DEFAULT
+ = -1;
+
+ /**
+ * The attempt number when retrying ProcessPendingMessagesAction
+ */
+ public static final String PROCESS_PENDING_MESSAGES_RETRY_COUNT
+ = "process_pending_retry";
+
+}
diff --git a/src/com/android/messaging/util/BugleSubscriptionPrefs.java b/src/com/android/messaging/util/BugleSubscriptionPrefs.java
new file mode 100644
index 0000000..039712a
--- /dev/null
+++ b/src/com/android/messaging/util/BugleSubscriptionPrefs.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+
+/**
+ * Provides interface to access per-subscription shared preferences. We have one instance of
+ * this per active subscription.
+ */
+public class BugleSubscriptionPrefs extends BuglePrefsImpl {
+ private final int mSubId;
+
+ public BugleSubscriptionPrefs(final Context context, final int subId) {
+ super(context);
+ mSubId = subId;
+ }
+
+ @Override
+ public String getSharedPreferencesName() {
+ return SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX + String.valueOf(mSubId);
+ }
+
+ @Override
+ protected void validateKey(String key) {
+ super.validateKey(key);
+ // Callers should only access per-subscription preferences from this class
+ Assert.isTrue(key.startsWith(SHARED_PREFERENCES_PER_SUBSCRIPTION_PREFIX));
+ }
+
+ @Override
+ public void onUpgrade(final int oldVersion, final int newVersion) {
+ switch (oldVersion) {
+ case BuglePrefs.NO_SHARED_PREFERENCES_VERSION:
+ // Upgrade to version 1. Adding per-subscription shared prefs.
+ // Migrate values from the application-wide settings.
+ migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "delivery_reports",
+ R.string.delivery_reports_pref_key, R.bool.delivery_reports_pref_default);
+ migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "auto_retrieve_mms",
+ R.string.auto_retrieve_mms_pref_key, R.bool.auto_retrieve_mms_pref_default);
+ migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(),
+ "auto_retrieve_mms_when_roaming",
+ R.string.auto_retrieve_mms_when_roaming_pref_key,
+ R.bool.auto_retrieve_mms_when_roaming_pref_default);
+ migratePrefBooleanInternal(BuglePrefs.getApplicationPrefs(), "group_messaging",
+ R.string.group_mms_pref_key, R.bool.group_mms_pref_default);
+
+ if (PhoneUtils.getDefault().getActiveSubscriptionCount() == 1) {
+ migratePrefStringInternal(BuglePrefs.getApplicationPrefs(), "mms_phone_number",
+ R.string.mms_phone_number_pref_key, null);
+ }
+ }
+ }
+
+ private void migratePrefBooleanInternal(final BuglePrefs oldPrefs, final String oldKey,
+ final int newKeyResId, final int defaultValueResId) {
+ final Resources resources = Factory.get().getApplicationContext().getResources();
+ final boolean defaultValue = resources.getBoolean(defaultValueResId);
+ final boolean oldValue = oldPrefs.getBoolean(oldKey, defaultValue);
+
+ // Only migrate pref value if it's different than the default.
+ if (oldValue != defaultValue) {
+ putBoolean(resources.getString(newKeyResId), oldValue);
+ }
+ }
+
+ private void migratePrefStringInternal(final BuglePrefs oldPrefs, final String oldKey,
+ final int newKeyResId, final String defaultValue) {
+ final Resources resources = Factory.get().getApplicationContext().getResources();
+ final String oldValue = oldPrefs.getString(oldKey, defaultValue);
+
+ // Only migrate pref value if it's different than the default.
+ if (!TextUtils.equals(oldValue, defaultValue)) {
+ putString(resources.getString(newKeyResId), oldValue);
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/BugleWidgetPrefs.java b/src/com/android/messaging/util/BugleWidgetPrefs.java
new file mode 100644
index 0000000..63ba567
--- /dev/null
+++ b/src/com/android/messaging/util/BugleWidgetPrefs.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+
+/**
+ * Provides interface to access shared preferences used by bugle widgets.
+ */
+public class BugleWidgetPrefs extends BuglePrefsImpl {
+ /**
+ * Shared preferences name for preferences applicable to the entire app.
+ */
+ public static final String SHARED_PREFERENCES_WIDGET_NAME = "bugle_widgets";
+
+ public BugleWidgetPrefs(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getSharedPreferencesName() {
+ return SHARED_PREFERENCES_WIDGET_NAME;
+ }
+
+ @Override
+ public void onUpgrade(int oldVersion, int newVersion) {
+ }
+}
diff --git a/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java b/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java
new file mode 100644
index 0000000..6cf2f25
--- /dev/null
+++ b/src/com/android/messaging/util/ChangeDefaultSmsAppHelper.java
@@ -0,0 +1,157 @@
+/*
+ * 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.util;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.view.View;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.SnackBar;
+import com.android.messaging.ui.UIIntents;
+
+public class ChangeDefaultSmsAppHelper {
+ private Runnable mRunAfterMadeDefault;
+ private ChangeSmsAppSettingRunnable mChangeSmsAppSettingRunnable;
+
+ private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
+
+ /**
+ * When there's some condition that prevents an operation, such as sending a message,
+ * call warnOfMissingActionConditions to put up a toast and allow the user to repair
+ * that condition.
+ * @param sending - true if we're called during a sending operation
+ * @param runAfterMadeDefault - a runnable to run after the user responds
+ * positively to the condition prompt and resolves the condition. It is
+ * preferable to specify the value in {@link #handleChangeDefaultSmsResult}
+ * as that handles the case where the process gets restarted.
+ * If null, the user will be shown a generic toast message.
+ * @param composeView - compose view that may have the keyboard opened and focused
+ * @param rootView - if non-null, use this to attach a snackBar
+ * @param activity - calling activity
+ * @param fragment - calling fragment, may be null if called directly from an activity
+ */
+ public void warnOfMissingActionConditions(final boolean sending,
+ final Runnable runAfterMadeDefault,
+ final View composeView, final View rootView,
+ final Activity activity, final Fragment fragment) {
+ final PhoneUtils phoneUtils = PhoneUtils.getDefault();
+ final boolean isSmsCapable = phoneUtils.isSmsCapable();
+ final boolean hasPreferredSmsSim = phoneUtils.getHasPreferredSmsSim();
+ final boolean isDefaultSmsApp = phoneUtils.isDefaultSmsApp();
+
+ // Supports SMS?
+ if (!isSmsCapable) {
+ UiUtils.showToast(R.string.sms_disabled);
+
+ // Has a preferred sim?
+ } else if (!hasPreferredSmsSim) {
+ UiUtils.showToast(R.string.no_preferred_sim_selected);
+
+ // Is the default sms app?
+ } else if (!isDefaultSmsApp) {
+ mChangeSmsAppSettingRunnable = new ChangeSmsAppSettingRunnable(activity, fragment);
+ promptToChangeDefaultSmsApp(sending, runAfterMadeDefault,
+ composeView, rootView, activity);
+ }
+
+ LogUtil.w(LogUtil.BUGLE_TAG, "Unsatisfied action condition: "
+ + "isSmsCapable=" + isSmsCapable + ", "
+ + "hasPreferredSmsSim=" + hasPreferredSmsSim + ", "
+ + "isDefaultSmsApp=" + isDefaultSmsApp);
+ }
+
+ private void promptToChangeDefaultSmsApp(final boolean sending,
+ final Runnable runAfterMadeDefault,
+ final View composeView, final View rootView,
+ final Activity activity) {
+ if (composeView != null) {
+ // Avoid bug in system which puts soft keyboard over dialog after orientation change
+ ImeUtil.hideSoftInput(activity, composeView);
+ }
+ mRunAfterMadeDefault = runAfterMadeDefault;
+
+ if (rootView == null) {
+ // Immediately open the system "Change default SMS app?" dialog setting.
+ mChangeSmsAppSettingRunnable.run();
+ } else {
+ UiUtils.showSnackBarWithCustomAction(activity,
+ rootView,
+ activity.getString(sending ? R.string.requires_default_sms_app_to_send :
+ R.string.requires_default_sms_app),
+ SnackBar.Action.createCustomAction(mChangeSmsAppSettingRunnable,
+ activity.getString(R.string.requires_default_sms_change_button)),
+ null /* interactions */,
+ SnackBar.Placement.above(composeView));
+ }
+ }
+
+ private class ChangeSmsAppSettingRunnable implements Runnable {
+ private final Activity mActivity;
+ private final Fragment mFragment;
+
+ public ChangeSmsAppSettingRunnable(final Activity activity, final Fragment fragment) {
+ mActivity = activity;
+ mFragment = fragment;
+ }
+
+ @Override
+ public void run() {
+ try {
+ final Intent intent = UIIntents.get().getChangeDefaultSmsAppIntent(mActivity);
+ if (mFragment != null) {
+ mFragment.startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
+ } else {
+ mActivity.startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
+ }
+ } catch (final ActivityNotFoundException ex) {
+ // We shouldn't get here, but the monkey on JB MR0 can trigger it.
+ LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex);
+ UiUtils.showToastAtBottom(R.string.activity_not_found_message);
+ }
+ }
+ }
+
+ public void handleChangeDefaultSmsResult(
+ final int requestCode,
+ final int resultCode,
+ Runnable runAfterMadeDefault) {
+ Assert.isTrue(mRunAfterMadeDefault == null || runAfterMadeDefault == null);
+ if (runAfterMadeDefault == null) {
+ runAfterMadeDefault = mRunAfterMadeDefault;
+ }
+
+ if (requestCode == REQUEST_SET_DEFAULT_SMS_APP) {
+ if (resultCode == Activity.RESULT_OK) {
+ // mRunAfterMadeDefault can be null if it was set only in
+ // promptToChangeDefaultSmsApp, and the process subsequently restarted when the
+ // user momentarily switched to another app. In that case, we'll simply show a
+ // generic toast since we do not know what the runnable was supposed to do.
+ if (runAfterMadeDefault != null) {
+ runAfterMadeDefault.run();
+ } else {
+ UiUtils.showToast(R.string.toast_after_setting_default_sms_app);
+ }
+ }
+ mRunAfterMadeDefault = null; // don't want to accidentally run it again
+ }
+ }
+}
+
+
diff --git a/src/com/android/messaging/util/CircularArray.java b/src/com/android/messaging/util/CircularArray.java
new file mode 100644
index 0000000..db6cf12
--- /dev/null
+++ b/src/com/android/messaging/util/CircularArray.java
@@ -0,0 +1,111 @@
+/*
+ * 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.util;
+
+/**
+ * Very simple circular array implementation.
+ *
+ * @param <E> The element type of this list.
+ * @LibraryInternal
+ */
+public class CircularArray<E> {
+ private int mNextWriter;
+ private boolean mHasWrapped;
+ private int mMaxCount;
+ Object mList[];
+
+ /**
+ * Constructor for CircularArray.
+ *
+ * @param count Max elements to hold in the list.
+ */
+ public CircularArray(int count) {
+ mMaxCount = count;
+ clear();
+ }
+
+ /**
+ * Reset the list.
+ */
+ public void clear() {
+ mNextWriter = 0;
+ mHasWrapped = false;
+ mList = new Object[mMaxCount];
+ }
+
+ /**
+ * Add an element to the end of the list.
+ *
+ * @param object The object to add.
+ */
+ public void add(E object) {
+ mList[mNextWriter] = object;
+ ++mNextWriter;
+ if (mNextWriter == mMaxCount) {
+ mNextWriter = 0;
+ mHasWrapped = true;
+ }
+ }
+
+ /**
+ * Get the number of elements in the list. This will be 0 <= returned count <= max count
+ *
+ * @return Elements in the circular list.
+ */
+ public int count() {
+ if (mHasWrapped) {
+ return mMaxCount;
+ } else {
+ return mNextWriter;
+ }
+ }
+
+ /**
+ * Return null if the list hasn't wrapped yet. Otherwise return the next object that would be
+ * overwritten. Can be useful to avoid extra allocations.
+ *
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ public E getFree() {
+ if (!mHasWrapped) {
+ return null;
+ } else {
+ return (E) mList[mNextWriter];
+ }
+ }
+
+ /**
+ * Get the object at index. Index 0 is the oldest item inserted into the list. Index (count() -
+ * 1) is the newest.
+ *
+ * @param index Index to retrieve.
+ * @return Object at index.
+ */
+ @SuppressWarnings("unchecked")
+ public E get(int index) {
+ if (mHasWrapped) {
+ int wrappedIndex = index + mNextWriter;
+ if (wrappedIndex >= mMaxCount) {
+ wrappedIndex -= mMaxCount;
+ }
+ return (E) mList[wrappedIndex];
+ } else {
+ return (E) mList[index];
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/ConnectivityUtil.java b/src/com/android/messaging/util/ConnectivityUtil.java
new file mode 100644
index 0000000..49f6e0a
--- /dev/null
+++ b/src/com/android/messaging/util/ConnectivityUtil.java
@@ -0,0 +1,247 @@
+/*
+ * 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.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+
+public class ConnectivityUtil {
+ // Assume not connected until informed differently
+ private volatile int mCurrentServiceState = ServiceState.STATE_POWER_OFF;
+
+ private final TelephonyManager mTelephonyManager;
+ private final Context mContext;
+ private final ConnectivityBroadcastReceiver mReceiver;
+ private final ConnectivityManager mConnMgr;
+
+ private ConnectivityListener mListener;
+ private final IntentFilter mIntentFilter;
+
+ public interface ConnectivityListener {
+ public void onConnectivityStateChanged(final Context context, final Intent intent);
+ public void onPhoneStateChanged(final Context context, int serviceState);
+ }
+
+ public ConnectivityUtil(final Context context) {
+ mContext = context;
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mConnMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mReceiver = new ConnectivityBroadcastReceiver();
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ }
+
+ public int getCurrentServiceState() {
+ return mCurrentServiceState;
+ }
+
+ private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(final ServiceState serviceState) {
+ if (mCurrentServiceState != serviceState.getState()) {
+ mCurrentServiceState = serviceState.getState();
+ onPhoneStateChanged(mCurrentServiceState);
+ }
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(final int state) {
+ mCurrentServiceState = (state == TelephonyManager.DATA_DISCONNECTED) ?
+ ServiceState.STATE_OUT_OF_SERVICE : ServiceState.STATE_IN_SERVICE;
+ }
+ };
+
+ private void onPhoneStateChanged(final int serviceState) {
+ final ConnectivityListener listener = mListener;
+ if (listener != null) {
+ listener.onPhoneStateChanged(mContext, serviceState);
+ }
+ }
+
+ private void onConnectivityChanged(final Context context, final Intent intent) {
+ final ConnectivityListener listener = mListener;
+ if (listener != null) {
+ listener.onConnectivityStateChanged(context, intent);
+ }
+ }
+
+ public void register(final ConnectivityListener listener) {
+ Assert.isTrue(mListener == null || mListener == listener);
+ if (mListener == null) {
+ if (mTelephonyManager != null) {
+ mCurrentServiceState = (PhoneUtils.getDefault().isAirplaneModeOn() ?
+ ServiceState.STATE_POWER_OFF : ServiceState.STATE_IN_SERVICE);
+ mTelephonyManager.listen(mPhoneStateListener,
+ PhoneStateListener.LISTEN_SERVICE_STATE);
+ }
+ if (mConnMgr != null) {
+ mContext.registerReceiver(mReceiver, mIntentFilter);
+ }
+ }
+ mListener = listener;
+ }
+
+ public void unregister() {
+ if (mListener != null) {
+ if (mTelephonyManager != null) {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mCurrentServiceState = ServiceState.STATE_POWER_OFF;
+ }
+ if (mConnMgr != null) {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+ mListener = null;
+ }
+
+ /**
+ * Connectivity change broadcast receiver. This gets the network connectivity updates.
+ * In case we don't get the active connectivity when we first acquire the network,
+ * this receiver will notify us when it is connected, so to unblock the waiting thread
+ * which is sending the message.
+ */
+ public class ConnectivityBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (!intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ return;
+ }
+
+ onConnectivityChanged(context, intent);
+ }
+ }
+
+ private int mSignalLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+
+ // We use a separate instance than mPhoneStateListener because the lifetimes are different.
+ private final PhoneStateListener mSignalStrengthListener = new PhoneStateListener() {
+ @Override
+ public void onSignalStrengthsChanged(final SignalStrength signalStrength) {
+ mSignalLevel = getLevel(signalStrength);
+ }
+ };
+
+ public void registerForSignalStrength() {
+ mTelephonyManager.listen(
+ mSignalStrengthListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ }
+
+ public void unregisterForSignalStrength() {
+ mTelephonyManager.listen(mSignalStrengthListener, PhoneStateListener.LISTEN_NONE);
+ }
+
+ /**
+ * @param subId This is ignored because TelephonyManager does not support it.
+ * @return Signal strength as level 0..4
+ */
+ public int getSignalLevel(final int subId) {
+ return mSignalLevel;
+ }
+
+ private static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+ private static final int SIGNAL_STRENGTH_POOR = 1;
+ private static final int SIGNAL_STRENGTH_MODERATE = 2;
+ private static final int SIGNAL_STRENGTH_GOOD = 3;
+ private static final int SIGNAL_STRENGTH_GREAT = 4;
+
+ private static final int GSM_SIGNAL_STRENGTH_GREAT = 12;
+ private static final int GSM_SIGNAL_STRENGTH_GOOD = 8;
+ private static final int GSM_SIGNAL_STRENGTH_MODERATE = 8;
+
+ private static int getLevel(final SignalStrength signalStrength) {
+ if (signalStrength.isGsm()) {
+ // From frameworks/base/telephony/java/android/telephony/CellSignalStrengthGsm.java
+
+ // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+ // asu = 0 (-113dB or less) is very weak
+ // signal, its better to show 0 bars to the user in such cases.
+ // asu = 99 is a special case, where the signal strength is unknown.
+ final int asu = signalStrength.getGsmSignalStrength();
+ if (asu <= 2 || asu == 99) return SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ else if (asu >= GSM_SIGNAL_STRENGTH_GREAT) return SIGNAL_STRENGTH_GREAT;
+ else if (asu >= GSM_SIGNAL_STRENGTH_GOOD) return SIGNAL_STRENGTH_GOOD;
+ else if (asu >= GSM_SIGNAL_STRENGTH_MODERATE) return SIGNAL_STRENGTH_MODERATE;
+ else return SIGNAL_STRENGTH_POOR;
+ } else {
+ // From frameworks/base/telephony/java/android/telephony/CellSignalStrengthCdma.java
+
+ final int cdmaLevel = getCdmaLevel(signalStrength);
+ final int evdoLevel = getEvdoLevel(signalStrength);
+ if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ /* We don't know evdo, use cdma */
+ return getCdmaLevel(signalStrength);
+ } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ /* We don't know cdma, use evdo */
+ return getEvdoLevel(signalStrength);
+ } else {
+ /* We know both, use the lowest level */
+ return cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
+ }
+ }
+ }
+
+ /**
+ * Get cdma as level 0..4
+ */
+ private static int getCdmaLevel(final SignalStrength signalStrength) {
+ final int cdmaDbm = signalStrength.getCdmaDbm();
+ final int cdmaEcio = signalStrength.getCdmaEcio();
+ int levelDbm;
+ int levelEcio;
+ if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT;
+ else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD;
+ else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE;
+ else if (cdmaDbm >= -100) levelDbm = SIGNAL_STRENGTH_POOR;
+ else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ // Ec/Io are in dB*10
+ if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT;
+ else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD;
+ else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE;
+ else if (cdmaEcio >= -150) levelEcio = SIGNAL_STRENGTH_POOR;
+ else levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ final int level = (levelDbm < levelEcio) ? levelDbm : levelEcio;
+ return level;
+ }
+ /**
+ * Get Evdo as level 0..4
+ */
+ private static int getEvdoLevel(final SignalStrength signalStrength) {
+ final int evdoDbm = signalStrength.getEvdoDbm();
+ final int evdoSnr = signalStrength.getEvdoSnr();
+ int levelEvdoDbm;
+ int levelEvdoSnr;
+ if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT;
+ else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD;
+ else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE;
+ else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR;
+ else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT;
+ else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD;
+ else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE;
+ else if (evdoSnr >= 1) levelEvdoSnr = SIGNAL_STRENGTH_POOR;
+ else levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ final int level = (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr;
+ return level;
+ }
+}
diff --git a/src/com/android/messaging/util/ContactRecipientEntryUtils.java b/src/com/android/messaging/util/ContactRecipientEntryUtils.java
new file mode 100644
index 0000000..78c6ffd
--- /dev/null
+++ b/src/com/android/messaging/util/ContactRecipientEntryUtils.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util;
+
+import android.net.Uri;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.text.TextUtils;
+
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.BugleRecipientEntry;
+import com.android.messaging.datamodel.data.ParticipantData;
+
+/**
+ * Provides utility methods around creating RecipientEntry instance specific to Bugle's needs.
+ */
+public class ContactRecipientEntryUtils {
+ /**
+ * A special contact id for generated contacts with no display name (number only) and avatar.
+ * By default, the chips UI doesn't load any avatar for chips with no display name, or where
+ * the display name is the same as phone number (which is true for unknown contacts).
+ * Since Bugle always generate a default avatar for all contacts, this is used to replace
+ * those default generated chips with a phone number and no avatars.
+ */
+ private static final long CONTACT_ID_NUMBER_WITH_AVATAR = -1000;
+
+ /**
+ * A generated special contact which says "Send to xxx" in the contact list, which allows
+ * a user to direct send an SMS to a number that was manually typed in.
+ */
+ private static final long CONTACT_ID_SENDTO_DESTINATION = -1001;
+
+ /**
+ * Construct a special "Send to xxx" entry for a given destination.
+ */
+ public static RecipientEntry constructSendToDestinationEntry(final String destination) {
+ return constructSpecialRecipientEntry(destination, CONTACT_ID_SENDTO_DESTINATION);
+ }
+
+ /**
+ * Construct a generated contact entry but with rendered avatar.
+ */
+ public static RecipientEntry constructNumberWithAvatarEntry(final String destination) {
+ return constructSpecialRecipientEntry(destination, CONTACT_ID_NUMBER_WITH_AVATAR);
+ }
+
+ private static RecipientEntry constructSpecialRecipientEntry(final String destination,
+ final long contactId) {
+ // For the send-to-destination (e.g. "Send to xxx" in the auto-complete drop-down)
+ // we want to show a default avatar with a static background so that it doesn't flicker
+ // as the user types.
+ final Uri avatarUri = contactId == CONTACT_ID_SENDTO_DESTINATION ?
+ AvatarUriUtil.DEFAULT_BACKGROUND_AVATAR : null;
+ return BugleRecipientEntry.constructTopLevelEntry(null, DisplayNameSources.STRUCTURED_NAME,
+ destination, RecipientEntry.INVALID_DESTINATION_TYPE, null, contactId,
+ null, contactId, avatarUri, true, null);
+ }
+
+ /**
+ * Gets the display name for contact list only. For most cases this is the same as the normal
+ * contact name, but there are cases where these two differ. For example, for the
+ * send to typed number item, we'd like to show "Send to xxx" in the contact list. However,
+ * when this item is actually added to the chips edit box, we would like to show just the
+ * phone number (i.e. no display name).
+ */
+ public static String getDisplayNameForContactList(final RecipientEntry entry) {
+ if (entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION) {
+ return Factory.get().getApplicationContext().getResources().getString(
+ R.string.contact_list_send_to_text, formatDestination(entry));
+ } else if (!TextUtils.isEmpty(entry.getDisplayName())) {
+ return entry.getDisplayName();
+ } else {
+ return formatDestination(entry);
+ }
+ }
+
+ public static String formatDestination(final RecipientEntry entry) {
+ return PhoneUtils.getDefault().formatForDisplay(entry.getDestination());
+ }
+
+ /**
+ * Returns true if the given entry has only avatar and number
+ */
+ public static boolean isAvatarAndNumberOnlyContact(final RecipientEntry entry) {
+ return entry.getContactId() == CONTACT_ID_NUMBER_WITH_AVATAR;
+ }
+
+ /**
+ * Returns true if the given entry is a special send to number item.
+ */
+ public static boolean isSendToDestinationContact(final RecipientEntry entry) {
+ return entry.getContactId() == CONTACT_ID_SENDTO_DESTINATION;
+ }
+
+ /**
+ * Returns true if the given participant is a special send to number item.
+ */
+ public static boolean isSendToDestinationContact(final ParticipantData participant) {
+ return participant.getContactId() == CONTACT_ID_SENDTO_DESTINATION;
+ }
+}
diff --git a/src/com/android/messaging/util/ContactUtil.java b/src/com/android/messaging/util/ContactUtil.java
new file mode 100644
index 0000000..8555889
--- /dev/null
+++ b/src/com/android/messaging/util/ContactUtil.java
@@ -0,0 +1,525 @@
+/*
+ * 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.util;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.Profile;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.ex.chips.RecipientEntry;
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.CursorQueryData;
+import com.android.messaging.datamodel.FrequentContactsCursorQueryData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Utility class including logic to list, filter, and lookup phone and emails in CP2.
+ */
+@VisibleForTesting
+public class ContactUtil {
+
+ /**
+ * Index of different columns in phone or email queries. All queries below should confirm to
+ * this column content and ordering so that caller can use the uniformed way to process
+ * returned cursors.
+ */
+ public static final int INDEX_CONTACT_ID = 0;
+ public static final int INDEX_DISPLAY_NAME = 1;
+ public static final int INDEX_PHOTO_URI = 2;
+ public static final int INDEX_PHONE_EMAIL = 3;
+ public static final int INDEX_PHONE_EMAIL_TYPE = 4;
+ public static final int INDEX_PHONE_EMAIL_LABEL = 5;
+
+ // An optional lookup_id column used by PhoneLookupQuery that is needed when querying for
+ // contact information.
+ public static final int INDEX_LOOKUP_KEY = 6;
+
+ // An optional _id column to query results that need to be displayed in a list view.
+ public static final int INDEX_DATA_ID = 7;
+
+ // An optional sort_key column for displaying contact section labels.
+ public static final int INDEX_SORT_KEY = 8;
+
+ // Lookup key column index specific to frequent contacts query.
+ public static final int INDEX_LOOKUP_KEY_FREQUENT = 3;
+
+ /**
+ * Constants for listing and filtering phones.
+ */
+ public static class PhoneQuery {
+ public static final String SORT_KEY = Phone.SORT_KEY_PRIMARY;
+
+ public static final String[] PROJECTION = new String[] {
+ Phone.CONTACT_ID, // 0
+ Phone.DISPLAY_NAME_PRIMARY, // 1
+ Phone.PHOTO_THUMBNAIL_URI, // 2
+ Phone.NUMBER, // 3
+ Phone.TYPE, // 4
+ Phone.LABEL, // 5
+ Phone.LOOKUP_KEY, // 6
+ Phone._ID, // 7
+ PhoneQuery.SORT_KEY, // 8
+ };
+ }
+
+ /**
+ * Constants for looking up phone numbers.
+ */
+ public static class PhoneLookupQuery {
+ public static final String[] PROJECTION = new String[] {
+ // The _ID field points to the contact id of the content
+ PhoneLookup._ID, // 0
+ PhoneLookup.DISPLAY_NAME, // 1
+ PhoneLookup.PHOTO_THUMBNAIL_URI, // 2
+ PhoneLookup.NUMBER, // 3
+ PhoneLookup.TYPE, // 4
+ PhoneLookup.LABEL, // 5
+ PhoneLookup.LOOKUP_KEY, // 6
+ // The data id is not included as part of the projection since it's not part of
+ // PhoneLookup. This is okay because the _id field serves as both the data id and
+ // contact id. Also we never show the results directly in a list view so we are not
+ // concerned about duplicated _id's (namely, the same contact has two same phone
+ // numbers)
+ };
+ }
+
+ public static class FrequentContactQuery {
+ public static final String[] PROJECTION = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME, // 1
+ Contacts.PHOTO_URI, // 2
+ Phone.LOOKUP_KEY, // 3
+ };
+ }
+
+ /**
+ * Constants for listing and filtering emails.
+ */
+ public static class EmailQuery {
+ public static final String SORT_KEY = Email.SORT_KEY_PRIMARY;
+
+ public static final String[] PROJECTION = new String[] {
+ Email.CONTACT_ID, // 0
+ Email.DISPLAY_NAME_PRIMARY, // 1
+ Email.PHOTO_THUMBNAIL_URI, // 2
+ Email.ADDRESS, // 3
+ Email.TYPE, // 4
+ Email.LABEL, // 5
+ Email.LOOKUP_KEY, // 6
+ Email._ID, // 7
+ EmailQuery.SORT_KEY, // 8
+ };
+ }
+
+ public static final int INDEX_SELF_QUERY_LOOKUP_KEY = 3;
+
+ /**
+ * Constants for querying self from CP2.
+ */
+ public static class SelfQuery {
+ public static final String[] PROJECTION = new String[] {
+ Profile._ID, // 0
+ Profile.DISPLAY_NAME_PRIMARY, // 1
+ Profile.PHOTO_THUMBNAIL_URI, // 2
+ Profile.LOOKUP_KEY // 3
+ // Phone number, type, label and data_id is not provided in this projection since
+ // Profile CONTENT_URI doesn't include this information. Also, we don't need it
+ // we just need the name and avatar url.
+ };
+ }
+
+ public static class StructuredNameQuery {
+ public static final String[] PROJECTION = new String[] {
+ StructuredName.DISPLAY_NAME,
+ StructuredName.GIVEN_NAME,
+ StructuredName.FAMILY_NAME,
+ StructuredName.PREFIX,
+ StructuredName.MIDDLE_NAME,
+ StructuredName.SUFFIX
+ };
+ }
+
+ public static final int INDEX_STRUCTURED_NAME_DISPLAY_NAME = 0;
+ public static final int INDEX_STRUCTURED_NAME_GIVEN_NAME = 1;
+ public static final int INDEX_STRUCTURED_NAME_FAMILY_NAME = 2;
+ public static final int INDEX_STRUCTURED_NAME_PREFIX = 3;
+ public static final int INDEX_STRUCTURED_NAME_MIDDLE_NAME = 4;
+ public static final int INDEX_STRUCTURED_NAME_SUFFIX = 5;
+
+ public static final long INVALID_CONTACT_ID = -1;
+
+ /**
+ * This class is static. No need to create an instance.
+ */
+ private ContactUtil() {
+ }
+
+ /**
+ * Shows a contact card or add to contacts dialog for the given contact info
+ * @param view The view whose click triggered this to show
+ * @param contactId The id of the contact in the android contacts DB
+ * @param contactLookupKey The lookup key from contacts DB
+ * @param avatarUri Uri to the avatar image if available
+ * @param normalizedDestination The normalized phone number or email
+ */
+ public static void showOrAddContact(final View view, final long contactId,
+ final String contactLookupKey, final Uri avatarUri,
+ final String normalizedDestination) {
+ if (contactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
+ && !TextUtils.isEmpty(contactLookupKey)) {
+ final Uri lookupUri =
+ ContactsContract.Contacts.getLookupUri(contactId, contactLookupKey);
+ ContactsContract.QuickContact.showQuickContact(view.getContext(), view, lookupUri,
+ ContactsContract.QuickContact.MODE_LARGE, null);
+ } else if (!TextUtils.isEmpty(normalizedDestination) && !TextUtils.equals(
+ normalizedDestination, ParticipantData.getUnknownSenderDestination())) {
+ final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
+ view.getContext(), avatarUri, normalizedDestination);
+ dialog.show();
+ }
+ }
+
+ @VisibleForTesting
+ public static CursorQueryData getSelf(final Context context) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+ return new CursorQueryData(context, Profile.CONTENT_URI, SelfQuery.PROJECTION, null, null,
+ null);
+ }
+
+ /**
+ * Get a list of phones sorted by contact name. One contact may have multiple phones.
+ * In that case, each phone will be returned as a separate record in the result cursor.
+ */
+ @VisibleForTesting
+ public static CursorQueryData getPhones(final Context context) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ // The AOSP Contacts provider allows adding a ContactsContract.REMOVE_DUPLICATE_ENTRIES
+ // query parameter that removes duplicate (raw) numbers. Unfortunately, we can't use that
+ // because it causes the some phones' contacts provider to return incorrect sections.
+ final Uri uri = Phone.CONTENT_URI.buildUpon().appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
+ .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true")
+ .build();
+
+ return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null,
+ PhoneQuery.SORT_KEY);
+ }
+
+ /**
+ * Lookup a destination (phone, email). Supplied destination should be a relatively complete
+ * one for this to succeed. PhoneLookup / EmailLookup URI will apply some smartness to do a
+ * loose match to see whether there is a contact that matches this destination.
+ */
+ public static CursorQueryData lookupDestination(final Context context,
+ final String destination) {
+ if (MmsSmsUtils.isEmailAddress(destination)) {
+ return ContactUtil.lookupEmail(context, destination);
+ } else {
+ return ContactUtil.lookupPhone(context, destination);
+ }
+ }
+
+ /**
+ * Returns whether the search text indicates an email based search or a phone number based one.
+ */
+ private static boolean shouldFilterForEmail(final String searchText) {
+ return searchText != null && searchText.contains("@");
+ }
+
+ /**
+ * Get a list of destinations (phone, email) matching the partial destination.
+ */
+ public static CursorQueryData filterDestination(final Context context,
+ final String destination) {
+ if (shouldFilterForEmail(destination)) {
+ return ContactUtil.filterEmails(context, destination);
+ } else {
+ return ContactUtil.filterPhones(context, destination);
+ }
+ }
+
+ /**
+ * Get a list of phones matching a search criteria. The search may be on contact name or
+ * phone number. In case search is on contact name, all matching contact's phone number
+ * will be returned.
+ * NOTE: This is visible for testing only, clients should only call filterDestination() since
+ * we support email addresses as well.
+ */
+ @VisibleForTesting
+ public static CursorQueryData filterPhones(final Context context, final String query) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ final Uri uri = Phone.CONTENT_FILTER_URI.buildUpon()
+ .appendPath(query).appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
+ .build();
+
+ return new CursorQueryData(context, uri, PhoneQuery.PROJECTION, null, null,
+ PhoneQuery.SORT_KEY);
+ }
+
+ /**
+ * Lookup a phone based on a phone number. Supplied phone should be a relatively complete
+ * phone number for this to succeed. PhoneLookup URI will apply some smartness to do a
+ * loose match to see whether there is a contact that matches this phone.
+ * NOTE: This is visible for testing only, clients should only call lookupDestination() since
+ * we support email addresses as well.
+ */
+ @VisibleForTesting
+ public static CursorQueryData lookupPhone(final Context context, final String phone) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ final Uri uri = getPhoneLookupUri().buildUpon()
+ .appendPath(phone).build();
+
+ return new CursorQueryData(context, uri, PhoneLookupQuery.PROJECTION, null, null, null);
+ }
+
+ /**
+ * Get frequently contacted people. This queries for Contacts.CONTENT_STREQUENT_URI, which
+ * includes both starred or frequently contacted people.
+ */
+ public static CursorQueryData getFrequentContacts(final Context context) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ return new FrequentContactsCursorQueryData(context, FrequentContactQuery.PROJECTION,
+ null, null, null);
+ }
+
+ /**
+ * Get a list of emails matching a search criteria. In Bugle, since email is not a common
+ * usage scenario, we should only do email search after user typed in a query indicating
+ * an intention to search by email (for example, "joe@").
+ * NOTE: This is visible for testing only, clients should only call filterDestination() since
+ * we support email addresses as well.
+ */
+ @VisibleForTesting
+ public static CursorQueryData filterEmails(final Context context, final String query) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ final Uri uri = Email.CONTENT_FILTER_URI.buildUpon()
+ .appendPath(query).appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
+ .build();
+
+ return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null,
+ EmailQuery.SORT_KEY);
+ }
+
+ /**
+ * Lookup emails based a complete email address. Since there is no special logic needed for
+ * email lookup, this simply calls filterEmails.
+ * NOTE: This is visible for testing only, clients should only call lookupDestination() since
+ * we support email addresses as well.
+ */
+ @VisibleForTesting
+ public static CursorQueryData lookupEmail(final Context context, final String email) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ final Uri uri = getEmailContentLookupUri().buildUpon()
+ .appendPath(email).appendQueryParameter(
+ ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
+ .build();
+
+ return new CursorQueryData(context, uri, EmailQuery.PROJECTION, null, null,
+ EmailQuery.SORT_KEY);
+ }
+
+ /**
+ * Looks up the structured name for a contact.
+ *
+ * @param primaryOnly If there are multiple raw contacts, set this flag to return only the
+ * name used as the primary display name. Otherwise, this method returns all names.
+ */
+ private static CursorQueryData lookupStructuredName(final Context context, final long contactId,
+ final boolean primaryOnly) {
+ if (!ContactUtil.hasReadContactsPermission()) {
+ return CursorQueryData.getEmptyQueryData();
+ }
+
+ // TODO: Handle enterprise contacts
+ final Uri uri = ContactsContract.Contacts.CONTENT_URI.buildUpon()
+ .appendPath(String.valueOf(contactId))
+ .appendPath(ContactsContract.Contacts.Data.CONTENT_DIRECTORY).build();
+
+ String selection = ContactsContract.Data.MIMETYPE + "=?";
+ final String[] selectionArgs = {
+ StructuredName.CONTENT_ITEM_TYPE
+ };
+ if (primaryOnly) {
+ selection += " AND " + Contacts.DISPLAY_NAME_PRIMARY + "="
+ + StructuredName.DISPLAY_NAME;
+ }
+
+ return new CursorQueryData(context, uri,
+ StructuredNameQuery.PROJECTION, selection, selectionArgs, null);
+ }
+
+ /**
+ * Looks up the first name for a contact. If there are multiple raw
+ * contacts, this returns the name that is associated with the contact's
+ * primary display name. The name is null when contact id does not exist
+ * (possibly because it is a corp contact) or it does not have a first name.
+ */
+ public static String lookupFirstName(final Context context, final long contactId) {
+ if (isEnterpriseContactId(contactId)) {
+ return null;
+ }
+ String firstName = null;
+ Cursor nameCursor = null;
+ try {
+ nameCursor = ContactUtil.lookupStructuredName(context, contactId, true)
+ .performSynchronousQuery();
+ if (nameCursor != null && nameCursor.moveToFirst()) {
+ firstName = nameCursor.getString(ContactUtil.INDEX_STRUCTURED_NAME_GIVEN_NAME);
+ }
+ } finally {
+ if (nameCursor != null) {
+ nameCursor.close();
+ }
+ }
+ return firstName;
+ }
+
+ /**
+ * Creates a RecipientEntry from the provided data fields (from the contacts cursor).
+ * @param firstLevel whether this item is the first entry of this contact in the list.
+ */
+ public static RecipientEntry createRecipientEntry(final String displayName,
+ final int displayNameSource, final String destination, final int destinationType,
+ final String destinationLabel, final long contactId, final String lookupKey,
+ final long dataId, final String photoThumbnailUri, final boolean firstLevel) {
+ if (firstLevel) {
+ return RecipientEntry.constructTopLevelEntry(displayName, displayNameSource,
+ destination, destinationType, destinationLabel, contactId, null, dataId,
+ photoThumbnailUri, true, lookupKey);
+ } else {
+ return RecipientEntry.constructSecondLevelEntry(displayName, displayNameSource,
+ destination, destinationType, destinationLabel, contactId, null, dataId,
+ photoThumbnailUri, true, lookupKey);
+ }
+ }
+
+ /**
+ * Creates a RecipientEntry for PhoneQuery result. The result is then displayed in the
+ * contact search drop down or as replacement chips in the chips edit box.
+ */
+ public static RecipientEntry createRecipientEntryForPhoneQuery(final Cursor cursor,
+ final boolean isFirstLevel) {
+ final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
+ final String displayName = cursor.getString(
+ ContactUtil.INDEX_DISPLAY_NAME);
+ final String photoThumbnailUri = cursor.getString(
+ ContactUtil.INDEX_PHOTO_URI);
+ final String destination = cursor.getString(
+ ContactUtil.INDEX_PHONE_EMAIL);
+ final int destinationType = cursor.getInt(
+ ContactUtil.INDEX_PHONE_EMAIL_TYPE);
+ final String destinationLabel = cursor.getString(
+ ContactUtil.INDEX_PHONE_EMAIL_LABEL);
+ final String lookupKey = cursor.getString(
+ ContactUtil.INDEX_LOOKUP_KEY);
+
+ // PhoneQuery uses the contact id as the data id ("_id").
+ final long dataId = contactId;
+
+ return createRecipientEntry(displayName,
+ DisplayNameSources.STRUCTURED_NAME, destination, destinationType,
+ destinationLabel, contactId, lookupKey, dataId, photoThumbnailUri,
+ isFirstLevel);
+ }
+
+ /**
+ * Returns if a given contact id is valid.
+ */
+ public static boolean isValidContactId(final long contactId) {
+ return contactId >= 0;
+ }
+
+ /**
+ * Returns if a given contact id belongs to managed profile.
+ */
+ public static boolean isEnterpriseContactId(final long contactId) {
+ return isWorkProfileSupported()
+ && ContactsContract.Contacts.isEnterpriseContactId(contactId);
+ }
+
+ /**
+ * Returns if managed profile is supported.
+ */
+ public static boolean isWorkProfileSupported() {
+ final PackageManager pm = Factory.get().getApplicationContext().getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
+ }
+
+ /**
+ * Returns Email lookup uri that will query both primary and corp profile
+ */
+ private static Uri getEmailContentLookupUri() {
+ if (isWorkProfileSupported() && OsUtil.isAtLeastM()) {
+ // TODO: use Email.ENTERPRISE_CONTENT_LOOKUP_URI, which will be available in M SDK API
+ return Uri.parse("content://com.android.contacts/data/emails/lookup_enterprise");
+ }
+ return Email.CONTENT_LOOKUP_URI;
+ }
+
+ /**
+ * Returns PhoneLookup URI.
+ */
+ public static Uri getPhoneLookupUri() {
+ // Apply it to M only
+ if (isWorkProfileSupported() && OsUtil.isAtLeastM()) {
+ return PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI;
+ }
+ return PhoneLookup.CONTENT_FILTER_URI;
+ }
+
+ public static boolean hasReadContactsPermission() {
+ return OsUtil.hasPermission(Manifest.permission.READ_CONTACTS);
+ }
+}
diff --git a/src/com/android/messaging/util/ContentType.java b/src/com/android/messaging/util/ContentType.java
new file mode 100644
index 0000000..bb4a7b2
--- /dev/null
+++ b/src/com/android/messaging/util/ContentType.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 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.util;
+
+import android.webkit.MimeTypeMap;
+
+public final class ContentType {
+ public static String THREE_GPP_EXTENSION = "3gp";
+ public static String VIDEO_MP4_EXTENSION = "mp4";
+ // Default extension used when we don't know one.
+ public static String DEFAULT_EXTENSION = "dat";
+
+ public static final int TYPE_IMAGE = 0;
+ public static final int TYPE_VIDEO = 1;
+ public static final int TYPE_AUDIO = 2;
+ public static final int TYPE_VCARD = 3;
+ public static final int TYPE_OTHER = 4;
+
+ public static final String ANY_TYPE = "*/*";
+ public static final String MMS_MESSAGE = "application/vnd.wap.mms-message";
+ // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+ // Notification.ind, Delivery.ind).
+ public static final String MMS_GENERIC = "application/vnd.wap.mms-generic";
+ public static final String MMS_MULTIPART_MIXED = "application/vnd.wap.multipart.mixed";
+ public static final String MMS_MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+ public static final String MMS_MULTIPART_ALTERNATIVE =
+ "application/vnd.wap.multipart.alternative";
+
+ public static final String TEXT_PLAIN = "text/plain";
+ public static final String TEXT_HTML = "text/html";
+ public static final String TEXT_VCALENDAR = "text/x-vCalendar";
+ public static final String TEXT_VCARD = "text/x-vCard";
+
+ public static final String IMAGE_PREFIX = "image/";
+ public static final String IMAGE_UNSPECIFIED = "image/*";
+ public static final String IMAGE_JPEG = "image/jpeg";
+ public static final String IMAGE_JPG = "image/jpg";
+ public static final String IMAGE_GIF = "image/gif";
+ public static final String IMAGE_WBMP = "image/vnd.wap.wbmp";
+ public static final String IMAGE_PNG = "image/png";
+ public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp";
+
+ public static final String AUDIO_UNSPECIFIED = "audio/*";
+ public static final String AUDIO_AAC = "audio/aac";
+ public static final String AUDIO_AMR = "audio/amr";
+ public static final String AUDIO_IMELODY = "audio/imelody";
+ public static final String AUDIO_MID = "audio/mid";
+ public static final String AUDIO_MIDI = "audio/midi";
+ public static final String AUDIO_MP3 = "audio/mp3";
+ public static final String AUDIO_MPEG3 = "audio/mpeg3";
+ public static final String AUDIO_MPEG = "audio/mpeg";
+ public static final String AUDIO_MPG = "audio/mpg";
+ public static final String AUDIO_MP4 = "audio/mp4";
+ public static final String AUDIO_MP4_LATM = "audio/mp4-latm";
+ public static final String AUDIO_X_MID = "audio/x-mid";
+ public static final String AUDIO_X_MIDI = "audio/x-midi";
+ public static final String AUDIO_X_MP3 = "audio/x-mp3";
+ public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3";
+ public static final String AUDIO_X_MPEG = "audio/x-mpeg";
+ public static final String AUDIO_X_MPG = "audio/x-mpg";
+ public static final String AUDIO_3GPP = "audio/3gpp";
+ public static final String AUDIO_X_WAV = "audio/x-wav";
+ public static final String AUDIO_OGG = "application/ogg";
+
+ public static final String MULTIPART_MIXED = "multipart/mixed";
+
+ public static final String VIDEO_UNSPECIFIED = "video/*";
+ public static final String VIDEO_3GP = "video/3gp";
+ public static final String VIDEO_3GPP = "video/3gpp";
+ public static final String VIDEO_3G2 = "video/3gpp2";
+ public static final String VIDEO_H263 = "video/h263";
+ public static final String VIDEO_M4V = "video/m4v";
+ public static final String VIDEO_MP4 = "video/mp4";
+ public static final String VIDEO_MPEG = "video/mpeg";
+ public static final String VIDEO_MPEG4 = "video/mpeg4";
+ public static final String VIDEO_WEBM = "video/webm";
+
+ public static final String APP_SMIL = "application/smil";
+ public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml";
+ public static final String APP_XHTML = "application/xhtml+xml";
+
+ public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content";
+ public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+ // This class should never be instantiated.
+ private ContentType() {
+ }
+
+ public static boolean isTextType(final String contentType) {
+ return TEXT_PLAIN.equals(contentType)
+ || TEXT_HTML.equals(contentType)
+ || APP_WAP_XHTML.equals(contentType);
+ }
+
+ public static boolean isMediaType(final String contentType) {
+ return isImageType(contentType)
+ || isVideoType(contentType)
+ || isAudioType(contentType)
+ || isVCardType(contentType);
+ }
+
+ public static boolean isImageType(final String contentType) {
+ return (null != contentType) && contentType.startsWith(IMAGE_PREFIX);
+ }
+
+ public static boolean isAudioType(final String contentType) {
+ return (null != contentType) &&
+ (contentType.startsWith("audio/") || contentType.equalsIgnoreCase(AUDIO_OGG));
+ }
+
+ public static boolean isVideoType(final String contentType) {
+ return (null != contentType) && contentType.startsWith("video/");
+ }
+
+ public static boolean isVCardType(final String contentType) {
+ return (null != contentType) && contentType.equalsIgnoreCase(TEXT_VCARD);
+ }
+
+ public static boolean isDrmType(final String contentType) {
+ return (null != contentType)
+ && (contentType.equals(APP_DRM_CONTENT)
+ || contentType.equals(APP_DRM_MESSAGE));
+ }
+
+ public static boolean isUnspecified(final String contentType) {
+ return (null != contentType) && contentType.endsWith("*");
+ }
+
+ /**
+ * If the content type is a type which can be displayed in the conversation list as a preview.
+ */
+ public static boolean isConversationListPreviewableType(final String contentType) {
+ return ContentType.isAudioType(contentType) || ContentType.isVideoType(contentType) ||
+ ContentType.isImageType(contentType) || ContentType.isVCardType(contentType);
+ }
+
+ /**
+ * Given a filename, look at the extension and try and determine the mime type.
+ *
+ * @param fileName a filename to determine the type from, such as img1231.jpg
+ * @param contentTypeDefault type to use when the content type can't be determined from the file
+ * extension. It can be null or a type such as ContentType.IMAGE_UNSPECIFIED
+ * @return Content type of the extension.
+ */
+ public static String getContentTypeFromExtension(final String fileName,
+ final String contentTypeDefault) {
+ final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
+ final String extension = MimeTypeMap.getFileExtensionFromUrl(fileName);
+ String contentType = mimeTypeMap.getMimeTypeFromExtension(extension);
+ if (contentType == null) {
+ contentType = contentTypeDefault;
+ }
+ return contentType;
+ }
+
+ /**
+ * Get the common file extension for a given content type
+ * @param contentType The content type
+ * @return The extension without the .
+ */
+ public static String getExtension(final String contentType) {
+ if (VIDEO_MP4.equals(contentType)) {
+ return VIDEO_MP4_EXTENSION;
+ } else if (VIDEO_3GPP.equals(contentType)) {
+ return THREE_GPP_EXTENSION;
+ } else {
+ return DEFAULT_EXTENSION;
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/ConversationIdSet.java b/src/com/android/messaging/util/ConversationIdSet.java
new file mode 100644
index 0000000..75bf634
--- /dev/null
+++ b/src/com/android/messaging/util/ConversationIdSet.java
@@ -0,0 +1,69 @@
+/*
+ * 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.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Utility class to make it easy to store multiple conversation id strings in a single string
+ * with delimeters.
+ */
+public class ConversationIdSet extends HashSet<String> {
+ private static final String JOIN_DELIMITER = "|";
+ private static final String SPLIT_DELIMITER = "\\|";
+
+ public ConversationIdSet() {
+ super();
+ }
+
+ public ConversationIdSet(final Collection<String> asList) {
+ super(asList);
+ }
+
+ public String first() {
+ if (size() > 0) {
+ return iterator().next();
+ } else {
+ return null;
+ }
+ }
+
+ public static ConversationIdSet createSet(final String conversationIdSetString) {
+ ConversationIdSet set = null;
+ if (conversationIdSetString != null) {
+ set = new ConversationIdSet(Arrays.asList(conversationIdSetString.split(
+ SPLIT_DELIMITER)));
+ }
+ return set;
+ }
+
+ public String getDelimitedString() {
+ return OsUtil.joinFromSetWithDelimiter(this, JOIN_DELIMITER);
+ }
+
+ public static String join(final String conversationIdSet1, final String conversationIdSet2) {
+ String joined = null;
+ if (conversationIdSet1 == null) {
+ joined = conversationIdSet2;
+ } else if (conversationIdSet2 != null) {
+ joined = conversationIdSet1 + JOIN_DELIMITER + conversationIdSet2;
+ }
+ return joined;
+ }
+
+}
diff --git a/src/com/android/messaging/util/CubicBezierInterpolator.java b/src/com/android/messaging/util/CubicBezierInterpolator.java
new file mode 100644
index 0000000..317b3e6
--- /dev/null
+++ b/src/com/android/messaging/util/CubicBezierInterpolator.java
@@ -0,0 +1,120 @@
+/*
+ * 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.util;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Class that acts as an interpolator to match the cubic-bezier css timing function where p0 is
+ * fixed at 0,0 and p3 is fixed at 1,1
+ */
+public class CubicBezierInterpolator implements Interpolator {
+ private final float mX1;
+ private final float mY1;
+ private final float mX2;
+ private final float mY2;
+
+ public CubicBezierInterpolator(final float x1, final float y1, final float x2, final float y2) {
+ mX1 = x1;
+ mY1 = y1;
+ mX2 = x2;
+ mY2 = y2;
+ }
+
+ @Override
+ public float getInterpolation(float v) {
+ return getY(getTForXValue(v));
+ }
+
+ private float getX(final float t) {
+ return getCoordinate(t, mX1, mX2);
+ }
+
+ private float getY(final float t) {
+ return getCoordinate(t, mY1, mY2);
+ }
+
+ private float getCoordinate(float t, float p1, float p2) {
+ // Special case start and end.
+ if (t == 0.0f || t == 1.0f) {
+ return t;
+ }
+
+ // Step one - from 4 points to 3.
+ float ip0 = linearInterpolate(0, p1, t);
+ float ip1 = linearInterpolate(p1, p2, t);
+ float ip2 = linearInterpolate(p2, 1, t);
+
+ // Step two - from 3 points to 2.
+ ip0 = linearInterpolate(ip0, ip1, t);
+ ip1 = linearInterpolate(ip1, ip2, t);
+
+ // Final step - last point.
+ return linearInterpolate(ip0, ip1, t);
+ }
+
+ private float linearInterpolate(float a, float b, float progress) {
+ return a + (b - a) * progress;
+ }
+
+ private float getTForXValue(final float x) {
+ final float epsilon = 1e-6f;
+ final int iterations = 8;
+
+ if (x <= 0.0f) {
+ return 0.0f;
+ } else if (x >= 1.0f) {
+ return 1.0f;
+ }
+
+ // Try gradient descent to solve for t. If it works, it is very fast.
+ float t = x;
+ float minT = 0.0f;
+ float maxT = 1.0f;
+ float value = 0.0f;
+ for (int i = 0; i < iterations; i++) {
+ value = getX(t);
+ double derivative = (getX(t + epsilon) - value) / epsilon;
+ if (Math.abs(value - x) < epsilon) {
+ return t;
+ } else if (Math.abs(derivative) < epsilon) {
+ break;
+ } else {
+ if (value < x) {
+ minT = t;
+ } else {
+ maxT = t;
+ }
+ t -= (value - x) / derivative;
+ }
+ }
+
+ // If the gradient descent got stuck in a local minimum, e.g. because the
+ // derivative was close to 0, use an interval bisection instead.
+ for (int i = 0; Math.abs(value - x) > epsilon && i < iterations; i++) {
+ if (value < x) {
+ minT = t;
+ t = (t + maxT) / 2.0f;
+ } else {
+ maxT = t;
+ t = (t + minT) / 2.0f;
+ }
+ value = getX(t);
+ }
+ return t;
+ }
+}
diff --git a/src/com/android/messaging/util/Dates.java b/src/com/android/messaging/util/Dates.java
new file mode 100644
index 0000000..d012dfd
--- /dev/null
+++ b/src/com/android/messaging/util/Dates.java
@@ -0,0 +1,280 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Collection of date utilities.
+ */
+public class Dates {
+ public static final long SECOND_IN_MILLIS = 1000;
+ public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
+ public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
+ public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
+ public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+
+ // Flags to specify whether or not to use 12 or 24 hour mode.
+ // Callers of methods in this class should never have to specify these; this is really
+ // intended only for unit tests.
+ @SuppressWarnings("deprecation")
+ @VisibleForTesting public static final int FORCE_12_HOUR = DateUtils.FORMAT_12HOUR;
+ @SuppressWarnings("deprecation")
+ @VisibleForTesting public static final int FORCE_24_HOUR = DateUtils.FORMAT_24HOUR;
+
+ /**
+ * Private default constructor
+ */
+ private Dates() {
+ }
+
+ private static Context getContext() {
+ return Factory.get().getApplicationContext();
+ }
+ /**
+ * Get the relative time as a string
+ *
+ * @param time The time
+ *
+ * @return The relative time
+ */
+ public static CharSequence getRelativeTimeSpanString(final long time) {
+ final long now = System.currentTimeMillis();
+ if (now - time < DateUtils.MINUTE_IN_MILLIS) {
+ // Also fixes bug where posts appear in the future
+ return getContext().getResources().getText(R.string.posted_just_now);
+ }
+
+ // Workaround for b/5657035. The platform method {@link DateUtils#getRelativeTimeSpan()}
+ // passes a null context to other platform methods. However, on some devices, this
+ // context is dereferenced when it shouldn't be and an NPE is thrown. We catch that
+ // here and use a slightly less precise time.
+ try {
+ return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_RELATIVE).toString();
+ } catch (final NullPointerException npe) {
+ return getShortRelativeTimeSpanString(time);
+ }
+ }
+
+ public static CharSequence getConversationTimeString(final long time) {
+ return getTimeString(time, true /*abbreviated*/, false /*minPeriodToday*/);
+ }
+
+ public static CharSequence getMessageTimeString(final long time) {
+ return getTimeString(time, false /*abbreviated*/, false /*minPeriodToday*/);
+ }
+
+ public static CharSequence getWidgetTimeString(final long time, final boolean abbreviated) {
+ return getTimeString(time, abbreviated, true /*minPeriodToday*/);
+ }
+
+ public static CharSequence getFastScrollPreviewTimeString(final long time) {
+ return getTimeString(time, true /* abbreviated */, true /* minPeriodToday */);
+ }
+
+ public static CharSequence getMessageDetailsTimeString(final long time) {
+ final Context context = getContext();
+ int flags;
+ if (android.text.format.DateFormat.is24HourFormat(context)) {
+ flags = FORCE_24_HOUR;
+ } else {
+ flags = FORCE_12_HOUR;
+ }
+ return getOlderThanAYearTimestamp(time,
+ context.getResources().getConfiguration().locale, false /*abbreviated*/,
+ flags);
+ }
+
+ private static CharSequence getTimeString(final long time, final boolean abbreviated,
+ final boolean minPeriodToday) {
+ final Context context = getContext();
+ int flags;
+ if (android.text.format.DateFormat.is24HourFormat(context)) {
+ flags = FORCE_24_HOUR;
+ } else {
+ flags = FORCE_12_HOUR;
+ }
+ return getTimestamp(time, System.currentTimeMillis(), abbreviated,
+ context.getResources().getConfiguration().locale, flags, minPeriodToday);
+ }
+
+ @VisibleForTesting
+ public static CharSequence getTimestamp(final long time, final long now,
+ final boolean abbreviated, final Locale locale, final int flags,
+ final boolean minPeriodToday) {
+ final long timeDiff = now - time;
+
+ if (!minPeriodToday && timeDiff < DateUtils.MINUTE_IN_MILLIS) {
+ return getLessThanAMinuteOldTimeString(abbreviated);
+ } else if (!minPeriodToday && timeDiff < DateUtils.HOUR_IN_MILLIS) {
+ return getLessThanAnHourOldTimeString(timeDiff, flags);
+ } else if (getNumberOfDaysPassed(time, now) == 0) {
+ return getTodayTimeStamp(time, flags);
+ } else if (timeDiff < DateUtils.WEEK_IN_MILLIS) {
+ return getThisWeekTimestamp(time, locale, abbreviated, flags);
+ } else if (timeDiff < DateUtils.YEAR_IN_MILLIS) {
+ return getThisYearTimestamp(time, locale, abbreviated, flags);
+ } else {
+ return getOlderThanAYearTimestamp(time, locale, abbreviated, flags);
+ }
+ }
+
+ private static CharSequence getLessThanAMinuteOldTimeString(
+ final boolean abbreviated) {
+ return getContext().getResources().getText(
+ abbreviated ? R.string.posted_just_now : R.string.posted_now);
+ }
+
+ private static CharSequence getLessThanAnHourOldTimeString(final long timeDiff,
+ final int flags) {
+ final long count = (timeDiff / MINUTE_IN_MILLIS);
+ final String format = getContext().getResources().getQuantityString(
+ R.plurals.num_minutes_ago, (int) count);
+ return String.format(format, count);
+ }
+
+ private static CharSequence getTodayTimeStamp(final long time, final int flags) {
+ return DateUtils.formatDateTime(getContext(), time,
+ DateUtils.FORMAT_SHOW_TIME | flags);
+ }
+
+ private static CharSequence getExplicitFormattedTime(final long time, final int flags,
+ final String format24, final String format12) {
+ SimpleDateFormat formatter;
+ if ((flags & FORCE_24_HOUR) == FORCE_24_HOUR) {
+ formatter = new SimpleDateFormat(format24);
+ } else {
+ formatter = new SimpleDateFormat(format12);
+ }
+ return formatter.format(new Date(time));
+ }
+
+ private static CharSequence getThisWeekTimestamp(final long time,
+ final Locale locale, final boolean abbreviated, final int flags) {
+ final Context context = getContext();
+ if (abbreviated) {
+ return DateUtils.formatDateTime(context, time,
+ DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY | flags);
+ } else {
+ if (locale.equals(Locale.US)) {
+ return getExplicitFormattedTime(time, flags, "EEE HH:mm", "EEE h:mmaa");
+ } else {
+ return DateUtils.formatDateTime(context, time,
+ DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_ABBREV_WEEKDAY
+ | flags);
+ }
+ }
+ }
+
+ private static CharSequence getThisYearTimestamp(final long time, final Locale locale,
+ final boolean abbreviated, final int flags) {
+ final Context context = getContext();
+ if (abbreviated) {
+ return DateUtils.formatDateTime(context, time,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH
+ | DateUtils.FORMAT_NO_YEAR | flags);
+ } else {
+ if (locale.equals(Locale.US)) {
+ return getExplicitFormattedTime(time, flags, "MMM d, HH:mm", "MMM d, h:mmaa");
+ } else {
+ return DateUtils.formatDateTime(context, time,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_ABBREV_MONTH
+ | DateUtils.FORMAT_NO_YEAR
+ | flags);
+ }
+ }
+ }
+
+ private static CharSequence getOlderThanAYearTimestamp(final long time,
+ final Locale locale, final boolean abbreviated, final int flags) {
+ final Context context = getContext();
+ if (abbreviated) {
+ if (locale.equals(Locale.US)) {
+ return getExplicitFormattedTime(time, flags, "M/d/yy", "M/d/yy");
+ } else {
+ return DateUtils.formatDateTime(context, time,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
+ | DateUtils.FORMAT_NUMERIC_DATE);
+ }
+ } else {
+ if (locale.equals(Locale.US)) {
+ return getExplicitFormattedTime(time, flags, "M/d/yy, HH:mm", "M/d/yy, h:mmaa");
+ } else {
+ return DateUtils.formatDateTime(context, time,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR
+ | flags);
+ }
+ }
+ }
+
+ public static CharSequence getShortRelativeTimeSpanString(final long time) {
+ final long now = System.currentTimeMillis();
+ final long duration = Math.abs(now - time);
+
+ int resId;
+ long count;
+
+ final Context context = getContext();
+
+ if (duration < HOUR_IN_MILLIS) {
+ count = duration / MINUTE_IN_MILLIS;
+ resId = R.plurals.num_minutes_ago;
+ } else if (duration < DAY_IN_MILLIS) {
+ count = duration / HOUR_IN_MILLIS;
+ resId = R.plurals.num_hours_ago;
+ } else if (duration < WEEK_IN_MILLIS) {
+ count = getNumberOfDaysPassed(time, now);
+ resId = R.plurals.num_days_ago;
+ } else {
+ // Although we won't be showing a time, there is a bug on some devices that use
+ // the passed in context. On these devices, passing in a {@code null} context
+ // here will generate an NPE. See b/5657035.
+ return DateUtils.formatDateRange(context, time, time,
+ DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_ABBREV_RELATIVE);
+ }
+
+ final String format = context.getResources().getQuantityString(resId, (int) count);
+ return String.format(format, count);
+ }
+
+ private static synchronized long getNumberOfDaysPassed(final long date1, final long date2) {
+ if (sThenTime == null) {
+ sThenTime = new Time();
+ }
+ sThenTime.set(date1);
+ final int day1 = Time.getJulianDay(date1, sThenTime.gmtoff);
+ sThenTime.set(date2);
+ final int day2 = Time.getJulianDay(date2, sThenTime.gmtoff);
+ return Math.abs(day2 - day1);
+ }
+
+ private static Time sThenTime;
+}
diff --git a/src/com/android/messaging/util/DebugUtils.java b/src/com/android/messaging/util/DebugUtils.java
new file mode 100644
index 0000000..f2c1d65
--- /dev/null
+++ b/src/com/android/messaging/util/DebugUtils.java
@@ -0,0 +1,425 @@
+/*
+ * 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.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.media.MediaPlayer;
+import android.os.Environment;
+import android.telephony.SmsMessage;
+import android.text.TextUtils;
+import android.widget.ArrayAdapter;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.action.DumpDatabaseAction;
+import com.android.messaging.datamodel.action.LogTelephonyDatabaseAction;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.debug.DebugSmsMmsFromDumpFileDialogFragment;
+import com.google.common.io.ByteStreams;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+
+public class DebugUtils {
+ private static final String TAG = "bugle.util.DebugUtils";
+
+ private static boolean sDebugNoise;
+ private static boolean sDebugClassZeroSms;
+ private static MediaPlayer [] sMediaPlayer;
+ private static final Object sLock = new Object();
+
+ public static final int DEBUG_SOUND_SERVER_REQUEST = 0;
+ public static final int DEBUG_SOUND_DB_OP = 1;
+
+ public static void maybePlayDebugNoise(final Context context, final int sound) {
+ if (sDebugNoise) {
+ synchronized (sLock) {
+ try {
+ if (sMediaPlayer == null) {
+ sMediaPlayer = new MediaPlayer[2];
+ sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST] =
+ MediaPlayer.create(context, R.raw.server_request_debug);
+ sMediaPlayer[DEBUG_SOUND_DB_OP] =
+ MediaPlayer.create(context, R.raw.db_op_debug);
+ sMediaPlayer[DEBUG_SOUND_DB_OP].setVolume(1.0F, 1.0F);
+ sMediaPlayer[DEBUG_SOUND_SERVER_REQUEST].setVolume(0.3F, 0.3F);
+ }
+ if (sMediaPlayer[sound] != null) {
+ sMediaPlayer[sound].start();
+ }
+ } catch (final IllegalArgumentException e) {
+ LogUtil.e(TAG, "MediaPlayer exception", e);
+ } catch (final SecurityException e) {
+ LogUtil.e(TAG, "MediaPlayer exception", e);
+ } catch (final IllegalStateException e) {
+ LogUtil.e(TAG, "MediaPlayer exception", e);
+ }
+ }
+ }
+ }
+
+ public static boolean isDebugEnabled() {
+ return BugleGservices.get().getBoolean(BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES,
+ BugleGservicesKeys.ENABLE_DEBUGGING_FEATURES_DEFAULT);
+ }
+
+ public abstract static class DebugAction {
+ String mTitle;
+ public DebugAction(final String title) {
+ mTitle = title;
+ }
+
+ @Override
+ public String toString() {
+ return mTitle;
+ }
+
+ public abstract void run();
+ }
+
+ public static void showDebugOptions(final Activity host) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(host);
+
+ final ArrayAdapter<DebugAction> arrayAdapter = new ArrayAdapter<DebugAction>(
+ host, android.R.layout.simple_list_item_1);
+
+ arrayAdapter.add(new DebugAction("Dump Database") {
+ @Override
+ public void run() {
+ DumpDatabaseAction.dumpDatabase();
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("Log Telephony Data") {
+ @Override
+ public void run() {
+ LogTelephonyDatabaseAction.dumpDatabase();
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("Toggle Noise") {
+ @Override
+ public void run() {
+ sDebugNoise = !sDebugNoise;
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("Force sync SMS") {
+ @Override
+ public void run() {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1);
+ SyncManager.forceSync();
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("Sync SMS") {
+ @Override
+ public void run() {
+ SyncManager.sync();
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("Load SMS/MMS from dump file") {
+ @Override
+ public void run() {
+ new DebugSmsMmsDumpTask(host,
+ DebugSmsMmsFromDumpFileDialogFragment.ACTION_LOAD).executeOnThreadPool();
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("Email SMS/MMS dump file") {
+ @Override
+ public void run() {
+ new DebugSmsMmsDumpTask(host,
+ DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL).executeOnThreadPool();
+ }
+ });
+
+ arrayAdapter.add(new DebugAction("MMS Config...") {
+ @Override
+ public void run() {
+ UIIntents.get().launchDebugMmsConfigActivity(host);
+ }
+ });
+
+ arrayAdapter.add(new DebugAction(sDebugClassZeroSms ? "Turn off Class 0 sms test" :
+ "Turn on Class Zero test") {
+ @Override
+ public void run() {
+ sDebugClassZeroSms = !sDebugClassZeroSms;
+ }
+ });
+
+ builder.setAdapter(arrayAdapter,
+ new android.content.DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface arg0, final int pos) {
+ arrayAdapter.getItem(pos).run();
+ }
+ });
+
+ builder.create().show();
+ }
+
+ /**
+ * Task to list all the dump files and perform an action on it
+ */
+ private static class DebugSmsMmsDumpTask extends SafeAsyncTask<Void, Void, String[]> {
+ private final String mAction;
+ private final Activity mHost;
+
+ public DebugSmsMmsDumpTask(final Activity host, final String action) {
+ mHost = host;
+ mAction = action;
+ }
+
+ @Override
+ protected void onPostExecute(final String[] result) {
+ if (result == null || result.length < 1) {
+ return;
+ }
+ final FragmentManager fragmentManager = mHost.getFragmentManager();
+ final FragmentTransaction ft = fragmentManager.beginTransaction();
+ final DebugSmsMmsFromDumpFileDialogFragment dialog =
+ DebugSmsMmsFromDumpFileDialogFragment.newInstance(result, mAction);
+ dialog.show(fragmentManager, ""/*tag*/);
+ }
+
+ @Override
+ protected String[] doInBackgroundTimed(final Void... params) {
+ final File dir = DebugUtils.getDebugFilesDir();
+ return dir.list(new FilenameFilter() {
+ @Override
+ public boolean accept(final File dir, final String filename) {
+ return filename != null
+ && ((mAction == DebugSmsMmsFromDumpFileDialogFragment.ACTION_EMAIL
+ && filename.equals(DumpDatabaseAction.DUMP_NAME))
+ || filename.startsWith(MmsUtils.MMS_DUMP_PREFIX)
+ || filename.startsWith(MmsUtils.SMS_DUMP_PREFIX));
+ }
+ });
+ }
+ }
+
+ /**
+ * Dump the received raw SMS data into a file on external storage
+ *
+ * @param id The ID to use as part of the dump file name
+ * @param messages The raw SMS data
+ */
+ public static void dumpSms(final long id, final android.telephony.SmsMessage[] messages,
+ final String format) {
+ try {
+ final String dumpFileName = MmsUtils.SMS_DUMP_PREFIX + Long.toString(id);
+ final File dumpFile = DebugUtils.getDebugFile(dumpFileName, true);
+ if (dumpFile != null) {
+ final FileOutputStream fos = new FileOutputStream(dumpFile);
+ final DataOutputStream dos = new DataOutputStream(fos);
+ try {
+ final int chars = (TextUtils.isEmpty(format) ? 0 : format.length());
+ dos.writeInt(chars);
+ if (chars > 0) {
+ dos.writeUTF(format);
+ }
+ dos.writeInt(messages.length);
+ for (final android.telephony.SmsMessage message : messages) {
+ final byte[] pdu = message.getPdu();
+ dos.writeInt(pdu.length);
+ dos.write(pdu, 0, pdu.length);
+ }
+ dos.flush();
+ } finally {
+ dos.close();
+ ensureReadable(dumpFile);
+ }
+ }
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "dumpSms: " + e, e);
+ }
+ }
+
+ /**
+ * Load MMS/SMS from the dump file
+ */
+ public static SmsMessage[] retreiveSmsFromDumpFile(final String dumpFileName) {
+ SmsMessage[] messages = null;
+ final File inputFile = DebugUtils.getDebugFile(dumpFileName, false);
+ if (inputFile != null) {
+ FileInputStream fis = null;
+ DataInputStream dis = null;
+ try {
+ fis = new FileInputStream(inputFile);
+ dis = new DataInputStream(fis);
+
+ // SMS dump
+ final int chars = dis.readInt();
+ if (chars > 0) {
+ final String format = dis.readUTF();
+ }
+ final int count = dis.readInt();
+ final SmsMessage[] messagesTemp = new SmsMessage[count];
+ for (int i = 0; i < count; i++) {
+ final int length = dis.readInt();
+ final byte[] pdu = new byte[length];
+ dis.read(pdu, 0, length);
+ messagesTemp[i] = SmsMessage.createFromPdu(pdu);
+ }
+ messages = messagesTemp;
+ } catch (final FileNotFoundException e) {
+ // Nothing to do
+ } catch (final StreamCorruptedException e) {
+ // Nothing to do
+ } catch (final IOException e) {
+ // Nothing to do
+ } finally {
+ if (dis != null) {
+ try {
+ dis.close();
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+ }
+ }
+ }
+ return messages;
+ }
+
+ public static File getDebugFile(final String fileName, final boolean create) {
+ final File dir = getDebugFilesDir();
+ final File file = new File(dir, fileName);
+ if (create && file.exists()) {
+ file.delete();
+ }
+ return file;
+ }
+
+ public static File getDebugFilesDir() {
+ final File dir = Environment.getExternalStorageDirectory();
+ return dir;
+ }
+
+ /**
+ * Load MMS/SMS from the dump file
+ */
+ public static byte[] receiveFromDumpFile(final String dumpFileName) {
+ byte[] data = null;
+ try {
+ final File inputFile = getDebugFile(dumpFileName, false);
+ if (inputFile != null) {
+ final FileInputStream fis = new FileInputStream(inputFile);
+ final BufferedInputStream bis = new BufferedInputStream(fis);
+ try {
+ // dump file
+ data = ByteStreams.toByteArray(bis);
+ if (data == null || data.length < 1) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: empty data");
+ }
+ } finally {
+ bis.close();
+ }
+ }
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "receiveFromDumpFile: " + e, e);
+ }
+ return data;
+ }
+
+ public static void ensureReadable(final File file) {
+ if (file.exists()){
+ file.setReadable(true, false);
+ }
+ }
+
+ /**
+ * Logs the name of the method that is currently executing, e.g. "MyActivity.onCreate". This is
+ * useful for surgically adding logs for tracing execution while debugging.
+ * <p>
+ * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead.
+ * However, this method is only executed on eng builds if DEBUG logs are loggable.
+ */
+ public static void logCurrentMethod(String tag) {
+ if (!LogUtil.isLoggable(tag, LogUtil.DEBUG)) {
+ return;
+ }
+ StackTraceElement caller = getCaller(1);
+ if (caller == null) {
+ return;
+ }
+ String className = caller.getClassName();
+ // Strip off the package name
+ int lastDot = className.lastIndexOf('.');
+ if (lastDot > -1) {
+ className = className.substring(lastDot + 1);
+ }
+ LogUtil.d(tag, className + "." + caller.getMethodName());
+ }
+
+ /**
+ * Returns info about the calling method. The {@code depth} parameter controls how far back to
+ * go. For example, if foo() calls bar(), and bar() calls getCaller(0), it returns info about
+ * bar(). If bar() instead called getCaller(1), it would return info about foo(). And so on.
+ * <p>
+ * NOTE: This method retrieves the current thread's stack trace, which adds runtime overhead.
+ * It should only be used in production where necessary to gather context about an error or
+ * unexpected event (e.g. the {@link Assert} class uses it).
+ *
+ * @return stack frame information for the caller (if found); otherwise {@code null}.
+ */
+ public static StackTraceElement getCaller(int depth) {
+ // If the signature of this method is changed, proguard.flags must be updated!
+ if (depth < 0) {
+ throw new IllegalArgumentException("depth cannot be negative");
+ }
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+ if (trace == null || trace.length < (depth + 2)) {
+ return null;
+ }
+ // The stack trace includes some methods we don't care about (e.g. this method).
+ // Walk down until we find this method, and then back up to the caller we're looking for.
+ for (int i = 0; i < trace.length - 1; i++) {
+ String methodName = trace[i].getMethodName();
+ if ("getCaller".equals(methodName)) {
+ return trace[i + depth + 1];
+ }
+ }
+ // Never found ourself in the stack?!
+ return null;
+ }
+
+ /**
+ * Returns a boolean indicating whether ClassZero debugging is enabled. If enabled, any received
+ * sms is treated as if it were a class zero message and displayed by the ClassZeroActivity.
+ */
+ public static boolean debugClassZeroSmsEnabled() {
+ return sDebugClassZeroSms;
+ }
+}
diff --git a/src/com/android/messaging/util/EmailAddress.java b/src/com/android/messaging/util/EmailAddress.java
new file mode 100644
index 0000000..0c0dab9
--- /dev/null
+++ b/src/com/android/messaging/util/EmailAddress.java
@@ -0,0 +1,272 @@
+/*
+ * 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.util;
+
+import com.google.common.base.CharMatcher;
+
+/**
+ * Parsing the email address
+ */
+public final class EmailAddress {
+ private static final CharMatcher ANY_WHITESPACE = CharMatcher.anyOf(
+ " \t\n\r\f\u000B\u0085\u2028\u2029\u200D\uFFEF\uFFFD\uFFFE\uFFFF");
+ private static final CharMatcher EMAIL_ALLOWED_CHARS = CharMatcher.inRange((char) 0, (char) 31)
+ .or(CharMatcher.is((char) 127))
+ .or(CharMatcher.anyOf(" @,:<>"))
+ .negate();
+
+ /**
+ * Helper method that checks whether the input text is valid email address.
+ * TODO: This creates a new EmailAddress object each time
+ * Need to make it more lightweight by pulling out the validation code into a static method.
+ */
+ public static boolean isValidEmail(final String emailText) {
+ return new EmailAddress(emailText).isValid();
+ }
+
+ /**
+ * Parses the specified email address. Internationalized addresses are treated as invalid.
+ *
+ * @param emailString A string representing just an email address. It should
+ * not contain any other tokens. <code>"Name&lt;foo@example.org>"</code> won't be valid.
+ */
+ public EmailAddress(final String emailString) {
+ this(emailString, false);
+ }
+
+ /**
+ * Parses the specified email address.
+ *
+ * @param emailString A string representing just an email address. It should
+ * not contain any other tokens. <code>"Name&lt;foo@example.org>"</code> won't be valid.
+ * @param i18n Accept an internationalized address if it is true.
+ */
+ public EmailAddress(final String emailString, final boolean i18n) {
+ allowI18n = i18n;
+ valid = parseEmail(emailString);
+ }
+
+ /**
+ * Parses the specified email address. Internationalized addresses are treated as invalid.
+ *
+ * @param user A string representing the username in the email prior to the '@' symbol
+ * @param host A string representing the host following the '@' symbol
+ */
+ public EmailAddress(final String user, final String host) {
+ this(user, host, false);
+ }
+
+ /**
+ * Parses the specified email address.
+ *
+ * @param user A string representing the username in the email prior to the '@' symbol
+ * @param host A string representing the host following the '@' symbol
+ * @param i18n Accept an internationalized address if it is true.
+ */
+ public EmailAddress(final String user, final String host, final boolean i18n) {
+ allowI18n = i18n;
+ this.user = user;
+ setHost(host);
+ }
+
+ protected boolean parseEmail(final String emailString) {
+ // check for null
+ if (emailString == null) {
+ return false;
+ }
+
+ // Check for an '@' character. Get the last one, in case the local part is
+ // quoted. See http://b/1944742.
+ final int atIndex = emailString.lastIndexOf('@');
+ if ((atIndex <= 0) || // no '@' character in the email address
+ // or @ on the first position
+ (atIndex == (emailString.length() - 1))) { // last character, no host
+ return false;
+ }
+
+ user = emailString.substring(0, atIndex);
+ host = emailString.substring(atIndex + 1);
+
+ return isValidInternal();
+ }
+
+ @Override
+ public String toString() {
+ return user + "@" + host;
+ }
+
+ /**
+ * Ensure the email address is valid, conforming to current RFC2821 and
+ * RFC2822 guidelines (although some iffy characters, like ! and ;, are
+ * allowed because they are not technically prohibited in the RFC)
+ */
+ private boolean isValidInternal() {
+ if ((user == null) || (host == null)) {
+ return false;
+ }
+
+ if ((user.length() == 0) || (host.length() == 0)) {
+ return false;
+ }
+
+ // check for white space in the host
+ if (ANY_WHITESPACE.indexIn(host) >= 0) {
+ return false;
+ }
+
+ // ensure the host is above the minimum length
+ if (host.length() < 4) {
+ return false;
+ }
+
+ final int firstDot = host.indexOf('.');
+
+ // ensure host contains at least one dot
+ if (firstDot == -1) {
+ return false;
+ }
+
+ // check if the host contains two continuous dots.
+ if (host.indexOf("..") >= 0) {
+ return false;
+ }
+
+ // check if the first host char is a dot.
+ if (host.charAt(0) == '.') {
+ return false;
+ }
+
+ final int secondDot = host.indexOf(".", firstDot + 1);
+
+ // if there's a dot at the end, there needs to be a second dot
+ if (host.charAt(host.length() - 1) == '.' && secondDot == -1) {
+ return false;
+ }
+
+ // Host must not have any disallowed characters; allowI18n dictates whether
+ // host must be ASCII.
+ if (!EMAIL_ALLOWED_CHARS.matchesAllOf(host)
+ || (!allowI18n && !CharMatcher.ASCII.matchesAllOf(host))) {
+ return false;
+ }
+
+ if (user.startsWith("\"")) {
+ if (!isQuotedUserValid()) {
+ return false;
+ }
+ } else {
+ // check for white space in the user
+ if (ANY_WHITESPACE.indexIn(user) >= 0) {
+ return false;
+ }
+
+ // the user cannot contain two continuous dots
+ if (user.indexOf("..") >= 0) {
+ return false;
+ }
+
+ // User must not have any disallowed characters; allow I18n dictates whether
+ // user must be ASCII.
+ if (!EMAIL_ALLOWED_CHARS.matchesAllOf(user)
+ || (!allowI18n && !CharMatcher.ASCII.matchesAllOf(user))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isQuotedUserValid() {
+ final int limit = user.length() - 1;
+ if (limit < 1 || !user.endsWith("\"")) {
+ return false;
+ }
+
+ // Unusual loop bounds (looking only at characters between the outer quotes,
+ // not at either quote character). Plus, i is manipulated within the loop.
+ for (int i = 1; i < limit; ++i) {
+ final char ch = user.charAt(i);
+ if (ch == '"' || ch == 127
+ // No non-whitespace control chars:
+ || (ch < 32 && !ANY_WHITESPACE.matches(ch))
+ // No non-ASCII chars, unless i18n is in effect:
+ || (ch >= 128 && !allowI18n)) {
+ return false;
+ } else if (ch == '\\') {
+ if (i + 1 < limit) {
+ ++i; // Skip the quoted character
+ } else {
+ // We have a trailing backslash -- so it can't be quoting anything.
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean equals(final Object otherObject) {
+ // Do an instance check first as an optimization.
+ if (this == otherObject) {
+ return true;
+ }
+ if (otherObject instanceof EmailAddress) {
+ final EmailAddress otherAddress = (EmailAddress) otherObject;
+ return toString().equals(otherAddress.toString());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ // Arbitrary hash code as a function of both host and user.
+ return toString().hashCode();
+ }
+
+ // accessors
+ public boolean isValid() {
+ return valid;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ // used to change the host on an email address and rechecks validity
+
+ /**
+ * Changes the host name of the email address and rechecks the address'
+ * validity. Exercise caution when storing EmailAddress instances in
+ * hash-keyed collections. Calling setHost() with a different host name will
+ * change the return value of hashCode.
+ *
+ * @param hostName The new host name of the email address.
+ */
+ public void setHost(final String hostName) {
+ host = hostName;
+ valid = isValidInternal();
+ }
+
+ protected boolean valid = false;
+ protected String user = null;
+ protected String host = null;
+ protected boolean allowI18n = false;
+}
diff --git a/src/com/android/messaging/util/FallbackStrategies.java b/src/com/android/messaging/util/FallbackStrategies.java
new file mode 100644
index 0000000..57c208f
--- /dev/null
+++ b/src/com/android/messaging/util/FallbackStrategies.java
@@ -0,0 +1,91 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides a generic and loose-coupled framework to execute one primary and multiple fallback
+ * strategies for solving a given task.<p>
+ * Basically, what would have been a nasty try-catch that's hard to separate and maintain:
+ * <pre><code>
+ * try {
+ * // doSomething() that may fail.
+ * } catch (Exception ex) {
+ * try {
+ * // fallback1() that may fail.
+ * } catch (Exception ex2) {
+ * try {
+ * // fallback2() that may fail.
+ * } catch (Exception ex3) {
+ * // ...
+ * }
+ * }
+ * }
+ * </code></pre>
+ * Now becomes:<br>
+ * <pre><code>
+ * FallbackStrategies
+ * .startWith(something)
+ * .thenTry(fallback1)
+ * .thenTry(fallback2)
+ * .execute();
+ * </code></pre>
+ */
+public class FallbackStrategies<Input, Output> {
+ public interface Strategy<Input, Output> {
+ Output execute(Input params) throws Exception;
+ }
+
+ private final List<Strategy<Input, Output>> mChainedStrategies;
+
+ private FallbackStrategies(final Strategy<Input, Output> primaryStrategy) {
+ mChainedStrategies = new ArrayList<Strategy<Input, Output>>();
+ mChainedStrategies.add(primaryStrategy);
+ }
+
+ public static <Input, Output> FallbackStrategies<Input, Output> startWith(
+ final Strategy<Input, Output> primaryStrategy) {
+ return new FallbackStrategies<Input, Output>(primaryStrategy);
+ }
+
+ public FallbackStrategies<Input, Output> thenTry(final Strategy<Input, Output> strategy) {
+ Assert.isFalse(mChainedStrategies.isEmpty());
+ mChainedStrategies.add(strategy);
+ return this;
+ }
+
+ public Output execute(final Input params) {
+ final int count = mChainedStrategies.size();
+ for (int i = 0; i < count; i++) {
+ final Strategy<Input, Output> strategy = mChainedStrategies.get(i);
+ try {
+ // If succeeds, this will directly return.
+ return strategy.execute(params);
+ } catch (Exception ex) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Exceptions occured when executing strategy " +
+ strategy + (i < count - 1 ?
+ ", attempting fallback " + mChainedStrategies.get(i + 1) :
+ ", and running out of fallbacks."), ex);
+ // This will fall through and continue with the next strategy (if any).
+ }
+ }
+ // Running out of strategies, return null.
+ // TODO: Should this accept user-defined fallback value other than null?
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/util/FileUtil.java b/src/com/android/messaging/util/FileUtil.java
new file mode 100644
index 0000000..7c47ae9
--- /dev/null
+++ b/src/com/android/messaging/util/FileUtil.java
@@ -0,0 +1,140 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.webkit.MimeTypeMap;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class FileUtil {
+ /** Returns a new file name, ensuring that such a file does not already exist. */
+ private static synchronized File getNewFile(File directory, String extension,
+ String fileNameFormat) throws IOException {
+ final Date date = new Date(System.currentTimeMillis());
+ final SimpleDateFormat dateFormat = new SimpleDateFormat(fileNameFormat);
+ final String numberedFileNameFormat = dateFormat.format(date) + "_%02d" + "." + extension;
+ for (int i = 1; i <= 99; i++) { // Only save 99 of the same file name.
+ final String newName = String.format(Locale.US, numberedFileNameFormat, i);
+ File testFile = new File(directory, newName);
+ if (!testFile.exists()) {
+ testFile.createNewFile();
+ return testFile;
+ }
+ }
+ LogUtil.e(LogUtil.BUGLE_TAG, "Too many duplicate file names: " + numberedFileNameFormat);
+ return null;
+ }
+
+ /**
+ * Creates an unused name to use for creating a new file. The format happens to be similar
+ * to that used by the Android camera application.
+ *
+ * @param directory directory that the file should be saved to
+ * @param contentType of the media being saved
+ * @return file name to be used for creating the new file. The caller is responsible for
+ * actually creating the file.
+ */
+ public static File getNewFile(File directory, String contentType) throws IOException {
+ MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
+ String fileExtension = mimeTypeMap.getExtensionFromMimeType(contentType);
+
+ final Context context = Factory.get().getApplicationContext();
+ String fileNameFormat = context.getString(ContentType.isImageType(contentType)
+ ? R.string.new_image_file_name_format : R.string.new_file_name_format);
+ return getNewFile(directory, fileExtension, fileNameFormat);
+ }
+
+ /** Delete everything below and including root */
+ public static void removeFileOrDirectory(File root) {
+ removeFileOrDirectoryExcept(root, null);
+ }
+
+ /** Delete everything below and including root except for the given file */
+ public static void removeFileOrDirectoryExcept(File root, File exclude) {
+ if (root.exists()) {
+ if (root.isDirectory()) {
+ for (File file : root.listFiles()) {
+ if (exclude == null || !file.equals(exclude)) {
+ removeFileOrDirectoryExcept(file, exclude);
+ }
+ }
+ root.delete();
+ } else if (root.isFile()) {
+ root.delete();
+ }
+ }
+ }
+
+ /**
+ * Move all files and folders under a directory into the target.
+ */
+ public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) {
+ if (sourceDir.isDirectory() && targetDir.isDirectory()) {
+ if (isSameOrSubDirectory(sourceDir, targetDir)) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " +
+ "directory is a parent of the target");
+ return;
+ }
+ for (File file : sourceDir.listFiles()) {
+ if (file.isDirectory()) {
+ final File dirTarget = new File(targetDir, file.getName());
+ dirTarget.mkdirs();
+ moveAllContentUnderDirectory(file, dirTarget);
+ } else {
+ try {
+ final File fileTarget = new File(targetDir, file.getName());
+ Files.move(file, fileTarget);
+ } catch (IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e);
+ // Try proceed with the next file.
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks, whether the child directory is the same as, or a sub-directory of the base
+ * directory.
+ */
+ private static boolean isSameOrSubDirectory(File base, File child) {
+ try {
+ base = base.getCanonicalFile();
+ child = child.getCanonicalFile();
+ File parentFile = child;
+ while (parentFile != null) {
+ if (base.equals(parentFile)) {
+ return true;
+ }
+ parentFile = parentFile.getParentFile();
+ }
+ return false;
+ } catch (IOException ex) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error while accessing file", ex);
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/GifTranscoder.java b/src/com/android/messaging/util/GifTranscoder.java
new file mode 100644
index 0000000..65413a0
--- /dev/null
+++ b/src/com/android/messaging/util/GifTranscoder.java
@@ -0,0 +1,94 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.text.format.Formatter;
+
+import com.google.common.base.Stopwatch;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Compresses a GIF so it can be sent via MMS.
+ * <p>
+ * The entry point lives in its own class, we can defer loading the native GIF transcoding library
+ * into memory until we actually need it.
+ */
+public class GifTranscoder {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static int MIN_HEIGHT = 100;
+ private static int MIN_WIDTH = 100;
+
+ static {
+ System.loadLibrary("giftranscode");
+ }
+
+ public static boolean transcode(Context context, String filePath, String outFilePath) {
+ if (!isEnabled()) {
+ return false;
+ }
+ final long inputSize = new File(filePath).length();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ final boolean success = transcodeInternal(filePath, outFilePath);
+ stopwatch.stop();
+ final long elapsedMs = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+ final long outputSize = new File(outFilePath).length();
+ final float compression = (inputSize > 0) ? ((float) outputSize / inputSize) : 0;
+
+ if (success) {
+ LogUtil.i(TAG, String.format("Resized GIF (%s) in %d ms, %s => %s (%.0f%%)",
+ LogUtil.sanitizePII(filePath),
+ elapsedMs,
+ Formatter.formatShortFileSize(context, inputSize),
+ Formatter.formatShortFileSize(context, outputSize),
+ compression * 100.0f));
+ }
+ return success;
+ }
+
+ private static native boolean transcodeInternal(String filePath, String outFilePath);
+
+ /**
+ * Estimates the size of a GIF transcoded from a GIF with the specified size.
+ */
+ public static long estimateFileSizeAfterTranscode(long fileSize) {
+ // I tested transcoding on ~70 GIFs and found that the transcoded files are in general
+ // about 25-35% the size of the original. This compression ratio is very consistent for the
+ // class of GIFs we care about most: those converted from video clips and 1-3 MB in size.
+ return (long) (fileSize * 0.35f);
+ }
+
+ public static boolean canBeTranscoded(int width, int height) {
+ if (!isEnabled()) {
+ return false;
+ }
+ return width >= MIN_WIDTH && height >= MIN_HEIGHT;
+ }
+
+ private static boolean isEnabled() {
+ final boolean enabled = BugleGservices.get().getBoolean(
+ BugleGservicesKeys.ENABLE_GIF_TRANSCODING,
+ BugleGservicesKeys.ENABLE_GIF_TRANSCODING_DEFAULT);
+ if (!enabled) {
+ LogUtil.w(TAG, "GIF transcoding is disabled");
+ }
+ return enabled;
+ }
+}
diff --git a/src/com/android/messaging/util/ImageUtils.java b/src/com/android/messaging/util/ImageUtils.java
new file mode 100644
index 0000000..05d3678
--- /dev/null
+++ b/src/com/android/messaging/util/ImageUtils.java
@@ -0,0 +1,908 @@
+/*
+ * 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.util;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.media.ImageRequest;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.android.messaging.util.exif.ExifInterface;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.Files;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+public class ImageUtils {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final int MAX_OOM_COUNT = 1;
+ private static final byte[] GIF87_HEADER = "GIF87a".getBytes(Charset.forName("US-ASCII"));
+ private static final byte[] GIF89_HEADER = "GIF89a".getBytes(Charset.forName("US-ASCII"));
+
+ // Used for drawBitmapWithCircleOnCanvas.
+ // Default color is transparent for both circle background and stroke.
+ public static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = 0;
+ public static final int DEFAULT_CIRCLE_STROKE_COLOR = 0;
+
+ private static volatile ImageUtils sInstance;
+
+ public static ImageUtils get() {
+ if (sInstance == null) {
+ synchronized (ImageUtils.class) {
+ if (sInstance == null) {
+ sInstance = new ImageUtils();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ public static void set(final ImageUtils imageUtils) {
+ sInstance = imageUtils;
+ }
+
+ /**
+ * Transforms a bitmap into a byte array.
+ *
+ * @param quality Value between 0 and 100 that the compressor uses to discern what quality the
+ * resulting bytes should be
+ * @param bitmap Bitmap to convert into bytes
+ * @return byte array of bitmap
+ */
+ public static byte[] bitmapToBytes(final Bitmap bitmap, final int quality)
+ throws OutOfMemoryError {
+ boolean done = false;
+ int oomCount = 0;
+ byte[] imageBytes = null;
+ while (!done) {
+ try {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os);
+ imageBytes = os.toByteArray();
+ done = true;
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(TAG, "OutOfMemory converting bitmap to bytes.");
+ oomCount++;
+ if (oomCount <= MAX_OOM_COUNT) {
+ Factory.get().reclaimMemory();
+ } else {
+ done = true;
+ LogUtil.w(TAG, "Failed to convert bitmap to bytes. Out of Memory.");
+ }
+ throw e;
+ }
+ }
+ return imageBytes;
+ }
+
+ /**
+ * Given the source bitmap and a canvas, draws the bitmap through a circular
+ * mask. Only draws a circle with diameter equal to the destination width.
+ *
+ * @param bitmap The source bitmap to draw.
+ * @param canvas The canvas to draw it on.
+ * @param source The source bound of the bitmap.
+ * @param dest The destination bound on the canvas.
+ * @param bitmapPaint Optional Paint object for the bitmap
+ * @param fillBackground when set, fill the circle with backgroundColor
+ * @param strokeColor draw a border outside the circle with strokeColor
+ */
+ public static void drawBitmapWithCircleOnCanvas(final Bitmap bitmap, final Canvas canvas,
+ final RectF source, final RectF dest, @Nullable Paint bitmapPaint,
+ final boolean fillBackground, final int backgroundColor, int strokeColor) {
+ // Draw bitmap through shader first.
+ final BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
+ final Matrix matrix = new Matrix();
+
+ // Fit bitmap to bounds.
+ matrix.setRectToRect(source, dest, Matrix.ScaleToFit.CENTER);
+
+ shader.setLocalMatrix(matrix);
+
+ if (bitmapPaint == null) {
+ bitmapPaint = new Paint();
+ }
+
+ bitmapPaint.setAntiAlias(true);
+ if (fillBackground) {
+ bitmapPaint.setColor(backgroundColor);
+ canvas.drawCircle(dest.centerX(), dest.centerX(), dest.width() / 2f, bitmapPaint);
+ }
+
+ bitmapPaint.setShader(shader);
+ canvas.drawCircle(dest.centerX(), dest.centerX(), dest.width() / 2f, bitmapPaint);
+ bitmapPaint.setShader(null);
+
+ if (strokeColor != 0) {
+ final Paint stroke = new Paint();
+ stroke.setAntiAlias(true);
+ stroke.setColor(strokeColor);
+ stroke.setStyle(Paint.Style.STROKE);
+ final float strokeWidth = 6f;
+ stroke.setStrokeWidth(strokeWidth);
+ canvas.drawCircle(dest.centerX(),
+ dest.centerX(),
+ dest.width() / 2f - stroke.getStrokeWidth() / 2f,
+ stroke);
+ }
+ }
+
+ /**
+ * Sets a drawable to the background of a view. setBackgroundDrawable() is deprecated since
+ * JB and replaced by setBackground().
+ */
+ @SuppressWarnings("deprecation")
+ public static void setBackgroundDrawableOnView(final View view, final Drawable drawable) {
+ if (OsUtil.isAtLeastJB()) {
+ view.setBackground(drawable);
+ } else {
+ view.setBackgroundDrawable(drawable);
+ }
+ }
+
+ /**
+ * Based on the input bitmap bounds given by BitmapFactory.Options, compute the required
+ * sub-sampling size for loading a scaled down version of the bitmap to the required size
+ * @param options a BitmapFactory.Options instance containing the bounds info of the bitmap
+ * @param reqWidth the desired width of the bitmap. Can be ImageRequest.UNSPECIFIED_SIZE.
+ * @param reqHeight the desired height of the bitmap. Can be ImageRequest.UNSPECIFIED_SIZE.
+ * @return
+ */
+ public int calculateInSampleSize(
+ final BitmapFactory.Options options, final int reqWidth, final int reqHeight) {
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ final boolean checkHeight = reqHeight != ImageRequest.UNSPECIFIED_SIZE;
+ final boolean checkWidth = reqWidth != ImageRequest.UNSPECIFIED_SIZE;
+ if ((checkHeight && height > reqHeight) ||
+ (checkWidth && width > reqWidth)) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+ // height and width larger than the requested height and width.
+ while ((!checkHeight || (halfHeight / inSampleSize) > reqHeight)
+ && (!checkWidth || (halfWidth / inSampleSize) > reqWidth)) {
+ inSampleSize *= 2;
+ }
+ }
+
+ return inSampleSize;
+ }
+
+ private static final String[] MEDIA_CONTENT_PROJECTION = new String[] {
+ MediaStore.MediaColumns.MIME_TYPE
+ };
+
+ private static final int INDEX_CONTENT_TYPE = 0;
+
+ @DoesNotRunOnMainThread
+ public static String getContentType(final ContentResolver cr, final Uri uri) {
+ // Figure out the content type of media.
+ String contentType = null;
+ Cursor cursor = null;
+ if (UriUtil.isMediaStoreUri(uri)) {
+ try {
+ cursor = cr.query(uri, MEDIA_CONTENT_PROJECTION, null, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ contentType = cursor.getString(INDEX_CONTENT_TYPE);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+ if (contentType == null) {
+ // Last ditch effort to get the content type. Look at the file extension.
+ contentType = ContentType.getContentTypeFromExtension(uri.toString(),
+ ContentType.IMAGE_UNSPECIFIED);
+ }
+ return contentType;
+ }
+
+ /**
+ * @param context Android context
+ * @param uri Uri to the image data
+ * @return The exif orientation value for the image in the specified uri
+ */
+ public static int getOrientation(final Context context, final Uri uri) {
+ try {
+ return getOrientation(context.getContentResolver().openInputStream(uri));
+ } catch (FileNotFoundException e) {
+ LogUtil.e(TAG, "getOrientation couldn't open: " + uri, e);
+ }
+ return android.media.ExifInterface.ORIENTATION_UNDEFINED;
+ }
+
+ /**
+ * @param inputStream The stream to the image file. Closed on completion
+ * @return The exif orientation value for the image in the specified stream
+ */
+ public static int getOrientation(final InputStream inputStream) {
+ int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
+ if (inputStream != null) {
+ try {
+ final ExifInterface exifInterface = new ExifInterface();
+ exifInterface.readExif(inputStream);
+ final Integer orientationValue =
+ exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+ if (orientationValue != null) {
+ orientation = orientationValue.intValue();
+ }
+ } catch (IOException e) {
+ // If the image if GIF, PNG, or missing exif header, just use the defaults
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ LogUtil.e(TAG, "getOrientation error closing input stream", e);
+ }
+ }
+ }
+ return orientation;
+ }
+
+ /**
+ * Returns whether the resource is a GIF image.
+ */
+ public static boolean isGif(String contentType, Uri contentUri) {
+ if (TextUtils.equals(contentType, ContentType.IMAGE_GIF)) {
+ return true;
+ }
+ if (ContentType.isImageType(contentType)) {
+ try {
+ ContentResolver contentResolver = Factory.get().getApplicationContext()
+ .getContentResolver();
+ InputStream inputStream = contentResolver.openInputStream(contentUri);
+ return ImageUtils.isGif(inputStream);
+ } catch (Exception e) {
+ LogUtil.w(TAG, "Could not open GIF input stream", e);
+ }
+ }
+ // Assume anything with a non-image content type is not a GIF
+ return false;
+ }
+
+ /**
+ * @param inputStream The stream to the image file. Closed on completion
+ * @return Whether the image stream represents a GIF
+ */
+ public static boolean isGif(InputStream inputStream) {
+ if (inputStream != null) {
+ try {
+ byte[] gifHeaderBytes = new byte[6];
+ int value = inputStream.read(gifHeaderBytes, 0, 6);
+ if (value == 6) {
+ return Arrays.equals(gifHeaderBytes, GIF87_HEADER)
+ || Arrays.equals(gifHeaderBytes, GIF89_HEADER);
+ }
+ } catch (IOException e) {
+ return false;
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Read an image and compress it to particular max dimensions and size.
+ * Used to ensure images can fit in an MMS.
+ * TODO: This uses memory very inefficiently as it processes the whole image as a unit
+ * (rather than slice by slice) but system JPEG functions do not support slicing and dicing.
+ */
+ public static class ImageResizer {
+
+ /**
+ * The quality parameter which is used to compress JPEG images.
+ */
+ private static final int IMAGE_COMPRESSION_QUALITY = 95;
+ /**
+ * The minimum quality parameter which is used to compress JPEG images.
+ */
+ private static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50;
+
+ /**
+ * Minimum factor to reduce quality value
+ */
+ private static final double QUALITY_SCALE_DOWN_RATIO = 0.85f;
+
+ /**
+ * Maximum passes through the resize loop before failing permanently
+ */
+ private static final int NUMBER_OF_RESIZE_ATTEMPTS = 6;
+
+ /**
+ * Amount to scale down the picture when it doesn't fit
+ */
+ private static final float MIN_SCALE_DOWN_RATIO = 0.75f;
+
+ /**
+ * When computing sampleSize target scaling of no more than this ratio
+ */
+ private static final float MAX_TARGET_SCALE_FACTOR = 1.5f;
+
+
+ // Current sample size for subsampling image during initial decode
+ private int mSampleSize;
+ // Current bitmap holding initial decoded source image
+ private Bitmap mDecoded;
+ // If scaling is needed this holds the scaled bitmap (else should equal mDecoded)
+ private Bitmap mScaled;
+ // Current JPEG compression quality to use when compressing image
+ private int mQuality;
+ // Current factor to scale down decoded image before compressing
+ private float mScaleFactor;
+ // Flag keeping track of whether cache memory has been reclaimed
+ private boolean mHasReclaimedMemory;
+
+ // Initial size of the image (typically provided but can be UNSPECIFIED_SIZE)
+ private int mWidth;
+ private int mHeight;
+ // Orientation params of image as read from EXIF data
+ private final ExifInterface.OrientationParams mOrientationParams;
+ // Matrix to undo orientation and scale at the same time
+ private final Matrix mMatrix;
+ // Size limit as provided by MMS library
+ private final int mWidthLimit;
+ private final int mHeightLimit;
+ private final int mByteLimit;
+ // Uri from which to read source image
+ private final Uri mUri;
+ // Application context
+ private final Context mContext;
+ // Cached value of bitmap factory options
+ private final BitmapFactory.Options mOptions;
+ private final String mContentType;
+
+ private final int mMemoryClass;
+
+ /**
+ * Return resized (compressed) image (else null)
+ *
+ * @param width The width of the image (if known)
+ * @param height The height of the image (if known)
+ * @param orientation The orientation of the image as an ExifInterface constant
+ * @param widthLimit The width limit, in pixels
+ * @param heightLimit The height limit, in pixels
+ * @param byteLimit The binary size limit, in bytes
+ * @param uri Uri to the image data
+ * @param context Needed to open the image
+ * @param contentType of image
+ * @return encoded image meeting size requirements else null
+ */
+ public static byte[] getResizedImageData(final int width, final int height,
+ final int orientation, final int widthLimit, final int heightLimit,
+ final int byteLimit, final Uri uri, final Context context,
+ final String contentType) {
+ final ImageResizer resizer = new ImageResizer(width, height, orientation,
+ widthLimit, heightLimit, byteLimit, uri, context, contentType);
+ return resizer.resize();
+ }
+
+ /**
+ * Create and initialize an image resizer
+ */
+ private ImageResizer(final int width, final int height, final int orientation,
+ final int widthLimit, final int heightLimit, final int byteLimit, final Uri uri,
+ final Context context, final String contentType) {
+ mWidth = width;
+ mHeight = height;
+ mOrientationParams = ExifInterface.getOrientationParams(orientation);
+ mMatrix = new Matrix();
+ mWidthLimit = widthLimit;
+ mHeightLimit = heightLimit;
+ mByteLimit = byteLimit;
+ mUri = uri;
+ mWidth = width;
+ mContext = context;
+ mQuality = IMAGE_COMPRESSION_QUALITY;
+ mScaleFactor = 1.0f;
+ mHasReclaimedMemory = false;
+ mOptions = new BitmapFactory.Options();
+ mOptions.inScaled = false;
+ mOptions.inDensity = 0;
+ mOptions.inTargetDensity = 0;
+ mOptions.inSampleSize = 1;
+ mOptions.inJustDecodeBounds = false;
+ mOptions.inMutable = false;
+ final ActivityManager am =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mMemoryClass = Math.max(16, am.getMemoryClass());
+ mContentType = contentType;
+ }
+
+ /**
+ * Try to compress the image
+ *
+ * @return encoded image meeting size requirements else null
+ */
+ private byte[] resize() {
+ return ImageUtils.isGif(mContentType, mUri) ? resizeGifImage() : resizeStaticImage();
+ }
+
+ private byte[] resizeGifImage() {
+ byte[] bytesToReturn = null;
+ final String inputFilePath;
+ if (MediaScratchFileProvider.isMediaScratchSpaceUri(mUri)) {
+ inputFilePath = MediaScratchFileProvider.getFileFromUri(mUri).getAbsolutePath();
+ } else {
+ if (!TextUtils.equals(mUri.getScheme(), ContentResolver.SCHEME_FILE)) {
+ Assert.fail("Expected a GIF file uri, but actual uri = " + mUri.toString());
+ }
+ inputFilePath = mUri.getPath();
+ }
+
+ if (GifTranscoder.canBeTranscoded(mWidth, mHeight)) {
+ // Needed to perform the transcoding so that the gif can continue to play in the
+ // conversation while the sending is taking place
+ final Uri tmpUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("gif");
+ final File outputFile = MediaScratchFileProvider.getFileFromUri(tmpUri);
+ final String outputFilePath = outputFile.getAbsolutePath();
+
+ final boolean success =
+ GifTranscoder.transcode(mContext, inputFilePath, outputFilePath);
+ if (success) {
+ try {
+ bytesToReturn = Files.toByteArray(outputFile);
+ } catch (IOException e) {
+ LogUtil.e(TAG, "Could not create FileInputStream with path of "
+ + outputFilePath, e);
+ }
+ }
+
+ // Need to clean up the new file created to compress the gif
+ mContext.getContentResolver().delete(tmpUri, null, null);
+ } else {
+ // We don't want to transcode the gif because its image dimensions would be too
+ // small so just return the bytes of the original gif
+ try {
+ bytesToReturn = Files.toByteArray(new File(inputFilePath));
+ } catch (IOException e) {
+ LogUtil.e(TAG,
+ "Could not create FileInputStream with path of " + inputFilePath, e);
+ }
+ }
+
+ return bytesToReturn;
+ }
+
+ private byte[] resizeStaticImage() {
+ if (!ensureImageSizeSet()) {
+ // Cannot read image size
+ return null;
+ }
+ // Find incoming image size
+ if (!canBeCompressed()) {
+ return null;
+ }
+
+ // Decode image - if out of memory - reclaim memory and retry
+ try {
+ for (int attempts = 0; attempts < NUMBER_OF_RESIZE_ATTEMPTS; attempts++) {
+ final byte[] encoded = recodeImage(attempts);
+
+ // Only return data within the limit
+ if (encoded != null && encoded.length <= mByteLimit) {
+ return encoded;
+ } else {
+ final int currentSize = (encoded == null ? 0 : encoded.length);
+ updateRecodeParameters(currentSize);
+ }
+ }
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "File disappeared during resizing");
+ } finally {
+ // Release all bitmaps
+ if (mScaled != null && mScaled != mDecoded) {
+ mScaled.recycle();
+ }
+ if (mDecoded != null) {
+ mDecoded.recycle();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Ensure that the width and height of the source image are known
+ * @return flag indicating whether size is known
+ */
+ private boolean ensureImageSizeSet() {
+ if (mWidth == MessagingContentProvider.UNSPECIFIED_SIZE ||
+ mHeight == MessagingContentProvider.UNSPECIFIED_SIZE) {
+ // First get the image data (compressed)
+ final ContentResolver cr = mContext.getContentResolver();
+ InputStream inputStream = null;
+ // Find incoming image size
+ try {
+ mOptions.inJustDecodeBounds = true;
+ inputStream = cr.openInputStream(mUri);
+ BitmapFactory.decodeStream(inputStream, null, mOptions);
+
+ mWidth = mOptions.outWidth;
+ mHeight = mOptions.outHeight;
+ mOptions.inJustDecodeBounds = false;
+
+ return true;
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(TAG, "Could not open file corresponding to uri " + mUri, e);
+ } catch (final NullPointerException e) {
+ LogUtil.e(TAG, "NPE trying to open the uri " + mUri, e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException e) {
+ // Nothing to do
+ }
+ }
+ }
+
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Choose an initial subsamplesize that ensures the decoded image is no more than
+ * MAX_TARGET_SCALE_FACTOR bigger than largest supported image and that it is likely to
+ * compress to smaller than the target size (assuming compression down to 1 bit per pixel).
+ * @return whether the image can be down subsampled
+ */
+ private boolean canBeCompressed() {
+ final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE);
+
+ int imageHeight = mHeight;
+ int imageWidth = mWidth;
+
+ // Assume can use half working memory to decode the initial image (4 bytes per pixel)
+ final int workingMemoryPixelLimit = (mMemoryClass * 1024 * 1024 / 8);
+ // Target 1 bits per pixel in final compressed image
+ final int finalSizePixelLimit = mByteLimit * 8;
+ // When choosing to halve the resolution - only do so the image will still be too big
+ // after scaling by MAX_TARGET_SCALE_FACTOR
+ final int heightLimitWithSlop = (int) (mHeightLimit * MAX_TARGET_SCALE_FACTOR);
+ final int widthLimitWithSlop = (int) (mWidthLimit * MAX_TARGET_SCALE_FACTOR);
+ final int pixelLimitWithSlop = (int) (finalSizePixelLimit *
+ MAX_TARGET_SCALE_FACTOR * MAX_TARGET_SCALE_FACTOR);
+ final int pixelLimit = Math.min(pixelLimitWithSlop, workingMemoryPixelLimit);
+
+ int sampleSize = 1;
+ boolean fits = (imageHeight < heightLimitWithSlop &&
+ imageWidth < widthLimitWithSlop &&
+ imageHeight * imageWidth < pixelLimit);
+
+ // Compare sizes to compute sub-sampling needed
+ while (!fits) {
+ sampleSize = sampleSize * 2;
+ // Note that recodeImage may try using mSampleSize * 2. Hence we use the factor of 4
+ if (sampleSize >= (Integer.MAX_VALUE / 4)) {
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, String.format(
+ "Cannot resize image: widthLimit=%d heightLimit=%d byteLimit=%d " +
+ "imageWidth=%d imageHeight=%d", mWidthLimit, mHeightLimit, mByteLimit,
+ mWidth, mHeight));
+ Assert.fail("Image cannot be resized"); // http://b/18926934
+ return false;
+ }
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "computeInitialSampleSize: Increasing sampleSize to " + sampleSize
+ + " as h=" + imageHeight + " vs " + heightLimitWithSlop
+ + " w=" + imageWidth + " vs " + widthLimitWithSlop
+ + " p=" + imageHeight * imageWidth + " vs " + pixelLimit);
+ }
+ imageHeight = mHeight / sampleSize;
+ imageWidth = mWidth / sampleSize;
+ fits = (imageHeight < heightLimitWithSlop &&
+ imageWidth < widthLimitWithSlop &&
+ imageHeight * imageWidth < pixelLimit);
+ }
+
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "computeInitialSampleSize: Initial sampleSize " + sampleSize
+ + " for h=" + imageHeight + " vs " + heightLimitWithSlop
+ + " w=" + imageWidth + " vs " + widthLimitWithSlop
+ + " p=" + imageHeight * imageWidth + " vs " + pixelLimit);
+ }
+
+ mSampleSize = sampleSize;
+ return true;
+ }
+
+ /**
+ * Recode the image from initial Uri to encoded JPEG
+ * @param attempt Attempt number
+ * @return encoded image
+ */
+ private byte[] recodeImage(final int attempt) throws FileNotFoundException {
+ byte[] encoded = null;
+ try {
+ final ContentResolver cr = mContext.getContentResolver();
+ final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE);
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: attempt=" + attempt
+ + " limit (w=" + mWidthLimit + " h=" + mHeightLimit + ") quality="
+ + mQuality + " scale=" + mScaleFactor + " sampleSize=" + mSampleSize);
+ }
+ if (mScaled == null) {
+ if (mDecoded == null) {
+ mOptions.inSampleSize = mSampleSize;
+ final InputStream inputStream = cr.openInputStream(mUri);
+ mDecoded = BitmapFactory.decodeStream(inputStream, null, mOptions);
+ if (mDecoded == null) {
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: got empty decoded bitmap");
+ }
+ return null;
+ }
+ }
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: decoded w,h="
+ + mDecoded.getWidth() + "," + mDecoded.getHeight());
+ }
+ // Make sure to scale the decoded image if dimension is not within limit
+ final int decodedWidth = mDecoded.getWidth();
+ final int decodedHeight = mDecoded.getHeight();
+ if (decodedWidth > mWidthLimit || decodedHeight > mHeightLimit) {
+ final float minScaleFactor = Math.max(
+ mWidthLimit == 0 ? 1.0f :
+ (float) decodedWidth / (float) mWidthLimit,
+ mHeightLimit == 0 ? 1.0f :
+ (float) decodedHeight / (float) mHeightLimit);
+ if (mScaleFactor < minScaleFactor) {
+ mScaleFactor = minScaleFactor;
+ }
+ }
+ if (mScaleFactor > 1.0 || mOrientationParams.rotation != 0) {
+ mMatrix.reset();
+ mMatrix.postRotate(mOrientationParams.rotation);
+ mMatrix.postScale(mOrientationParams.scaleX / mScaleFactor,
+ mOrientationParams.scaleY / mScaleFactor);
+ mScaled = Bitmap.createBitmap(mDecoded, 0, 0, decodedWidth, decodedHeight,
+ mMatrix, false /* filter */);
+ if (mScaled == null) {
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: got empty scaled bitmap");
+ }
+ return null;
+ }
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "getResizedImageData: scaled w,h="
+ + mScaled.getWidth() + "," + mScaled.getHeight());
+ }
+ } else {
+ mScaled = mDecoded;
+ }
+ }
+ // Now encode it at current quality
+ encoded = ImageUtils.bitmapToBytes(mScaled, mQuality);
+ if (encoded != null && logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: Encoded down to " + encoded.length + "@"
+ + mScaled.getWidth() + "/" + mScaled.getHeight() + "~"
+ + mQuality);
+ }
+ } catch (final OutOfMemoryError e) {
+ LogUtil.w(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData - image too big (OutOfMemoryError), will try "
+ + " with smaller scale factor");
+ // fall through and keep trying with more compression
+ }
+ return encoded;
+ }
+
+ /**
+ * When image recode fails this method updates compression parameters for the next attempt
+ * @param currentSize encoded image size (will be 0 if OOM)
+ */
+ private void updateRecodeParameters(final int currentSize) {
+ final boolean logv = LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE);
+ // Only return data within the limit
+ if (currentSize > 0 &&
+ mQuality > MINIMUM_IMAGE_COMPRESSION_QUALITY) {
+ // First if everything succeeded but failed to hit target size
+ // Try quality proportioned to sqrt of size over size limit
+ mQuality = Math.max(MINIMUM_IMAGE_COMPRESSION_QUALITY,
+ Math.min((int) (mQuality * Math.sqrt((1.0 * mByteLimit) / currentSize)),
+ (int) (mQuality * QUALITY_SCALE_DOWN_RATIO)));
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: Retrying at quality " + mQuality);
+ }
+ } else if (currentSize > 0 &&
+ mScaleFactor < 2.0 * MIN_SCALE_DOWN_RATIO * MIN_SCALE_DOWN_RATIO) {
+ // JPEG compression failed to hit target size - need smaller image
+ // First try scaling by a little (< factor of 2) just so long resulting scale down
+ // ratio is still significantly bigger than next subsampling step
+ // i.e. mScaleFactor/MIN_SCALE_DOWN_RATIO (new scaling factor) <
+ // 2.0 / MIN_SCALE_DOWN_RATIO (arbitrary limit)
+ mQuality = IMAGE_COMPRESSION_QUALITY;
+ mScaleFactor = mScaleFactor / MIN_SCALE_DOWN_RATIO;
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: Retrying at scale " + mScaleFactor);
+ }
+ // Release scaled bitmap to trigger rescaling
+ if (mScaled != null && mScaled != mDecoded) {
+ mScaled.recycle();
+ }
+ mScaled = null;
+ } else if (currentSize <= 0 && !mHasReclaimedMemory) {
+ // Then before we subsample try cleaning up our cached memory
+ Factory.get().reclaimMemory();
+ mHasReclaimedMemory = true;
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: Retrying after reclaiming memory ");
+ }
+ } else {
+ // Last resort - subsample image by another factor of 2 and try again
+ mSampleSize = mSampleSize * 2;
+ mQuality = IMAGE_COMPRESSION_QUALITY;
+ mScaleFactor = 1.0f;
+ if (logv) {
+ LogUtil.v(LogUtil.BUGLE_IMAGE_TAG,
+ "getResizedImageData: Retrying at sampleSize " + mSampleSize);
+ }
+ // Release all bitmaps to trigger subsampling
+ if (mScaled != null && mScaled != mDecoded) {
+ mScaled.recycle();
+ }
+ mScaled = null;
+ if (mDecoded != null) {
+ mDecoded.recycle();
+ mDecoded = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Scales and center-crops a bitmap to the size passed in and returns the new bitmap.
+ *
+ * @param source Bitmap to scale and center-crop
+ * @param newWidth destination width
+ * @param newHeight destination height
+ * @return Bitmap scaled and center-cropped bitmap
+ */
+ public static Bitmap scaleCenterCrop(final Bitmap source, final int newWidth,
+ final int newHeight) {
+ final int sourceWidth = source.getWidth();
+ final int sourceHeight = source.getHeight();
+
+ // Compute the scaling factors to fit the new height and width, respectively.
+ // To cover the final image, the final scaling will be the bigger
+ // of these two.
+ final float xScale = (float) newWidth / sourceWidth;
+ final float yScale = (float) newHeight / sourceHeight;
+ final float scale = Math.max(xScale, yScale);
+
+ // Now get the size of the source bitmap when scaled
+ final float scaledWidth = scale * sourceWidth;
+ final float scaledHeight = scale * sourceHeight;
+
+ // Let's find out the upper left coordinates if the scaled bitmap
+ // should be centered in the new size give by the parameters
+ final float left = (newWidth - scaledWidth) / 2;
+ final float top = (newHeight - scaledHeight) / 2;
+
+ // The target rectangle for the new, scaled version of the source bitmap will now
+ // be
+ final RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
+
+ // Finally, we create a new bitmap of the specified size and draw our new,
+ // scaled bitmap onto it.
+ final Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
+ final Canvas canvas = new Canvas(dest);
+ canvas.drawBitmap(source, null, targetRect, null);
+
+ return dest;
+ }
+
+ /**
+ * The drawable can be a Nine-Patch. If we directly use the same drawable instance for each
+ * drawable of different sizes, then the drawable sizes would interfere with each other. The
+ * solution here is to create a new drawable instance for every time with the SAME
+ * ConstantState (i.e. sharing the same common state such as the bitmap, so that we don't have
+ * to recreate the bitmap resource), and apply the different properties on top (nine-patch
+ * size and color tint).
+ *
+ * TODO: we are creating new drawable instances here, but there are optimizations that
+ * can be made. For example, message bubbles shouldn't need the mutate() call and the
+ * play/pause buttons shouldn't need to create new drawable from the constant state.
+ */
+ public static Drawable getTintedDrawable(final Context context, final Drawable drawable,
+ final int color) {
+ // For some reason occassionally drawables on JB has a null constant state
+ final Drawable.ConstantState constantStateDrawable = drawable.getConstantState();
+ final Drawable retDrawable = (constantStateDrawable != null)
+ ? constantStateDrawable.newDrawable(context.getResources()).mutate()
+ : drawable;
+ retDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ return retDrawable;
+ }
+
+ /**
+ * Decodes image resource header and returns the image size.
+ */
+ public static Rect decodeImageBounds(final Context context, final Uri imageUri) {
+ final ContentResolver cr = context.getContentResolver();
+ try {
+ final InputStream inputStream = cr.openInputStream(imageUri);
+ if (inputStream != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(inputStream, null, options);
+ return new Rect(0, 0, options.outWidth, options.outHeight);
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ LogUtil.e(TAG, "Couldn't open input stream for uri = " + imageUri);
+ }
+ return new Rect(0, 0, ImageRequest.UNSPECIFIED_SIZE, ImageRequest.UNSPECIFIED_SIZE);
+ }
+}
diff --git a/src/com/android/messaging/util/ImeUtil.java b/src/com/android/messaging/util/ImeUtil.java
new file mode 100644
index 0000000..ab0df13
--- /dev/null
+++ b/src/com/android/messaging/util/ImeUtil.java
@@ -0,0 +1,88 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class ImeUtil {
+ public interface ImeStateObserver {
+ void onImeStateChanged(boolean imeOpen);
+ }
+
+ public interface ImeStateHost {
+ void onDisplayHeightChanged(int heightMeasureSpec);
+ void registerImeStateObserver(ImeUtil.ImeStateObserver observer);
+ void unregisterImeStateObserver(ImeUtil.ImeStateObserver observer);
+ boolean isImeOpen();
+ }
+
+ private static volatile ImeUtil sInstance;
+
+ // Used to clear the static cached instance of ImeUtil during testing. This is necessary
+ // because a previous test may have installed a mocked instance (or vice versa).
+ public static void clearInstance() {
+ sInstance = null;
+ }
+ public static ImeUtil get() {
+ if (sInstance == null) {
+ synchronized (ImeUtil.class) {
+ if (sInstance == null) {
+ sInstance = new ImeUtil();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ public static void set(final ImeUtil imeUtil) {
+ sInstance = imeUtil;
+ }
+
+ public void hideImeKeyboard(@NonNull final Context context, @NonNull final View v) {
+ Assert.notNull(context);
+ Assert.notNull(v);
+
+ final InputMethodManager inputMethodManager =
+ (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (inputMethodManager != null) {
+ inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0 /* flags */);
+ }
+ }
+
+ public void showImeKeyboard(@NonNull final Context context, @NonNull final View v) {
+ Assert.notNull(context);
+ Assert.notNull(v);
+
+ final InputMethodManager inputMethodManager =
+ (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (inputMethodManager != null) {
+ v.requestFocus();
+ inputMethodManager.showSoftInput(v, 0 /* flags */);
+ }
+ }
+
+ public static void hideSoftInput(@NonNull final Context context, @NonNull final View v) {
+ final InputMethodManager inputMethodManager =
+ (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+}
diff --git a/src/com/android/messaging/util/LogSaver.java b/src/com/android/messaging/util/LogSaver.java
new file mode 100644
index 0000000..7d1f2fd
--- /dev/null
+++ b/src/com/android/messaging/util/LogSaver.java
@@ -0,0 +1,293 @@
+/*
+ * 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.util;
+
+import android.os.Process;
+import android.util.Log;
+
+import com.android.messaging.Factory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.logging.FileHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Save the app's own log to dump along with adb bugreport
+ */
+public abstract class LogSaver {
+ /**
+ * Writes the accumulated log entries, from oldest to newest, to the specified PrintWriter.
+ * Log lines are emitted in much the same form as logcat -v threadtime -- specifically,
+ * lines will include a timestamp, pid, tid, level, and tag.
+ *
+ * @param writer The PrintWriter to output
+ */
+ public abstract void dump(PrintWriter writer);
+
+ /**
+ * Log a line
+ *
+ * @param level The log level to use
+ * @param tag The log tag
+ * @param msg The message of the log line
+ */
+ public abstract void log(int level, String tag, String msg);
+
+ /**
+ * Check if the LogSaver still matches the current Gservices settings
+ *
+ * @return true if matches, false otherwise
+ */
+ public abstract boolean isCurrent();
+
+ private LogSaver() {
+ }
+
+ public static LogSaver newInstance() {
+ final boolean persistent = BugleGservices.get().getBoolean(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
+ if (persistent) {
+ final int setSize = BugleGservices.get().getInt(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
+ final int fileLimitBytes = BugleGservices.get().getInt(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
+ return new DiskLogSaver(setSize, fileLimitBytes);
+ } else {
+ final int size = BugleGservices.get().getInt(
+ BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
+ BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
+ return new MemoryLogSaver(size);
+ }
+ }
+
+ /**
+ * A circular in-memory log to be used to log potentially verbose logs. The logs will be
+ * persisted in memory in the application and can be dumped by various dump() methods.
+ * For example, adb shell dumpsys activity provider com.android.messaging.
+ * The dump will also show up in bugreports.
+ */
+ private static final class MemoryLogSaver extends LogSaver {
+ /**
+ * Record to store a single log entry. Stores timestamp, tid, level, tag, and message.
+ * It can be reused when the circular log rolls over. This avoids creating new objects.
+ */
+ private static class LogRecord {
+ int mTid;
+ String mLevelString;
+ long mTimeMillis; // from System.currentTimeMillis
+ String mTag;
+ String mMessage;
+
+ LogRecord() {
+ }
+
+ void set(int tid, int level, long time, String tag, String message) {
+ this.mTid = tid;
+ this.mTimeMillis = time;
+ this.mTag = tag;
+ this.mMessage = message;
+ this.mLevelString = getLevelString(level);
+ }
+ }
+
+ private final int mSize;
+ private final CircularArray<LogRecord> mLogList;
+ private final Object mLock;
+
+ private final SimpleDateFormat mSdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+
+ public MemoryLogSaver(final int size) {
+ mSize = size;
+ mLogList = new CircularArray<LogRecord>(size);
+ mLock = new Object();
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ int pid = Process.myPid();
+ synchronized (mLock) {
+ for (int i = 0; i < mLogList.count(); i++) {
+ LogRecord rec = mLogList.get(i);
+ writer.println(String.format("%s %5d %5d %s %s: %s",
+ mSdf.format(rec.mTimeMillis),
+ pid, rec.mTid, rec.mLevelString, rec.mTag, rec.mMessage));
+ }
+ }
+ }
+
+ @Override
+ public void log(int level, String tag, String msg) {
+ synchronized (mLock) {
+ LogRecord rec = mLogList.getFree();
+ if (rec == null) {
+ rec = new LogRecord();
+ }
+ rec.set(Process.myTid(), level, System.currentTimeMillis(), tag, msg);
+ mLogList.add(rec);
+ }
+ }
+
+ @Override
+ public boolean isCurrent() {
+ final boolean persistent = BugleGservices.get().getBoolean(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
+ if (persistent) {
+ return false;
+ }
+ final int size = BugleGservices.get().getInt(
+ BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT,
+ BugleGservicesKeys.IN_MEMORY_LOGSAVER_RECORD_COUNT_DEFAULT);
+ return size == mSize;
+ }
+ }
+
+ /**
+ * A persistent, on-disk log saver. It uses the standard Java util logger along with
+ * a rotation log file set to store the logs in app's local file directory "app_logs".
+ */
+ private static final class DiskLogSaver extends LogSaver {
+ private static final String DISK_LOG_DIR_NAME = "logs";
+
+ private final int mSetSize;
+ private final int mFileLimitBytes;
+ private Logger mDiskLogger;
+
+ public DiskLogSaver(final int setSize, final int fileLimitBytes) {
+ Assert.isTrue(setSize > 0);
+ Assert.isTrue(fileLimitBytes > 0);
+ mSetSize = setSize;
+ mFileLimitBytes = fileLimitBytes;
+ initDiskLog();
+ }
+
+ private static void clearDefaultHandlers(Logger logger) {
+ Assert.notNull(logger);
+ for (Handler handler : logger.getHandlers()) {
+ logger.removeHandler(handler);
+ }
+ }
+
+ private void initDiskLog() {
+ mDiskLogger = Logger.getLogger(LogUtil.BUGLE_TAG);
+ // We don't want the default console handler
+ clearDefaultHandlers(mDiskLogger);
+ // Don't want duplicate print in system log
+ mDiskLogger.setUseParentHandlers(false);
+ // FileHandler manages the log files in a fixed rotation set
+ final File logDir = Factory.get().getApplicationContext().getDir(
+ DISK_LOG_DIR_NAME, 0/*mode*/);
+ FileHandler handler = null;
+ try {
+ handler = new FileHandler(
+ logDir + "/%g.log", mFileLimitBytes, mSetSize, true/*append*/);
+ } catch (Exception e) {
+ Log.e(LogUtil.BUGLE_TAG, "LogSaver: fail to init disk logger", e);
+ return;
+ }
+ final Formatter formatter = new Formatter() {
+ @Override
+ public String format(java.util.logging.LogRecord r) {
+ return r.getMessage();
+ }
+ };
+ handler.setFormatter(formatter);
+ handler.setLevel(Level.ALL);
+ mDiskLogger.addHandler(handler);
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ for (int i = mSetSize - 1; i >= 0; i--) {
+ final File logDir = Factory.get().getApplicationContext().getDir(
+ DISK_LOG_DIR_NAME, 0/*mode*/);
+ final String logFilePath = logDir + "/" + i + ".log";
+ try {
+ final File logFile = new File(logFilePath);
+ if (!logFile.exists()) {
+ continue;
+ }
+ final BufferedReader reader = new BufferedReader(new FileReader(logFile));
+ for (String line; (line = reader.readLine()) != null;) {
+ line = line.trim();
+ writer.println(line);
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not find log file " + logFilePath);
+ } catch (IOException e) {
+ Log.w(LogUtil.BUGLE_TAG, "LogSaver: can not read log file", e);
+ }
+ }
+ }
+
+ @Override
+ public void log(int level, String tag, String msg) {
+ final SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ mDiskLogger.info(String.format("%s %5d %5d %s %s: %s\n",
+ sdf.format(System.currentTimeMillis()),
+ Process.myPid(), Process.myTid(), getLevelString(level), tag, msg));
+ }
+
+ @Override
+ public boolean isCurrent() {
+ final boolean persistent = BugleGservices.get().getBoolean(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_DEFAULT);
+ if (!persistent) {
+ return false;
+ }
+ final int setSize = BugleGservices.get().getInt(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_ROTATION_SET_SIZE_DEFAULT);
+ final int fileLimitBytes = BugleGservices.get().getInt(
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES,
+ BugleGservicesKeys.PERSISTENT_LOGSAVER_FILE_LIMIT_BYTES_DEFAULT);
+ return setSize == mSetSize && fileLimitBytes == mFileLimitBytes;
+ }
+ }
+
+ private static String getLevelString(final int level) {
+ switch (level) {
+ case android.util.Log.DEBUG:
+ return "D";
+ case android.util.Log.WARN:
+ return "W";
+ case android.util.Log.INFO:
+ return "I";
+ case android.util.Log.VERBOSE:
+ return "V";
+ case android.util.Log.ERROR:
+ return "E";
+ case android.util.Log.ASSERT:
+ return "A";
+ default:
+ return "?";
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/LogUtil.java b/src/com/android/messaging/util/LogUtil.java
new file mode 100644
index 0000000..021f39b
--- /dev/null
+++ b/src/com/android/messaging/util/LogUtil.java
@@ -0,0 +1,274 @@
+/*
+ * 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.util;
+
+/**
+ * Log utility class.
+ */
+public class LogUtil {
+ public static final String BUGLE_TAG = "MessagingApp";
+ public static final String PROFILE_TAG = "MessagingAppProf";
+ public static final String BUGLE_DATABASE_TAG = "MessagingAppDb";
+ public static final String BUGLE_DATABASE_PERF_TAG = "MessagingAppDbPerf";
+ public static final String BUGLE_DATAMODEL_TAG = "MessagingAppDataModel";
+ public static final String BUGLE_IMAGE_TAG = "MessagingAppImage";
+ public static final String BUGLE_NOTIFICATIONS_TAG = "MessagingAppNotif";
+ public static final String BUGLE_WIDGET_TAG = "MessagingAppWidget";
+
+ public static final int DEBUG = android.util.Log.DEBUG;
+ public static final int WARN = android.util.Log.WARN;
+ public static final int VERBOSE = android.util.Log.VERBOSE;
+ public static final int INFO = android.util.Log.INFO;
+ public static final int ERROR = android.util.Log.ERROR;
+
+ // If this is non-null, DEBUG and higher logs will be tracked in-memory. It will not include
+ // VERBOSE logs.
+ private static LogSaver sDebugLogSaver;
+ private static volatile boolean sCaptureDebugLogs;
+
+ /**
+ * Read Gservices to see if logging should be enabled.
+ */
+ public static void refreshGservices(final BugleGservices gservices) {
+ sCaptureDebugLogs = gservices.getBoolean(
+ BugleGservicesKeys.ENABLE_LOG_SAVER,
+ BugleGservicesKeys.ENABLE_LOG_SAVER_DEFAULT);
+ if (sCaptureDebugLogs && (sDebugLogSaver == null || !sDebugLogSaver.isCurrent())) {
+ // We were not capturing logs before. We are now.
+ sDebugLogSaver = LogSaver.newInstance();
+ } else if (!sCaptureDebugLogs && sDebugLogSaver != null) {
+ // We were capturing logs. We aren't anymore.
+ sDebugLogSaver = null;
+ }
+ }
+
+ // This is called from FactoryImpl once the Gservices class is initialized.
+ public static void initializeGservices (final BugleGservices gservices) {
+ gservices.registerForChanges(new Runnable() {
+ @Override
+ public void run() {
+ refreshGservices(gservices);
+ }
+ });
+ refreshGservices(gservices);
+ }
+
+ /**
+ * Send a {@link #VERBOSE} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void v(final String tag, final String msg) {
+ println(android.util.Log.VERBOSE, tag, msg);
+ }
+
+ /**
+ * Send a {@link #VERBOSE} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static void v(final String tag, final String msg, final Throwable tr) {
+ println(android.util.Log.VERBOSE, tag, msg + '\n'
+ + android.util.Log.getStackTraceString(tr));
+ }
+
+ /**
+ * Send a {@link #DEBUG} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void d(final String tag, final String msg) {
+ println(android.util.Log.DEBUG, tag, msg);
+ }
+
+ /**
+ * Send a {@link #DEBUG} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static void d(final String tag, final String msg, final Throwable tr) {
+ println(android.util.Log.DEBUG, tag, msg + '\n'
+ + android.util.Log.getStackTraceString(tr));
+ }
+
+ /**
+ * Send an {@link #INFO} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void i(final String tag, final String msg) {
+ println(android.util.Log.INFO, tag, msg);
+ }
+
+ /**
+ * Send a {@link #INFO} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static void i(final String tag, final String msg, final Throwable tr) {
+ println(android.util.Log.INFO, tag, msg + '\n'
+ + android.util.Log.getStackTraceString(tr));
+ }
+
+ /**
+ * Send a {@link #WARN} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void w(final String tag, final String msg) {
+ println(android.util.Log.WARN, tag, msg);
+ }
+
+ /**
+ * Send a {@link #WARN} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static void w(final String tag, final String msg, final Throwable tr) {
+ println(android.util.Log.WARN, tag, msg);
+ println(android.util.Log.WARN, tag, android.util.Log.getStackTraceString(tr));
+ }
+
+ /**
+ * Send an {@link #ERROR} log message.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void e(final String tag, final String msg) {
+ println(android.util.Log.ERROR, tag, msg);
+ }
+
+ /**
+ * Send a {@link #ERROR} log message and log the exception.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static void e(final String tag, final String msg, final Throwable tr) {
+ println(android.util.Log.ERROR, tag, msg);
+ println(android.util.Log.ERROR, tag, android.util.Log.getStackTraceString(tr));
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static void wtf(final String tag, final String msg) {
+ // Make sure this goes into our log buffer
+ println(android.util.Log.ASSERT, tag, "wtf\n" + msg);
+ android.util.Log.wtf(tag, msg, new Exception());
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log
+ */
+ public static void wtf(final String tag, final String msg, final Throwable tr) {
+ // Make sure this goes into our log buffer
+ println(android.util.Log.ASSERT, tag, "wtf\n" + msg + '\n' +
+ android.util.Log.getStackTraceString(tr));
+ android.util.Log.wtf(tag, msg, tr);
+ }
+
+ /**
+ * Low-level logging call.
+ * @param level The priority/type of this log message
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ private static void println(final int level, final String tag, final String msg) {
+ android.util.Log.println(level, tag, msg);
+
+ LogSaver serviceLog = sDebugLogSaver;
+ if (serviceLog != null && level >= android.util.Log.DEBUG) {
+ serviceLog.log(level, tag, msg);
+ }
+ }
+
+ /**
+ * Save logging into LogSaver only, for dumping to bug report
+ *
+ * @param level The priority/type of this log message
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param msg The message you would like logged.
+ */
+ public static void save(final int level, final String tag, final String msg) {
+ LogSaver serviceLog = sDebugLogSaver;
+ if (serviceLog != null) {
+ serviceLog.log(level, tag, msg);
+ }
+ }
+
+ /**
+ * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+ * See {@link android.util.Log#isLoggable(String, int)} for more discussion.
+ */
+ public static boolean isLoggable(final String tag, final int level) {
+ return android.util.Log.isLoggable(tag, level);
+ }
+
+ /**
+ * Returns text as is if {@value #BUGLE_TAG}'s log level is set to DEBUG or VERBOSE;
+ * returns "--" otherwise. Useful for log statements where we don't want to log
+ * various strings (e.g., usernames) with default logging to avoid leaking PII in logcat.
+ */
+ public static String sanitizePII(final String text) {
+ if (text == null) {
+ return null;
+ }
+
+ if (android.util.Log.isLoggable(BUGLE_TAG, android.util.Log.DEBUG)) {
+ return text;
+ } else {
+ return "Redacted-" + text.length();
+ }
+ }
+
+ public static void dump(java.io.PrintWriter out) {
+ final LogSaver logsaver = sDebugLogSaver;
+ if (logsaver != null) {
+ logsaver.dump(out);
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/LoggingTimer.java b/src/com/android/messaging/util/LoggingTimer.java
new file mode 100644
index 0000000..d0d41ac
--- /dev/null
+++ b/src/com/android/messaging/util/LoggingTimer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util;
+
+import android.os.SystemClock;
+
+/**
+ * A utility timer that logs the execution time of operations
+ */
+public class LoggingTimer {
+ private static final int NO_WARN_LIMIT = -1;
+
+ private final String mTag;
+ private final String mName;
+ private final long mWarnLimitMillis;
+ private long mStartMillis;
+
+ public LoggingTimer(final String tag, final String name) {
+ this(tag, name, NO_WARN_LIMIT);
+ }
+
+ public LoggingTimer(final String tag, final String name, final long warnLimitMillis) {
+ mTag = tag;
+ mName = name;
+ mWarnLimitMillis = warnLimitMillis;
+ }
+
+ /**
+ * This method should be called at the start of the operation to be timed.
+ */
+ public void start() {
+ mStartMillis = SystemClock.elapsedRealtime();
+
+ if (LogUtil.isLoggable(mTag, LogUtil.VERBOSE)) {
+ LogUtil.v(mTag, "Timer start for " + mName);
+ }
+ }
+
+ /**
+ * This method should be called at the end of the operation to be timed. It logs the time since
+ * the last call to {@link #start}
+ */
+ public void stopAndLog() {
+ final long elapsedMs = SystemClock.elapsedRealtime() - mStartMillis;
+
+ final String logMessage = String.format("Used %dms for %s", elapsedMs, mName);
+
+ LogUtil.save(LogUtil.DEBUG, mTag, logMessage);
+
+ if (mWarnLimitMillis != NO_WARN_LIMIT && elapsedMs > mWarnLimitMillis) {
+ LogUtil.w(mTag, logMessage);
+ } else if (LogUtil.isLoggable(mTag, LogUtil.VERBOSE)) {
+ LogUtil.v(mTag, logMessage);
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/LongSparseSet.java b/src/com/android/messaging/util/LongSparseSet.java
new file mode 100644
index 0000000..8e2cfca
--- /dev/null
+++ b/src/com/android/messaging/util/LongSparseSet.java
@@ -0,0 +1,59 @@
+/*
+ * 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.util;
+
+import android.support.v4.util.LongSparseArray;
+
+/**
+ * A space saving set for long values using v4 compat LongSparseArray
+ */
+public class LongSparseSet {
+ private static final Object THE_ONLY_VALID_VALUE = new Object();
+ private final LongSparseArray<Object> mSet = new LongSparseArray<Object>();
+
+ public LongSparseSet() {
+ }
+
+ /**
+ * @param key The element to check
+ * @return True if the element is in the set, false otherwise
+ */
+ public boolean contains(long key) {
+ if (mSet.get(key, null/*default*/) == THE_ONLY_VALID_VALUE) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add an element to the set
+ *
+ * @param key The element to add
+ */
+ public void add(long key) {
+ mSet.put(key, THE_ONLY_VALID_VALUE);
+ }
+
+ /**
+ * Remove an element from the set
+ *
+ * @param key The element to remove
+ */
+ public void remove(long key) {
+ mSet.delete(key);
+ }
+}
diff --git a/src/com/android/messaging/util/MaterialPalette.java b/src/com/android/messaging/util/MaterialPalette.java
new file mode 100644
index 0000000..8dbacbf
--- /dev/null
+++ b/src/com/android/messaging/util/MaterialPalette.java
@@ -0,0 +1,27 @@
+/*
+ * 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.util;
+
+
+public class MaterialPalette{
+ public final int mPrimaryColor;
+ public final int mSecondaryColor;
+
+ public MaterialPalette(final int primaryColor, final int secondaryColor) {
+ mPrimaryColor = primaryColor;
+ mSecondaryColor = secondaryColor;
+ }
+}
diff --git a/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java b/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java
new file mode 100644
index 0000000..b1078d1
--- /dev/null
+++ b/src/com/android/messaging/util/MediaMetadataRetrieverWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.util;
+
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+
+import java.io.IOException;
+
+/**
+ * Convenience wrapper for {@link MediaMetadataRetriever} to help with its eccentric error handling.
+ */
+public class MediaMetadataRetrieverWrapper {
+ private final MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
+
+ public MediaMetadataRetrieverWrapper() {
+ }
+
+ public void setDataSource(Uri uri) throws IOException {
+ ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
+ AssetFileDescriptor fd = resolver.openAssetFileDescriptor(uri, "r");
+ if (fd == null) {
+ throw new IOException("openAssetFileDescriptor returned null for " + uri);
+ }
+ try {
+ mRetriever.setDataSource(fd.getFileDescriptor());
+ } catch (RuntimeException e) {
+ release();
+ throw new IOException(e);
+ } finally {
+ fd.close();
+ }
+ }
+
+ public int extractInteger(final int key, final int defaultValue) {
+ final String s = mRetriever.extractMetadata(key);
+ if (TextUtils.isEmpty(s)) {
+ return defaultValue;
+ }
+ return Integer.parseInt(s);
+ }
+
+ public String extractMetadata(final int key) {
+ return mRetriever.extractMetadata(key);
+ }
+
+ public Bitmap getFrameAtTime() {
+ return mRetriever.getFrameAtTime();
+ }
+
+ public Bitmap getFrameAtTime(final long timeUs) {
+ return mRetriever.getFrameAtTime(timeUs);
+ }
+
+ public void release() {
+ try {
+ mRetriever.release();
+ } catch (RuntimeException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "MediaMetadataRetriever.release failed", e);
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/MediaUtil.java b/src/com/android/messaging/util/MediaUtil.java
new file mode 100644
index 0000000..f25354c
--- /dev/null
+++ b/src/com/android/messaging/util/MediaUtil.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+
+import com.android.messaging.Factory;
+
+public abstract class MediaUtil {
+ public static interface OnCompletionListener {
+ public void onCompletion();
+ }
+
+ public static MediaUtil get() {
+ return Factory.get().getMediaUtil();
+ }
+
+ /**
+ * Play sound from local resources given a resource id.
+ */
+ public abstract void playSound(final Context context, final int resId,
+ final OnCompletionListener completionListener);
+}
diff --git a/src/com/android/messaging/util/MediaUtilImpl.java b/src/com/android/messaging/util/MediaUtilImpl.java
new file mode 100644
index 0000000..272a057
--- /dev/null
+++ b/src/com/android/messaging/util/MediaUtilImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+
+/**
+ * Default implementation of MediaUtil
+ */
+public class MediaUtilImpl extends MediaUtil {
+
+ @Override
+ public void playSound(final Context context, final int resId,
+ final OnCompletionListener completionListener) {
+ // We want to play at the media volume and not the ringer volume, but we do want to
+ // avoid playing sound when the ringer/notifications are silenced. This is used for
+ // in app sounds that are not critical and should not impact running silent but also
+ // shouldn't play at full ring volume if you want to hear your ringer but don't want
+ // to be annoyed with in-app volume.
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ try {
+ final MediaPlayer mediaPlayer = new MediaPlayer();
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+ mediaPlayer.setDataSource(
+ afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+ afd.close();
+ mediaPlayer.prepare();
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(final MediaPlayer mp) {
+ if (completionListener != null) {
+ completionListener.onCompletion();
+ }
+ mp.stop();
+ mp.release();
+ }
+ });
+ mediaPlayer.seekTo(0);
+ mediaPlayer.start();
+ return;
+ } catch (final Exception e) {
+ LogUtil.w("MediaUtilImpl", "Error playing sound id: " + resId, e);
+ }
+ if (completionListener != null) {
+ // Call the completion handler to not block functionality if audio play fails
+ completionListener.onCompletion();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/messaging/util/NotificationPlayer.java b/src/com/android/messaging/util/NotificationPlayer.java
new file mode 100644
index 0000000..a4ed44e
--- /dev/null
+++ b/src/com/android/messaging/util/NotificationPlayer.java
@@ -0,0 +1,363 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.net.Uri;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+
+import com.android.messaging.Factory;
+
+import java.util.LinkedList;
+
+/**
+ * This class is provides the same interface and functionality as android.media.AsyncPlayer
+ * with the following differences:
+ * - whenever audio is played, audio focus is requested,
+ * - whenever audio playback is stopped or the playback completed, audio focus is abandoned.
+ *
+ * This file has been copied from com.android.server.NotificationPlayer. The only modification is
+ * the addition of a volume parameter. Hopefully the framework will adapt AsyncPlayer to support
+ * all the functionality in this class, at which point this one can be deleted.
+ */
+public class NotificationPlayer implements OnCompletionListener {
+ private static final int PLAY = 1;
+ private static final int STOP = 2;
+ private static final boolean mDebug = false;
+
+ private static final class Command {
+ int code;
+ Uri uri;
+ boolean looping;
+ int stream;
+ float volume;
+ long requestTime;
+ boolean releaseFocus;
+
+ @Override
+ public String toString() {
+ return "{ code=" + code + " looping=" + looping + " stream=" + stream
+ + " uri=" + uri + " }";
+ }
+ }
+
+ private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
+
+ private Looper mLooper;
+
+ /*
+ * Besides the use of audio focus, the only implementation difference between AsyncPlayer and
+ * NotificationPlayer resides in the creation of the MediaPlayer. For the completion callback,
+ * OnCompletionListener, to be called at the end of the playback, the MediaPlayer needs to
+ * be created with a looper running so its event handler is not null.
+ */
+ private final class CreationAndCompletionThread extends Thread {
+ public Command mCmd;
+ public CreationAndCompletionThread(final Command cmd) {
+ super();
+ mCmd = cmd;
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ synchronized (this) {
+ final AudioManager audioManager =
+ (AudioManager) Factory.get().getApplicationContext()
+ .getSystemService(Context.AUDIO_SERVICE);
+ try {
+ final MediaPlayer player = new MediaPlayer();
+ player.setAudioStreamType(mCmd.stream);
+ player.setDataSource(Factory.get().getApplicationContext(), mCmd.uri);
+ player.setLooping(mCmd.looping);
+ player.setVolume(mCmd.volume, mCmd.volume);
+ player.prepare();
+ if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
+ && (mCmd.uri.getEncodedPath().length() > 0)) {
+ audioManager.requestAudioFocus(null, mCmd.stream,
+ mCmd.looping ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
+ : AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
+ }
+ player.setOnCompletionListener(NotificationPlayer.this);
+ player.start();
+ if (mPlayer != null) {
+ mPlayer.release();
+ }
+ mPlayer = player;
+ } catch (final Exception e) {
+ LogUtil.w(mTag, "error loading sound for " + mCmd.uri, e);
+ }
+ mAudioManager = audioManager;
+ this.notify();
+ }
+ Looper.loop();
+ }
+ }
+
+ private void startSound(final Command cmd) {
+ // Preparing can be slow, so if there is something else
+ // is playing, let it continue until we're done, so there
+ // is less of a glitch.
+ try {
+ if (mDebug) {
+ LogUtil.d(mTag, "Starting playback");
+ }
+ //-----------------------------------
+ // This is were we deviate from the AsyncPlayer implementation and create the
+ // MediaPlayer in a new thread with which we're synchronized
+ synchronized (mCompletionHandlingLock) {
+ // if another sound was already playing, it doesn't matter we won't get notified
+ // of the completion, since only the completion notification of the last sound
+ // matters
+ if ((mLooper != null)
+ && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+ mLooper.quit();
+ }
+ mCompletionThread = new CreationAndCompletionThread(cmd);
+ synchronized (mCompletionThread) {
+ mCompletionThread.start();
+ mCompletionThread.wait();
+ }
+ }
+ //-----------------------------------
+
+ final long delay = SystemClock.elapsedRealtime() - cmd.requestTime;
+ if (delay > 1000) {
+ LogUtil.w(mTag, "Notification sound delayed by " + delay + "msecs");
+ }
+ } catch (final Exception e) {
+ LogUtil.w(mTag, "error loading sound for " + cmd.uri, e);
+ }
+ }
+
+ private void stopSound(final Command cmd) {
+ if (mPlayer == null) {
+ return;
+ }
+ final long delay = SystemClock.elapsedRealtime() - cmd.requestTime;
+ if (delay > 1000) {
+ LogUtil.w(mTag, "Notification stop delayed by " + delay + "msecs");
+ }
+ mPlayer.stop();
+ mPlayer.release();
+ mPlayer = null;
+ if (cmd.releaseFocus && mAudioManager != null) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ mAudioManager = null;
+ if ((mLooper != null) && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+ mLooper.quit();
+ }
+ }
+
+ private final class CmdThread extends java.lang.Thread {
+ CmdThread() {
+ super("NotificationPlayer-" + mTag);
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ Command cmd = null;
+
+ synchronized (mCmdQueue) {
+ if (mDebug) {
+ LogUtil.d(mTag, "RemoveFirst");
+ }
+ cmd = mCmdQueue.removeFirst();
+ }
+
+ switch (cmd.code) {
+ case PLAY:
+ if (mDebug) {
+ LogUtil.d(mTag, "PLAY");
+ }
+ startSound(cmd);
+ break;
+ case STOP:
+ if (mDebug) {
+ LogUtil.d(mTag, "STOP");
+ }
+ stopSound(cmd);
+ break;
+ }
+
+ synchronized (mCmdQueue) {
+ if (mCmdQueue.size() == 0) {
+ // nothing left to do, quit
+ // doing this check after we're done prevents the case where they
+ // added it during the operation from spawning two threads and
+ // trying to do them in parallel.
+ mThread = null;
+ releaseWakeLock();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCompletion(final MediaPlayer mp) {
+ if (mAudioManager != null) {
+ mAudioManager.abandonAudioFocus(null);
+ }
+ // if there are no more sounds to play, end the Looper to listen for media completion
+ synchronized (mCmdQueue) {
+ if (mCmdQueue.size() == 0) {
+ synchronized (mCompletionHandlingLock) {
+ if (mLooper != null) {
+ mLooper.quit();
+ }
+ mCompletionThread = null;
+ }
+ }
+ }
+ }
+
+ private String mTag;
+ private CmdThread mThread;
+ private CreationAndCompletionThread mCompletionThread;
+ private final Object mCompletionHandlingLock = new Object();
+ private MediaPlayer mPlayer;
+ private PowerManager.WakeLock mWakeLock;
+ private AudioManager mAudioManager;
+
+ // The current state according to the caller. Reality lags behind
+ // because of the asynchronous nature of this class.
+ private int mState = STOP;
+
+ /**
+ * Construct a NotificationPlayer object.
+ *
+ * @param tag a string to use for debugging
+ */
+ public NotificationPlayer(final String tag) {
+ if (tag != null) {
+ mTag = tag;
+ } else {
+ mTag = "NotificationPlayer";
+ }
+ }
+
+ /**
+ * Start playing the sound. It will actually start playing at some
+ * point in the future. There are no guarantees about latency here.
+ * Calling this before another audio file is done playing will stop
+ * that one and start the new one.
+ *
+ * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)})
+ * @param looping Whether the audio should loop forever.
+ * (see {@link MediaPlayer#setLooping(boolean)})
+ * @param stream the AudioStream to use.
+ * (see {@link MediaPlayer#setAudioStreamType(int)})
+ * @param volume The volume at which to play this sound, as a fraction of the system volume for
+ * the relevant stream type. A value of 1 is the maximum and means play at the system
+ * volume with no attenuation.
+ */
+ public void play(final Uri uri, final boolean looping, final int stream, final float volume) {
+ final Command cmd = new Command();
+ cmd.requestTime = SystemClock.elapsedRealtime();
+ cmd.code = PLAY;
+ cmd.uri = uri;
+ cmd.looping = looping;
+ cmd.stream = stream;
+ cmd.volume = volume;
+ synchronized (mCmdQueue) {
+ enqueueLocked(cmd);
+ mState = PLAY;
+ }
+ }
+
+ /** Same as calling stop(true) */
+ public void stop() {
+ stop(true);
+ }
+
+ /**
+ * Stop a previously played sound. It can't be played again or unpaused
+ * at this point. Calling this multiple times has no ill effects.
+ * @param releaseAudioFocus whether to release audio focus
+ */
+ public void stop(final boolean releaseAudioFocus) {
+ synchronized (mCmdQueue) {
+ // This check allows stop to be called multiple times without starting
+ // a thread that ends up doing nothing.
+ if (mState != STOP) {
+ final Command cmd = new Command();
+ cmd.requestTime = SystemClock.elapsedRealtime();
+ cmd.code = STOP;
+ cmd.releaseFocus = releaseAudioFocus;
+ enqueueLocked(cmd);
+ mState = STOP;
+ }
+ }
+ }
+
+ private void enqueueLocked(final Command cmd) {
+ mCmdQueue.add(cmd);
+ if (mThread == null) {
+ acquireWakeLock();
+ mThread = new CmdThread();
+ mThread.start();
+ }
+ }
+
+ /**
+ * We want to hold a wake lock while we do the prepare and play. The stop probably is
+ * optional, but it won't hurt to have it too. The problem is that if you start a sound
+ * while you're holding a wake lock (e.g. an alarm starting a notification), you want the
+ * sound to play, but if the CPU turns off before mThread gets to work, it won't. The
+ * simplest way to deal with this is to make it so there is a wake lock held while the
+ * thread is starting or running. You're going to need the WAKE_LOCK permission if you're
+ * going to call this.
+ *
+ * This must be called before the first time play is called.
+ *
+ * @hide
+ */
+ public void setUsesWakeLock() {
+ if (mWakeLock != null || mThread != null) {
+ // if either of these has happened, we've already played something.
+ // and our releases will be out of sync.
+ throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+ + " mThread=" + mThread);
+ }
+ final PowerManager pm = (PowerManager) Factory.get().getApplicationContext()
+ .getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+ }
+
+ private void acquireWakeLock() {
+ if (mWakeLock != null) {
+ mWakeLock.acquire();
+ }
+ }
+
+ private void releaseWakeLock() {
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ }
+ }
+}
+
diff --git a/src/com/android/messaging/util/OsUtil.java b/src/com/android/messaging/util/OsUtil.java
new file mode 100644
index 0000000..e45a63c
--- /dev/null
+++ b/src/com/android/messaging/util/OsUtil.java
@@ -0,0 +1,269 @@
+/*
+ * 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.util;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.messaging.Factory;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Set;
+
+/**
+ * Android OS version utilities
+ */
+public class OsUtil {
+ private static boolean sIsAtLeastICS_MR1;
+ private static boolean sIsAtLeastJB;
+ private static boolean sIsAtLeastJB_MR1;
+ private static boolean sIsAtLeastJB_MR2;
+ private static boolean sIsAtLeastKLP;
+ private static boolean sIsAtLeastL;
+ private static boolean sIsAtLeastL_MR1;
+ private static boolean sIsAtLeastM;
+
+ private static Boolean sIsSecondaryUser = null;
+
+ static {
+ final int v = getApiVersion();
+ sIsAtLeastICS_MR1 = v >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
+ sIsAtLeastJB = v >= android.os.Build.VERSION_CODES.JELLY_BEAN;
+ sIsAtLeastJB_MR1 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+ sIsAtLeastJB_MR2 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+ sIsAtLeastKLP = v >= android.os.Build.VERSION_CODES.KITKAT;
+ sIsAtLeastL = v >= android.os.Build.VERSION_CODES.LOLLIPOP;
+ sIsAtLeastL_MR1 = v >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
+ sIsAtLeastM = v >= android.os.Build.VERSION_CODES.M;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least Ice Cream Sandwich
+ * MR1 (API level 15).
+ */
+ public static boolean isAtLeastICS_MR1() {
+ return sIsAtLeastICS_MR1;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least Jelly Bean
+ * (API level 16).
+ */
+ public static boolean isAtLeastJB() {
+ return sIsAtLeastJB;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least Jelly Bean MR1
+ * (API level 17).
+ */
+ public static boolean isAtLeastJB_MR1() {
+ return sIsAtLeastJB_MR1;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least Jelly Bean MR2
+ * (API level 18).
+ */
+ public static boolean isAtLeastJB_MR2() {
+ return sIsAtLeastJB_MR2;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least KLP
+ * (API level 19).
+ */
+ public static boolean isAtLeastKLP() {
+ return sIsAtLeastKLP;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least L
+ * (API level 21).
+ */
+ public static boolean isAtLeastL() {
+ return sIsAtLeastL;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least L MR1
+ * (API level 22).
+ */
+ public static boolean isAtLeastL_MR1() {
+ return sIsAtLeastL_MR1;
+ }
+
+ /**
+ * @return True if the version of Android that we're running on is at least M
+ * (API level 23).
+ */
+ public static boolean isAtLeastM() {
+ return sIsAtLeastM;
+ }
+
+ /**
+ * @return The Android API version of the OS that we're currently running on.
+ */
+ public static int getApiVersion() {
+ return android.os.Build.VERSION.SDK_INT;
+ }
+
+ public static boolean isSecondaryUser() {
+ if (sIsSecondaryUser == null) {
+ final Context context = Factory.get().getApplicationContext();
+ boolean isSecondaryUser = false;
+
+ // Only check for newer devices (but not the nexus 10)
+ if (OsUtil.sIsAtLeastJB_MR1 && !"Nexus 10".equals(Build.MODEL)) {
+ final UserHandle uh = android.os.Process.myUserHandle();
+ final UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (userManager != null) {
+ final long userSerialNumber = userManager.getSerialNumberForUser(uh);
+ isSecondaryUser = (0 != userSerialNumber);
+ }
+ }
+ sIsSecondaryUser = isSecondaryUser;
+ }
+ return sIsSecondaryUser;
+ }
+
+ /**
+ * Creates a joined string from a Set<String> using the given delimiter.
+ * @param values
+ * @param delimiter
+ * @return
+ */
+ public static String joinFromSetWithDelimiter(
+ final Set<String> values, final String delimiter) {
+ if (values != null) {
+ final StringBuilder joinedStringBuilder = new StringBuilder();
+ boolean firstValue = true;
+ for (final String value : values) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ joinedStringBuilder.append(delimiter);
+ }
+ joinedStringBuilder.append(value);
+ }
+ return joinedStringBuilder.toString();
+ }
+ return null;
+ }
+
+ private static Hashtable<String, Integer> sPermissions = new Hashtable<String, Integer>();
+
+ /**
+ * Check if the app has the specified permission. If it does not, the app needs to use
+ * {@link android.app.Activity#requestPermission}. Note that if it
+ * returns true, it cannot return false in the same process as the OS kills the process when
+ * any permission is revoked.
+ * @param permission A permission from {@link android.Manifest.permission}
+ */
+ public static boolean hasPermission(final String permission) {
+ if (OsUtil.isAtLeastM()) {
+ // It is safe to cache the PERMISSION_GRANTED result as the process gets killed if the
+ // user revokes the permission setting. However, PERMISSION_DENIED should not be
+ // cached as the process does not get killed if the user enables the permission setting.
+ if (!sPermissions.containsKey(permission)
+ || sPermissions.get(permission) == PackageManager.PERMISSION_DENIED) {
+ final Context context = Factory.get().getApplicationContext();
+ final int permissionState = context.checkSelfPermission(permission);
+ sPermissions.put(permission, permissionState);
+ }
+ return sPermissions.get(permission) == PackageManager.PERMISSION_GRANTED;
+ } else {
+ return true;
+ }
+ }
+
+ /** Does the app have all the specified permissions */
+ public static boolean hasPermissions(final String[] permissions) {
+ for (final String permission : permissions) {
+ if (!hasPermission(permission)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean hasPhonePermission() {
+ return hasPermission(Manifest.permission.READ_PHONE_STATE);
+ }
+
+ public static boolean hasSmsPermission() {
+ return hasPermission(Manifest.permission.READ_SMS);
+ }
+
+ public static boolean hasLocationPermission() {
+ return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+
+
+ public static boolean hasStoragePermission() {
+ // Note that READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are granted or denied
+ // together.
+ return OsUtil.hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+
+ public static boolean hasRecordAudioPermission() {
+ return OsUtil.hasPermission(Manifest.permission.RECORD_AUDIO);
+ }
+
+ /**
+ * Returns array with the set of permissions that have not been granted from the given set.
+ * The array will be empty if the app has all of the specified permissions. Note that calling
+ * {@link Activity#requestPermissions} for an already granted permission can prompt the user
+ * again, and its up to the app to only request permissions that are missing.
+ */
+ public static String[] getMissingPermissions(final String[] permissions) {
+ final ArrayList<String> missingList = new ArrayList<String>();
+ for (final String permission : permissions) {
+ if (!hasPermission(permission)) {
+ missingList.add(permission);
+ }
+ }
+
+ final String[] missingArray = new String[missingList.size()];
+ missingList.toArray(missingArray);
+ return missingArray;
+ }
+
+ private static String[] sRequiredPermissions = new String[] {
+ // Required to read existing SMS threads
+ Manifest.permission.READ_SMS,
+ // Required for knowing the phone number, number of SIMs, etc.
+ Manifest.permission.READ_PHONE_STATE,
+ // This is not strictly required, but simplifies the contact picker scenarios
+ Manifest.permission.READ_CONTACTS,
+ };
+
+ /** Does the app have the minimum set of permissions required to operate. */
+ public static boolean hasRequiredPermissions() {
+ return hasPermissions(sRequiredPermissions);
+ }
+
+ public static String[] getMissingRequiredPermissions() {
+ return getMissingPermissions(sRequiredPermissions);
+ }
+}
diff --git a/src/com/android/messaging/util/PendingIntentConstants.java b/src/com/android/messaging/util/PendingIntentConstants.java
new file mode 100644
index 0000000..1a594c5
--- /dev/null
+++ b/src/com/android/messaging/util/PendingIntentConstants.java
@@ -0,0 +1,40 @@
+/*
+ * 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.util;
+
+
+public class PendingIntentConstants {
+ // Notifications
+ public static final int SMS_NOTIFICATION_ID = 0;
+ public static final int SMS_SECONDARY_USER_NOTIFICATION_ID = 1;
+ public static final int MSG_SEND_ERROR = 2;
+ public static final int SMS_STORAGE_LOW_NOTIFICATION_ID = 3;
+
+ // Request codes
+ public static final int UPDATE_NOTIFICATIONS_ALARM_ACTION_ID = 100;
+
+ public static final int MIN_ASSIGNED_REQUEST_CODE = 1001;
+
+ // Logging
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final boolean VERBOSE = false;
+
+ // Internal Constants
+ private static final String NOTIFICATION_REQUEST_CODE_PREFS = "notificationRequestCodes.v1";
+ private static final String REQUEST_CODE_DELIMITER = "|";
+ private static final String MAX_REQUEST_CODE_KEY = "maxRequestCode";
+}
diff --git a/src/com/android/messaging/util/PhoneUtils.java b/src/com/android/messaging/util/PhoneUtils.java
new file mode 100644
index 0000000..726f083
--- /dev/null
+++ b/src/com/android/messaging/util/PhoneUtils.java
@@ -0,0 +1,1011 @@
+/*
+ * 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.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.support.v4.util.ArrayMap;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class abstracts away platform dependency of calling telephony related
+ * platform APIs, mostly involving TelephonyManager, SubscriptionManager and
+ * a bit of SmsManager.
+ *
+ * The class instance can only be obtained via the get(int subId) method parameterized
+ * by a SIM subscription ID. On pre-L_MR1, the subId is not used and it has to be
+ * the default subId (-1).
+ *
+ * A convenient getDefault() method is provided for default subId (-1) on any platform
+ */
+public abstract class PhoneUtils {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private static final int MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT = 6;
+
+ private static final List<SubscriptionInfo> EMPTY_SUBSCRIPTION_LIST = new ArrayList<>();
+
+ // The canonical phone number cache
+ // Each country gets its own cache. The following maps from ISO country code to
+ // the country's cache. Each cache maps from original phone number to canonicalized phone
+ private static final ArrayMap<String, ArrayMap<String, String>> sCanonicalPhoneNumberCache =
+ new ArrayMap<>();
+
+ protected final Context mContext;
+ protected final TelephonyManager mTelephonyManager;
+ protected final int mSubId;
+
+ public PhoneUtils(int subId) {
+ mSubId = subId;
+ mContext = Factory.get().getApplicationContext();
+ mTelephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
+ /**
+ * Get the SIM's country code
+ *
+ * @return the country code on the SIM
+ */
+ public abstract String getSimCountry();
+
+ /**
+ * Get number of SIM slots
+ *
+ * @return the SIM slot count
+ */
+ public abstract int getSimSlotCount();
+
+ /**
+ * Get SIM's carrier name
+ *
+ * @return the carrier name of the SIM
+ */
+ public abstract String getCarrierName();
+
+ /**
+ * Check if there is SIM inserted on the device
+ *
+ * @return true if there is SIM inserted, false otherwise
+ */
+ public abstract boolean hasSim();
+
+ /**
+ * Check if the SIM is roaming
+ *
+ * @return true if the SIM is in romaing state, false otherwise
+ */
+ public abstract boolean isRoaming();
+
+ /**
+ * Get the MCC and MNC in integer of the SIM's provider
+ *
+ * @return an array of two ints, [0] is the MCC code and [1] is the MNC code
+ */
+ public abstract int[] getMccMnc();
+
+ /**
+ * Get the mcc/mnc string
+ *
+ * @return the text of mccmnc string
+ */
+ public abstract String getSimOperatorNumeric();
+
+ /**
+ * Get the SIM's self raw number, i.e. not canonicalized
+ *
+ * @param allowOverride Whether to use the app's setting to override the self number
+ * @return the original self number
+ * @throws IllegalStateException if no active subscription on L-MR1+
+ */
+ public abstract String getSelfRawNumber(final boolean allowOverride);
+
+ /**
+ * Returns the "effective" subId, or the subId used in the context of actual messages,
+ * conversations and subscription-specific settings, for the given "nominal" sub id.
+ *
+ * For pre-L-MR1 platform, this should always be
+ * {@value com.android.messaging.datamodel.data.ParticipantData#DEFAULT_SELF_SUB_ID};
+ *
+ * On the other hand, for L-MR1 and above, DEFAULT_SELF_SUB_ID will be mapped to the system
+ * default subscription id for SMS.
+ *
+ * @param subId The input subId
+ * @return the real subId if we can convert
+ */
+ public abstract int getEffectiveSubId(int subId);
+
+ /**
+ * Returns the number of active subscriptions in the device.
+ */
+ public abstract int getActiveSubscriptionCount();
+
+ /**
+ * Get {@link SmsManager} instance
+ *
+ * @return the relevant SmsManager instance based on OS version and subId
+ */
+ public abstract SmsManager getSmsManager();
+
+ /**
+ * Get the default SMS subscription id
+ *
+ * @return the default sub ID
+ */
+ public abstract int getDefaultSmsSubscriptionId();
+
+ /**
+ * Returns if there's currently a system default SIM selected for sending SMS.
+ */
+ public abstract boolean getHasPreferredSmsSim();
+
+ /**
+ * For L_MR1, system may return a negative subId. Convert this into our own
+ * subId, so that we consistently use -1 for invalid or default.
+ *
+ * see b/18629526 and b/18670346
+ *
+ * @param intent The push intent from system
+ * @param extraName The name of the sub id extra
+ * @return the subId that is valid and meaningful for the app
+ */
+ public abstract int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName);
+
+ /**
+ * Get the subscription_id column value from a telephony provider cursor
+ *
+ * @param cursor The database query cursor
+ * @param subIdIndex The index of the subId column in the cursor
+ * @return the subscription_id column value from the cursor
+ */
+ public abstract int getSubIdFromTelephony(Cursor cursor, int subIdIndex);
+
+ /**
+ * Check if data roaming is enabled
+ *
+ * @return true if data roaming is enabled, false otherwise
+ */
+ public abstract boolean isDataRoamingEnabled();
+
+ /**
+ * Check if mobile data is enabled
+ *
+ * @return true if mobile data is enabled, false otherwise
+ */
+ public abstract boolean isMobileDataEnabled();
+
+ /**
+ * Get the set of self phone numbers, all normalized
+ *
+ * @return the set of normalized self phone numbers
+ */
+ public abstract HashSet<String> getNormalizedSelfNumbers();
+
+ /**
+ * This interface packages methods should only compile on L_MR1.
+ * This is needed to make unit tests happy when mockito tries to
+ * mock these methods. Calling on these methods on L_MR1 requires
+ * an extra invocation of toMr1().
+ */
+ public interface LMr1 {
+ /**
+ * Get this SIM's information. Only applies to L_MR1 above
+ *
+ * @return the subscription info of the SIM
+ */
+ public abstract SubscriptionInfo getActiveSubscriptionInfo();
+
+ /**
+ * Get the list of active SIMs in system. Only applies to L_MR1 above
+ *
+ * @return the list of subscription info for all inserted SIMs
+ */
+ public abstract List<SubscriptionInfo> getActiveSubscriptionInfoList();
+
+ /**
+ * Register subscription change listener. Only applies to L_MR1 above
+ *
+ * @param listener The listener to register
+ */
+ public abstract void registerOnSubscriptionsChangedListener(
+ SubscriptionManager.OnSubscriptionsChangedListener listener);
+ }
+
+ /**
+ * The PhoneUtils class for pre L_MR1
+ */
+ public static class PhoneUtilsPreLMR1 extends PhoneUtils {
+ private final ConnectivityManager mConnectivityManager;
+
+ public PhoneUtilsPreLMR1() {
+ super(ParticipantData.DEFAULT_SELF_SUB_ID);
+ mConnectivityManager =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ @Override
+ public String getSimCountry() {
+ final String country = mTelephonyManager.getSimCountryIso();
+ if (TextUtils.isEmpty(country)) {
+ return null;
+ }
+ return country.toUpperCase();
+ }
+
+ @Override
+ public int getSimSlotCount() {
+ // Don't support MSIM pre-L_MR1
+ return 1;
+ }
+
+ @Override
+ public String getCarrierName() {
+ return mTelephonyManager.getNetworkOperatorName();
+ }
+
+ @Override
+ public boolean hasSim() {
+ return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
+ }
+
+ @Override
+ public boolean isRoaming() {
+ return mTelephonyManager.isNetworkRoaming();
+ }
+
+ @Override
+ public int[] getMccMnc() {
+ final String mccmnc = mTelephonyManager.getSimOperator();
+ int mcc = 0;
+ int mnc = 0;
+ try {
+ mcc = Integer.parseInt(mccmnc.substring(0, 3));
+ mnc = Integer.parseInt(mccmnc.substring(3));
+ } catch (Exception e) {
+ LogUtil.w(TAG, "PhoneUtils.getMccMnc: invalid string " + mccmnc, e);
+ }
+ return new int[]{mcc, mnc};
+ }
+
+ @Override
+ public String getSimOperatorNumeric() {
+ return mTelephonyManager.getSimOperator();
+ }
+
+ @Override
+ public String getSelfRawNumber(final boolean allowOverride) {
+ if (allowOverride) {
+ final String userDefinedNumber = getNumberFromPrefs(mContext,
+ ParticipantData.DEFAULT_SELF_SUB_ID);
+ if (!TextUtils.isEmpty(userDefinedNumber)) {
+ return userDefinedNumber;
+ }
+ }
+ return mTelephonyManager.getLine1Number();
+ }
+
+ @Override
+ public int getEffectiveSubId(int subId) {
+ Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId);
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ @Override
+ public SmsManager getSmsManager() {
+ return SmsManager.getDefault();
+ }
+
+ @Override
+ public int getDefaultSmsSubscriptionId() {
+ Assert.fail("PhoneUtils.getDefaultSmsSubscriptionId(): not supported before L MR1");
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ @Override
+ public boolean getHasPreferredSmsSim() {
+ // SIM selection is not supported pre-L_MR1.
+ return true;
+ }
+
+ @Override
+ public int getActiveSubscriptionCount() {
+ return hasSim() ? 1 : 0;
+ }
+
+ @Override
+ public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
+ // Pre-L_MR1 always returns the default id
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ @Override
+ public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
+ // No subscription_id column before L_MR1
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public boolean isDataRoamingEnabled() {
+ boolean dataRoamingEnabled = false;
+ final ContentResolver cr = mContext.getContentResolver();
+ if (OsUtil.isAtLeastJB_MR1()) {
+ dataRoamingEnabled =
+ (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0);
+ } else {
+ dataRoamingEnabled =
+ (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0);
+ }
+ return dataRoamingEnabled;
+ }
+
+ @Override
+ public boolean isMobileDataEnabled() {
+ boolean mobileDataEnabled = false;
+ try {
+ final Class cmClass = mConnectivityManager.getClass();
+ final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled");
+ method.setAccessible(true); // Make the method callable
+ // get the setting for "mobile data"
+ mobileDataEnabled = (Boolean) method.invoke(mConnectivityManager);
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e);
+ }
+ return mobileDataEnabled;
+ }
+
+ @Override
+ public HashSet<String> getNormalizedSelfNumbers() {
+ final HashSet<String> numbers = new HashSet<>();
+ numbers.add(getCanonicalForSelf(true/*allowOverride*/));
+ return numbers;
+ }
+ }
+
+ /**
+ * The PhoneUtils class for L_MR1
+ */
+ public static class PhoneUtilsLMR1 extends PhoneUtils implements LMr1 {
+ private final SubscriptionManager mSubscriptionManager;
+
+ public PhoneUtilsLMR1(final int subId) {
+ super(subId);
+ mSubscriptionManager = SubscriptionManager.from(Factory.get().getApplicationContext());
+ }
+
+ @Override
+ public String getSimCountry() {
+ final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
+ if (subInfo != null) {
+ final String country = subInfo.getCountryIso();
+ if (TextUtils.isEmpty(country)) {
+ return null;
+ }
+ return country.toUpperCase();
+ }
+ return null;
+ }
+
+ @Override
+ public int getSimSlotCount() {
+ return mSubscriptionManager.getActiveSubscriptionInfoCountMax();
+ }
+
+ @Override
+ public String getCarrierName() {
+ final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
+ if (subInfo != null) {
+ final CharSequence displayName = subInfo.getDisplayName();
+ if (!TextUtils.isEmpty(displayName)) {
+ return displayName.toString();
+ }
+ final CharSequence carrierName = subInfo.getCarrierName();
+ if (carrierName != null) {
+ return carrierName.toString();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasSim() {
+ return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0;
+ }
+
+ @Override
+ public boolean isRoaming() {
+ return mSubscriptionManager.isNetworkRoaming(mSubId);
+ }
+
+ @Override
+ public int[] getMccMnc() {
+ int mcc = 0;
+ int mnc = 0;
+ final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
+ if (subInfo != null) {
+ mcc = subInfo.getMcc();
+ mnc = subInfo.getMnc();
+ }
+ return new int[]{mcc, mnc};
+ }
+
+ @Override
+ public String getSimOperatorNumeric() {
+ // For L_MR1 we return the canonicalized (xxxxxx) string
+ return getMccMncString(getMccMnc());
+ }
+
+ @Override
+ public String getSelfRawNumber(final boolean allowOverride) {
+ if (allowOverride) {
+ final String userDefinedNumber = getNumberFromPrefs(mContext, mSubId);
+ if (!TextUtils.isEmpty(userDefinedNumber)) {
+ return userDefinedNumber;
+ }
+ }
+
+ final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
+ if (subInfo != null) {
+ String phoneNumber = subInfo.getNumber();
+ if (TextUtils.isEmpty(phoneNumber) && LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "SubscriptionInfo phone number for self is empty!");
+ }
+ return phoneNumber;
+ }
+ LogUtil.w(TAG, "PhoneUtils.getSelfRawNumber: subInfo is null for " + mSubId);
+ throw new IllegalStateException("No active subscription");
+ }
+
+ @Override
+ public SubscriptionInfo getActiveSubscriptionInfo() {
+ try {
+ final SubscriptionInfo subInfo =
+ mSubscriptionManager.getActiveSubscriptionInfo(mSubId);
+ if (subInfo == null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ // This is possible if the sub id is no longer available.
+ LogUtil.d(TAG, "PhoneUtils.getActiveSubscriptionInfo(): empty sub info for "
+ + mSubId);
+ }
+ }
+ return subInfo;
+ } catch (Exception e) {
+ LogUtil.e(TAG, "PhoneUtils.getActiveSubscriptionInfo: system exception for "
+ + mSubId, e);
+ }
+ return null;
+ }
+
+ @Override
+ public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
+ final List<SubscriptionInfo> subscriptionInfos =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subscriptionInfos != null) {
+ return subscriptionInfos;
+ }
+ return EMPTY_SUBSCRIPTION_LIST;
+ }
+
+ @Override
+ public int getEffectiveSubId(int subId) {
+ if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) {
+ return getDefaultSmsSubscriptionId();
+ }
+ return subId;
+ }
+
+ @Override
+ public void registerOnSubscriptionsChangedListener(
+ SubscriptionManager.OnSubscriptionsChangedListener listener) {
+ mSubscriptionManager.addOnSubscriptionsChangedListener(listener);
+ }
+
+ @Override
+ public SmsManager getSmsManager() {
+ return SmsManager.getSmsManagerForSubscriptionId(mSubId);
+ }
+
+ @Override
+ public int getDefaultSmsSubscriptionId() {
+ final int systemDefaultSubId = SmsManager.getDefaultSmsSubscriptionId();
+ if (systemDefaultSubId < 0) {
+ // Always use -1 for any negative subId from system
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+ return systemDefaultSubId;
+ }
+
+ @Override
+ public boolean getHasPreferredSmsSim() {
+ return getDefaultSmsSubscriptionId() != ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+
+ @Override
+ public int getActiveSubscriptionCount() {
+ return mSubscriptionManager.getActiveSubscriptionInfoCount();
+ }
+
+ @Override
+ public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
+ return getEffectiveIncomingSubIdFromSystem(intent.getIntExtra(extraName,
+ ParticipantData.DEFAULT_SELF_SUB_ID));
+ }
+
+ private int getEffectiveIncomingSubIdFromSystem(int subId) {
+ if (subId < 0) {
+ if (mSubscriptionManager.getActiveSubscriptionInfoCount() > 1) {
+ // For multi-SIM device, we can not decide which SIM to use if system
+ // does not know either. So just make it the invalid sub id.
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+ // For single-SIM device, it must come from the only SIM we have
+ return getDefaultSmsSubscriptionId();
+ }
+ return subId;
+ }
+
+ @Override
+ public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
+ return getEffectiveIncomingSubIdFromSystem(cursor.getInt(subIdIndex));
+ }
+
+ @Override
+ public boolean isDataRoamingEnabled() {
+ final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
+ if (subInfo == null) {
+ // There is nothing we can do if system give us empty sub info
+ LogUtil.e(TAG, "PhoneUtils.isDataRoamingEnabled: system return empty sub info for "
+ + mSubId);
+ return false;
+ }
+ return subInfo.getDataRoaming() != SubscriptionManager.DATA_ROAMING_DISABLE;
+ }
+
+ @Override
+ public boolean isMobileDataEnabled() {
+ boolean mobileDataEnabled = false;
+ try {
+ final Class cmClass = mTelephonyManager.getClass();
+ final Method method = cmClass.getDeclaredMethod("getDataEnabled", Integer.TYPE);
+ method.setAccessible(true); // Make the method callable
+ // get the setting for "mobile data"
+ mobileDataEnabled = (Boolean) method.invoke(
+ mTelephonyManager, Integer.valueOf(mSubId));
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e);
+ }
+ return mobileDataEnabled;
+
+ }
+
+ @Override
+ public HashSet<String> getNormalizedSelfNumbers() {
+ final HashSet<String> numbers = new HashSet<>();
+ for (SubscriptionInfo info : getActiveSubscriptionInfoList()) {
+ numbers.add(PhoneUtils.get(info.getSubscriptionId()).getCanonicalForSelf(
+ true/*allowOverride*/));
+ }
+ return numbers;
+ }
+ }
+
+ /**
+ * A convenient get() method that uses the default SIM. Use this when SIM is
+ * not relevant, e.g. isDefaultSmsApp
+ *
+ * @return an instance of PhoneUtils for default SIM
+ */
+ public static PhoneUtils getDefault() {
+ return Factory.get().getPhoneUtils(ParticipantData.DEFAULT_SELF_SUB_ID);
+ }
+
+ /**
+ * Get an instance of PhoneUtils associated with a specific SIM, which is also platform
+ * specific.
+ *
+ * @param subId The SIM's subscription ID
+ * @return the instance
+ */
+ public static PhoneUtils get(int subId) {
+ return Factory.get().getPhoneUtils(subId);
+ }
+
+ public LMr1 toLMr1() {
+ if (OsUtil.isAtLeastL_MR1()) {
+ return (LMr1) this;
+ } else {
+ Assert.fail("PhoneUtils.toLMr1(): invalid OS version");
+ return null;
+ }
+ }
+
+ /**
+ * Check if this device supports SMS
+ *
+ * @return true if SMS is supported, false otherwise
+ */
+ public boolean isSmsCapable() {
+ return mTelephonyManager.isSmsCapable();
+ }
+
+ /**
+ * Check if this device supports voice calling
+ *
+ * @return true if voice calling is supported, false otherwise
+ */
+ public boolean isVoiceCapable() {
+ return mTelephonyManager.isVoiceCapable();
+ }
+
+ /**
+ * Get the ISO country code from system locale setting
+ *
+ * @return the ISO country code from system locale
+ */
+ private static String getLocaleCountry() {
+ final String country = Locale.getDefault().getCountry();
+ if (TextUtils.isEmpty(country)) {
+ return null;
+ }
+ return country.toUpperCase();
+ }
+
+ /**
+ * Get ISO country code from the SIM, if not available, fall back to locale
+ *
+ * @return SIM or locale ISO country code
+ */
+ public String getSimOrDefaultLocaleCountry() {
+ String country = getSimCountry();
+ if (country == null) {
+ country = getLocaleCountry();
+ }
+ return country;
+ }
+
+ // Get or set the cache of canonicalized phone numbers for a specific country
+ private static ArrayMap<String, String> getOrAddCountryMapInCacheLocked(String country) {
+ if (country == null) {
+ country = "";
+ }
+ ArrayMap<String, String> countryMap = sCanonicalPhoneNumberCache.get(country);
+ if (countryMap == null) {
+ countryMap = new ArrayMap<>();
+ sCanonicalPhoneNumberCache.put(country, countryMap);
+ }
+ return countryMap;
+ }
+
+ // Get canonicalized phone number from cache
+ private static String getCanonicalFromCache(final String phoneText, String country) {
+ synchronized (sCanonicalPhoneNumberCache) {
+ final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country);
+ return countryMap.get(phoneText);
+ }
+ }
+
+ // Put canonicalized phone number into cache
+ private static void putCanonicalToCache(final String phoneText, String country,
+ final String canonical) {
+ synchronized (sCanonicalPhoneNumberCache) {
+ final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country);
+ countryMap.put(phoneText, canonical);
+ }
+ }
+
+ /**
+ * Utility method to parse user input number into standard E164 number.
+ *
+ * @param phoneText Phone number text as input by user.
+ * @param country ISO country code based on which to parse the number.
+ * @return E164 phone number. Returns null in case parsing failed.
+ */
+ private static String getValidE164Number(final String phoneText, final String country) {
+ final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+ try {
+ final PhoneNumber phoneNumber = phoneNumberUtil.parse(phoneText, country);
+ if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
+ return phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
+ }
+ } catch (final NumberParseException e) {
+ LogUtil.e(TAG, "PhoneUtils.getValidE164Number(): Not able to parse phone number "
+ + LogUtil.sanitizePII(phoneText) + " for country " + country);
+ }
+ return null;
+ }
+
+ /**
+ * Canonicalize phone number using system locale country
+ *
+ * @param phoneText The phone number to canonicalize
+ * @return the canonicalized number
+ */
+ public String getCanonicalBySystemLocale(final String phoneText) {
+ return getCanonicalByCountry(phoneText, getLocaleCountry());
+ }
+
+ /**
+ * Canonicalize phone number using SIM's country, may fall back to system locale country
+ * if SIM country can not be obtained
+ *
+ * @param phoneText The phone number to canonicalize
+ * @return the canonicalized number
+ */
+ public String getCanonicalBySimLocale(final String phoneText) {
+ return getCanonicalByCountry(phoneText, getSimOrDefaultLocaleCountry());
+ }
+
+ /**
+ * Canonicalize phone number using a country code.
+ * This uses an internal cache per country to speed up.
+ *
+ * @param phoneText The phone number to canonicalize
+ * @param country The ISO country code to use
+ * @return the canonicalized number, or the original number if can't be parsed
+ */
+ private String getCanonicalByCountry(final String phoneText, final String country) {
+ Assert.notNull(phoneText);
+
+ String canonicalNumber = getCanonicalFromCache(phoneText, country);
+ if (canonicalNumber != null) {
+ return canonicalNumber;
+ }
+ canonicalNumber = getValidE164Number(phoneText, country);
+ if (canonicalNumber == null) {
+ // If we can't normalize this number, we just use the display string number.
+ // This is possible for short codes and other non-localizable numbers.
+ canonicalNumber = phoneText;
+ }
+ putCanonicalToCache(phoneText, country, canonicalNumber);
+ return canonicalNumber;
+ }
+
+ /**
+ * Canonicalize the self (per SIM) phone number
+ *
+ * @param allowOverride whether to use the override number in app settings
+ * @return the canonicalized self phone number
+ */
+ public String getCanonicalForSelf(final boolean allowOverride) {
+ String selfNumber = null;
+ try {
+ selfNumber = getSelfRawNumber(allowOverride);
+ } catch (IllegalStateException e) {
+ // continue;
+ }
+ if (selfNumber == null) {
+ return "";
+ }
+ return getCanonicalBySimLocale(selfNumber);
+ }
+
+ /**
+ * Get the SIM's phone number in NATIONAL format with only digits, used in sending
+ * as LINE1NOCOUNTRYCODE macro in mms_config
+ *
+ * @return all digits national format number of the SIM
+ */
+ public String getSimNumberNoCountryCode() {
+ String selfNumber = null;
+ try {
+ selfNumber = getSelfRawNumber(false/*allowOverride*/);
+ } catch (IllegalStateException e) {
+ // continue
+ }
+ if (selfNumber == null) {
+ selfNumber = "";
+ }
+ final String country = getSimCountry();
+ final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+ try {
+ final PhoneNumber phoneNumber = phoneNumberUtil.parse(selfNumber, country);
+ if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
+ return phoneNumberUtil
+ .format(phoneNumber, PhoneNumberFormat.NATIONAL)
+ .replaceAll("\\D", "");
+ }
+ } catch (final NumberParseException e) {
+ LogUtil.e(TAG, "PhoneUtils.getSimNumberNoCountryCode(): Not able to parse phone number "
+ + LogUtil.sanitizePII(selfNumber) + " for country " + country);
+ }
+ return selfNumber;
+
+ }
+
+ /**
+ * Format a phone number for displaying, using system locale country.
+ * If the country code matches between the system locale and the input phone number,
+ * it will be formatted into NATIONAL format, otherwise, the INTERNATIONAL format
+ *
+ * @param phoneText The original phone text
+ * @return formatted number
+ */
+ public String formatForDisplay(final String phoneText) {
+ // Only format a valid number which length >=6
+ if (TextUtils.isEmpty(phoneText) ||
+ phoneText.replaceAll("\\D", "").length() < MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT) {
+ return phoneText;
+ }
+ final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+ final String systemCountry = getLocaleCountry();
+ final int systemCountryCode = phoneNumberUtil.getCountryCodeForRegion(systemCountry);
+ try {
+ final PhoneNumber parsedNumber = phoneNumberUtil.parse(phoneText, systemCountry);
+ final PhoneNumberFormat phoneNumberFormat =
+ (systemCountryCode > 0 && parsedNumber.getCountryCode() == systemCountryCode) ?
+ PhoneNumberFormat.NATIONAL : PhoneNumberFormat.INTERNATIONAL;
+ return phoneNumberUtil.format(parsedNumber, phoneNumberFormat);
+ } catch (NumberParseException e) {
+ LogUtil.e(TAG, "PhoneUtils.formatForDisplay: invalid phone number "
+ + LogUtil.sanitizePII(phoneText) + " with country " + systemCountry);
+ return phoneText;
+ }
+ }
+
+ /**
+ * Is Messaging the default SMS app?
+ * - On KLP+ this checks the system setting.
+ * - On JB (and below) this always returns true, since the setting was added in KLP.
+ */
+ public boolean isDefaultSmsApp() {
+ if (OsUtil.isAtLeastKLP()) {
+ final String configuredApplication = Telephony.Sms.getDefaultSmsPackage(mContext);
+ return mContext.getPackageName().equals(configuredApplication);
+ }
+ return true;
+ }
+
+ /**
+ * Get default SMS app package name
+ *
+ * @return the package name of default SMS app
+ */
+ public String getDefaultSmsApp() {
+ if (OsUtil.isAtLeastKLP()) {
+ return Telephony.Sms.getDefaultSmsPackage(mContext);
+ }
+ return null;
+ }
+
+ /**
+ * Determines if SMS is currently enabled on this device.
+ * - Device must support SMS
+ * - On KLP+ we must be set as the default SMS app
+ */
+ public boolean isSmsEnabled() {
+ return isSmsCapable() && isDefaultSmsApp();
+ }
+
+ /**
+ * Returns the name of the default SMS app, or the empty string if there is
+ * an error or there is no default app (e.g. JB and below).
+ */
+ public String getDefaultSmsAppLabel() {
+ if (OsUtil.isAtLeastKLP()) {
+ final String packageName = Telephony.Sms.getDefaultSmsPackage(mContext);
+ final PackageManager pm = mContext.getPackageManager();
+ try {
+ final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
+ return pm.getApplicationLabel(appInfo).toString();
+ } catch (NameNotFoundException e) {
+ // Fall through and return empty string
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Gets the state of Airplane Mode.
+ *
+ * @return true if enabled.
+ */
+ @SuppressWarnings("deprecation")
+ public boolean isAirplaneModeOn() {
+ if (OsUtil.isAtLeastJB_MR1()) {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ } else {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.AIRPLANE_MODE_ON, 0) != 0;
+ }
+ }
+
+ public static String getMccMncString(int[] mccmnc) {
+ if (mccmnc == null || mccmnc.length != 2) {
+ return "000000";
+ }
+ return String.format("%03d%03d", mccmnc[0], mccmnc[1]);
+ }
+
+ public static String canonicalizeMccMnc(final String mcc, final String mnc) {
+ try {
+ return String.format("%03d%03d", Integer.parseInt(mcc), Integer.parseInt(mnc));
+ } catch (final NumberFormatException e) {
+ // Return invalid as is
+ LogUtil.w(TAG, "canonicalizeMccMnc: invalid mccmnc:" + mcc + " ," + mnc);
+ }
+ return mcc + mnc;
+ }
+
+ /**
+ * Returns whether the given destination is valid for sending SMS/MMS message.
+ */
+ public static boolean isValidSmsMmsDestination(final String destination) {
+ return PhoneNumberUtils.isWellFormedSmsAddress(destination) ||
+ MmsSmsUtils.isEmailAddress(destination);
+ }
+
+ public interface SubscriptionRunnable {
+ void runForSubscription(int subId);
+ }
+
+ /**
+ * A convenience method for iterating through all active subscriptions
+ *
+ * @param runnable a {@link SubscriptionRunnable} for performing work on each subscription.
+ */
+ public static void forEachActiveSubscription(final SubscriptionRunnable runnable) {
+ if (OsUtil.isAtLeastL_MR1()) {
+ final List<SubscriptionInfo> subscriptionList =
+ getDefault().toLMr1().getActiveSubscriptionInfoList();
+ for (final SubscriptionInfo subscriptionInfo : subscriptionList) {
+ runnable.runForSubscription(subscriptionInfo.getSubscriptionId());
+ }
+ } else {
+ runnable.runForSubscription(ParticipantData.DEFAULT_SELF_SUB_ID);
+ }
+ }
+
+ private static String getNumberFromPrefs(final Context context, final int subId) {
+ final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
+ final String mmsPhoneNumberPrefKey =
+ context.getString(R.string.mms_phone_number_pref_key);
+ final String userDefinedNumber = prefs.getString(mmsPhoneNumberPrefKey, null);
+ if (!TextUtils.isEmpty(userDefinedNumber)) {
+ return userDefinedNumber;
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/messaging/util/RingtoneUtil.java b/src/com/android/messaging/util/RingtoneUtil.java
new file mode 100644
index 0000000..a7facfb
--- /dev/null
+++ b/src/com/android/messaging/util/RingtoneUtil.java
@@ -0,0 +1,53 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+
+public class RingtoneUtil {
+ /**
+ * Return a ringtone Uri for the string representation passed in. Use the app
+ * and system defaults as fallbacks
+ * @param ringtoneString is the ringtone to resolve
+ * @return the Uri of the ringtone or the fallback ringtone
+ */
+ public static Uri getNotificationRingtoneUri(String ringtoneString) {
+ if (ringtoneString == null) {
+ // No override specified, fall back to system-wide setting.
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final String prefKey = context.getString(R.string.notification_sound_pref_key);
+ ringtoneString = prefs.getString(prefKey, null);
+ }
+
+ if (!TextUtils.isEmpty(ringtoneString)) {
+ // We have set a value, even if it is the default Uri at some point
+ return Uri.parse(ringtoneString);
+ } else if (ringtoneString == null) {
+ // We have no setting specified (== null), so we default to the system default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ } else {
+ // An empty string (== "") here is the result of selecting "None" as the ringtone
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/SafeAsyncTask.java b/src/com/android/messaging/util/SafeAsyncTask.java
new file mode 100644
index 0000000..1cce6e9
--- /dev/null
+++ b/src/com/android/messaging/util/SafeAsyncTask.java
@@ -0,0 +1,176 @@
+/*
+ * 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.util;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Debug;
+import android.os.SystemClock;
+
+import com.android.messaging.Factory;
+import com.android.messaging.util.Assert.RunsOnAnyThread;
+
+/**
+ * Wrapper class which provides explicit API for:
+ * <ol>
+ * <li>Threading policy choice - Users of this class should use the explicit API instead of
+ * {@link #execute} which uses different threading policy on different OS versions.
+ * <li>Enforce creation on main thread as required by AsyncTask
+ * <li>Enforce that the background task does not take longer than expected.
+ * </ol>
+ */
+public abstract class SafeAsyncTask<Params, Progress, Result>
+ extends AsyncTask<Params, Progress, Result> {
+ private static final long DEFAULT_MAX_EXECUTION_TIME_MILLIS = 10 * 1000; // 10 seconds
+
+ /** This is strongly discouraged as it can block other AsyncTasks indefinitely. */
+ public static final long UNBOUNDED_TIME = Long.MAX_VALUE;
+
+ private static final String WAKELOCK_ID = "bugle_safe_async_task_wakelock";
+ protected static final int WAKELOCK_OP = 1000;
+ private static WakeLockHelper sWakeLock = new WakeLockHelper(WAKELOCK_ID);
+
+ private final long mMaxExecutionTimeMillis;
+ private final boolean mCancelExecutionOnTimeout;
+ private boolean mThreadPoolRequested;
+
+ public SafeAsyncTask() {
+ this(DEFAULT_MAX_EXECUTION_TIME_MILLIS, false);
+ }
+
+ public SafeAsyncTask(final long maxTimeMillis) {
+ this(maxTimeMillis, false);
+ }
+
+ /**
+ * @param maxTimeMillis maximum expected time for the background operation. This is just
+ * a diagnostic tool to catch unexpectedly long operations. If an operation does take
+ * longer than expected, it is fine to increase this argument. If the value is larger
+ * than a minute, you should consider using a dedicated thread so as not to interfere
+ * with other AsyncTasks.
+ *
+ * <p>Use {@link #UNBOUNDED_TIME} if you do not know the maximum expected time. This
+ * is strongly discouraged as it can block other AsyncTasks indefinitely.
+ *
+ * @param cancelExecutionOnTimeout whether to attempt to cancel the task execution on timeout.
+ * If this is set, at execution timeout we will call cancel(), so doInBackgroundTimed()
+ * should periodically check if the task is to be cancelled and finish promptly if
+ * possible, and handle the cancel event in onCancelled(). Also, at the end of execution
+ * we will not crash the execution if it went over limit since we explicitly canceled it.
+ */
+ public SafeAsyncTask(final long maxTimeMillis, final boolean cancelExecutionOnTimeout) {
+ Assert.isMainThread(); // AsyncTask has to be created on the main thread
+ mMaxExecutionTimeMillis = maxTimeMillis;
+ mCancelExecutionOnTimeout = cancelExecutionOnTimeout;
+ }
+
+ public final SafeAsyncTask<Params, Progress, Result> executeOnThreadPool(
+ final Params... params) {
+ Assert.isMainThread(); // AsyncTask requires this
+ mThreadPoolRequested = true;
+ executeOnExecutor(THREAD_POOL_EXECUTOR, params);
+ return this;
+ }
+
+ protected abstract Result doInBackgroundTimed(final Params... params);
+
+ @Override
+ protected final Result doInBackground(final Params... params) {
+ // This enforces that executeOnThreadPool was called, not execute. Ideally, we would
+ // make execute throw an exception, but since it is final, we cannot override it.
+ Assert.isTrue(mThreadPoolRequested);
+
+ if (mCancelExecutionOnTimeout) {
+ ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (getStatus() == Status.RUNNING) {
+ // Cancel the task if it's still running.
+ LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s timed out and is canceled",
+ this));
+ cancel(true /* mayInterruptIfRunning */);
+ }
+ }
+ }, mMaxExecutionTimeMillis);
+ }
+
+ final long startTime = SystemClock.elapsedRealtime();
+ try {
+ return doInBackgroundTimed(params);
+ } finally {
+ final long executionTime = SystemClock.elapsedRealtime() - startTime;
+ if (executionTime > mMaxExecutionTimeMillis) {
+ LogUtil.w(LogUtil.BUGLE_TAG, String.format("%s took %dms", this, executionTime));
+ // Don't crash if debugger is attached or if we are asked to cancel on timeout.
+ if (!Debug.isDebuggerConnected() && !mCancelExecutionOnTimeout) {
+ Assert.fail(this + " took too long");
+ }
+ }
+ }
+
+ }
+
+ @Override
+ protected void onPostExecute(final Result result) {
+ // No need to use AsyncTask at all if there is no onPostExecute
+ Assert.fail("Use SafeAsyncTask.executeOnThreadPool");
+ }
+
+ /**
+ * This provides a way for people to run async tasks but without onPostExecute.
+ * This can be called on any thread.
+ *
+ * Run code in a thread using AsyncTask's thread pool.
+ *
+ * To enable wakelock during the execution, see {@link #executeOnThreadPool(Runnable, boolean)}
+ *
+ * @param runnable The Runnable to execute asynchronously
+ */
+ @RunsOnAnyThread
+ public static void executeOnThreadPool(final Runnable runnable) {
+ executeOnThreadPool(runnable, false);
+ }
+
+ /**
+ * This provides a way for people to run async tasks but without onPostExecute.
+ * This can be called on any thread.
+ *
+ * Run code in a thread using AsyncTask's thread pool.
+ *
+ * @param runnable The Runnable to execute asynchronously
+ * @param withWakeLock when set, a wake lock will be held for the duration of the runnable
+ * execution
+ */
+ public static void executeOnThreadPool(final Runnable runnable, final boolean withWakeLock) {
+ if (withWakeLock) {
+ final Intent intent = new Intent();
+ sWakeLock.acquire(Factory.get().getApplicationContext(), intent, WAKELOCK_OP);
+ THREAD_POOL_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ runnable.run();
+ } finally {
+ sWakeLock.release(intent, WAKELOCK_OP);
+ }
+ }
+ });
+ } else {
+ THREAD_POOL_EXECUTOR.execute(runnable);
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/SwitchCompatUtils.java b/src/com/android/messaging/util/SwitchCompatUtils.java
new file mode 100644
index 0000000..b5d1ed5
--- /dev/null
+++ b/src/com/android/messaging/util/SwitchCompatUtils.java
@@ -0,0 +1,129 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.v7.graphics.drawable.DrawableWrapper;
+import android.support.v7.widget.SwitchCompat;
+import android.util.TypedValue;
+
+/* Most methods in this file are copied from
+ * v7/appcompat/src/android/support/v7/internal/widget/TintManager.java. It would be better if
+ * we could have just extended the TintManager but this is a final class that we do not have
+ * access to. */
+
+/**
+ * Util methods for the SwitchCompat widget
+ */
+public class SwitchCompatUtils {
+ /**
+ * Given a color and a SwitchCompat view, updates the SwitchCompat to appear with the appropiate
+ * color when enabled and checked
+ */
+ public static void updateSwitchCompatColor(SwitchCompat switchCompat, final int color) {
+ final Context context = switchCompat.getContext();
+ final TypedValue typedValue = new TypedValue();
+
+ switchCompat.setThumbDrawable(getColorTintedDrawable(switchCompat.getThumbDrawable(),
+ getSwitchThumbColorStateList(context, color, typedValue),
+ PorterDuff.Mode.MULTIPLY));
+
+ switchCompat.setTrackDrawable(getColorTintedDrawable(switchCompat.getTrackDrawable(),
+ getSwitchTrackColorStateList(context, color, typedValue), PorterDuff.Mode.SRC_IN));
+ }
+
+ private static Drawable getColorTintedDrawable(Drawable oldDrawable,
+ final ColorStateList colorStateList, final PorterDuff.Mode mode) {
+ final int[] thumbState = oldDrawable.isStateful() ? oldDrawable.getState() : null;
+ if (oldDrawable instanceof DrawableWrapper) {
+ oldDrawable = ((DrawableWrapper) oldDrawable).getWrappedDrawable();
+ }
+ final Drawable newDrawable = new TintDrawableWrapper(oldDrawable, colorStateList, mode);
+ if (thumbState != null) {
+ newDrawable.setState(thumbState);
+ }
+ return newDrawable;
+ }
+
+ private static ColorStateList getSwitchThumbColorStateList(final Context context,
+ final int color, final TypedValue typedValue) {
+ final int[][] states = new int[3][];
+ final int[] colors = new int[3];
+ int i = 0;
+ // Disabled state
+ states[i] = new int[] { -android.R.attr.state_enabled };
+ colors[i] = getColor(Color.parseColor("#ffbdbdbd"), 1f);
+ i++;
+ states[i] = new int[] { android.R.attr.state_checked };
+ colors[i] = color;
+ i++;
+ // Default enabled state
+ states[i] = new int[0];
+ colors[i] = getThemeAttrColor(context, typedValue,
+ android.support.v7.appcompat.R.attr.colorSwitchThumbNormal);
+ i++;
+ return new ColorStateList(states, colors);
+ }
+
+ private static ColorStateList getSwitchTrackColorStateList(final Context context,
+ final int color, final TypedValue typedValue) {
+ final int[][] states = new int[3][];
+ final int[] colors = new int[3];
+ int i = 0;
+ // Disabled state
+ states[i] = new int[] { -android.R.attr.state_enabled };
+ colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.1f);
+ i++;
+ states[i] = new int[] { android.R.attr.state_checked };
+ colors[i] = getColor(color, 0.3f);
+ i++;
+ // Default enabled state
+ states[i] = new int[0];
+ colors[i] = getThemeAttrColor(context, typedValue, android.R.attr.colorForeground, 0.3f);
+ i++;
+ return new ColorStateList(states, colors);
+ }
+
+ private static int getThemeAttrColor(final Context context, final TypedValue typedValue,
+ final int attr) {
+ if (context.getTheme().resolveAttribute(attr, typedValue, true)) {
+ if (typedValue.type >= TypedValue.TYPE_FIRST_INT
+ && typedValue.type <= TypedValue.TYPE_LAST_INT) {
+ return typedValue.data;
+ } else if (typedValue.type == TypedValue.TYPE_STRING) {
+ return context.getResources().getColor(typedValue.resourceId);
+ }
+ }
+ return 0;
+ }
+
+ private static int getThemeAttrColor(final Context context, final TypedValue typedValue,
+ final int attr, final float alpha) {
+ final int color = getThemeAttrColor(context, typedValue, attr);
+ return getColor(color, alpha);
+ }
+
+ private static int getColor(int color, float alpha) {
+ final int originalAlpha = Color.alpha(color);
+ // Return the color, multiplying the original alpha by the disabled value
+ return (color & 0x00ffffff) | (Math.round(originalAlpha * alpha) << 24);
+ }
+}
diff --git a/src/com/android/messaging/util/TextUtil.java b/src/com/android/messaging/util/TextUtil.java
new file mode 100644
index 0000000..b240396
--- /dev/null
+++ b/src/com/android/messaging/util/TextUtil.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util;
+
+import android.support.annotation.Nullable;
+
+public class TextUtil {
+ /**
+ * Returns true if the string is empty, null or only whitespace.
+ */
+ public static boolean isAllWhitespace(@Nullable String string) {
+ if (string == null || string.isEmpty()) {
+ return true;
+ }
+
+ for (int i = 0; i < string.length(); ++i) {
+ if (!Character.isWhitespace(string.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Taken from PhoneNumberUtils, where it is only available in API 21+ Replaces all unicode
+ * (e.g. Arabic, Persian) digits with their decimal digit equivalents.
+ *
+ * @param number the number to perform the replacement on.
+ * @return the replaced number.
+ */
+ public static String replaceUnicodeDigits(String number) {
+ StringBuilder normalizedDigits = new StringBuilder(number.length());
+ for (char c : number.toCharArray()) {
+ int digit = Character.digit(c, 10);
+ if (digit != -1) {
+ normalizedDigits.append(digit);
+ } else {
+ normalizedDigits.append(c);
+ }
+ }
+ return normalizedDigits.toString();
+ }
+
+ /**
+ * Appends text to the stringBuilder.
+ * If stringBuilder already has content, separator is prepended to create a separator between
+ * entries.
+ * @param stringBuilder The stringBuilder to add to
+ * @param text The text to append
+ * @param separator The separator to add if there is already text, typically "," or "\n"
+ */
+ public static void appendWithSeparator(final StringBuilder stringBuilder, final String text,
+ final String separator) {
+ if (stringBuilder.length() > 0) {
+ stringBuilder.append(separator);
+ }
+ stringBuilder.append(text);
+ }
+}
diff --git a/src/com/android/messaging/util/ThreadUtil.java b/src/com/android/messaging/util/ThreadUtil.java
new file mode 100644
index 0000000..3b935d8
--- /dev/null
+++ b/src/com/android/messaging/util/ThreadUtil.java
@@ -0,0 +1,28 @@
+/*
+ * 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.util;
+
+import android.os.Handler;
+import android.os.Looper;
+
+public class ThreadUtil {
+ private static final Handler sHandler = new Handler(Looper.getMainLooper());
+
+ public static Handler getMainThreadHandler() {
+ return sHandler;
+ }
+}
diff --git a/src/com/android/messaging/util/TintDrawableWrapper.java b/src/com/android/messaging/util/TintDrawableWrapper.java
new file mode 100644
index 0000000..e6ea4bd
--- /dev/null
+++ b/src/com/android/messaging/util/TintDrawableWrapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.util;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.v7.graphics.drawable.DrawableWrapper;
+
+/*
+ * This is directly copied from v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
+ */
+
+/**
+ * A {@link DrawableWrapper} which updates it's color filter using a {@link ColorStateList}.
+ */
+class TintDrawableWrapper extends DrawableWrapper {
+ private final ColorStateList mTintStateList;
+ private final PorterDuff.Mode mTintMode;
+ private int mCurrentColor;
+ public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList) {
+ this(drawable, tintStateList, PorterDuff.Mode.SRC_IN);
+ }
+ public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList,
+ PorterDuff.Mode tintMode) {
+ super(drawable);
+ mTintStateList = tintStateList;
+ mTintMode = tintMode;
+ }
+ @Override
+ public boolean isStateful() {
+ return (mTintStateList != null && mTintStateList.isStateful()) || super.isStateful();
+ }
+ @Override
+ public boolean setState(int[] stateSet) {
+ boolean handled = super.setState(stateSet);
+ handled = updateTint(stateSet) || handled;
+ return handled;
+ }
+ private boolean updateTint(int[] state) {
+ if (mTintStateList != null) {
+ final int color = mTintStateList.getColorForState(state, mCurrentColor);
+ if (color != mCurrentColor) {
+ if (color != Color.TRANSPARENT) {
+ setColorFilter(color, mTintMode);
+ } else {
+ clearColorFilter();
+ }
+ mCurrentColor = color;
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/messaging/util/Trace.java b/src/com/android/messaging/util/Trace.java
new file mode 100644
index 0000000..da1e87c
--- /dev/null
+++ b/src/com/android/messaging/util/Trace.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util;
+
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+/**
+ * Helper class for systrace (see http://developer.android.com/tools/help/systrace.html).<p>
+ * To enable, set log.tag.Bugle_Trace (defined by {@link #TAG} to VERBOSE before
+ * the process starts.<p>
+ * Note that this will run only on JBMR2 or later; on earlier platforms or if the log
+ * tag isn't set, calls to {@link #beginSection(String)} or {@link #endSection()} are no-ops. <p>
+ * Internally, calls dispatch to either a class that actually does work or a class that doesn't.
+ * This avoids Dalvik complaining when it loads the class on earlier platforms that the
+ * opcodes aren't available, and, according to the Dalvik team, using vtable dispatching for
+ * something like this should be faster than if (OsUtil.isAtLeast...()) on each call.
+ */
+public final class Trace {
+ private static final String TAG = "Bugle_Trace";
+ private abstract static class AbstractTrace {
+ abstract void beginSection(String sectionName);
+ abstract void endSection();
+ }
+
+ private static final AbstractTrace sTrace;
+
+ // Static initializer to pick the correct trace class to handle tracing.
+ static {
+ // Use android.util.Log instead of LogUtil here to avoid pulling in Gservices
+ // too early in app startup.
+ if (OsUtil.isAtLeastJB_MR2() &&
+ android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) {
+ sTrace = new TraceJBMR2();
+ } else {
+ sTrace = new TraceShim();
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has begun. This call must
+ * be followed by a corresponding call to {@link #endSection()} on the same thread.
+ *
+ * <p class="note"> At this time the vertical bar character '|', newline character '\n', and
+ * null character '\0' are used internally by the tracing mechanism. If sectionName contains
+ * these characters they will be replaced with a space character in the trace.
+ *
+ * @param sectionName The name of the code section to appear in the trace. This may be at
+ * most 127 Unicode code units long.
+ */
+ public static void beginSection(String sectionName) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "beginSection() " + sectionName);
+ }
+ sTrace.beginSection(sectionName);
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has ended. This call must
+ * be preceeded by a corresponding call to {@link #beginSection(String)}. Calling this method
+ * will mark the end of the most recently begun section of code, so care must be taken to
+ * ensure that beginSection / endSection pairs are properly nested and called from the same
+ * thread.
+ */
+ public static void endSection() {
+ sTrace.endSection();
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "endSection()");
+ }
+ }
+
+ /**
+ * Internal class that we use if we really did enable tracing.
+ */
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+ private static final class TraceJBMR2 extends AbstractTrace {
+ @Override
+ void beginSection(String sectionName) {
+ android.os.Trace.beginSection(sectionName);
+ }
+
+ @Override
+ void endSection() {
+ android.os.Trace.endSection();
+ }
+ }
+
+ /**
+ * Dummy class that we use if we aren't really tracing.
+ */
+ private static final class TraceShim extends AbstractTrace {
+ @Override
+ void beginSection(String sectionName) {
+ }
+
+ @Override
+ void endSection() {
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/Typefaces.java b/src/com/android/messaging/util/Typefaces.java
new file mode 100644
index 0000000..eb8562c
--- /dev/null
+++ b/src/com/android/messaging/util/Typefaces.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.util;
+
+import android.graphics.Typeface;
+
+/**
+ * Provides access to typefaces used by code. Specially important for typefaces coming from assets,
+ * which appear (from platform code inspection) to not be cached.
+ * Note: Considered making this a singleton provided by factory/appcontext, but seemed too simple,
+ * not worth stubbing.
+ */
+public class Typefaces {
+ private static Typeface sRobotoBold;
+ private static Typeface sRobotoNormal;
+
+ public static Typeface getRobotoBold() {
+ Assert.isMainThread();
+ if (sRobotoBold == null) {
+ sRobotoBold = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
+ }
+ return sRobotoBold;
+ }
+
+ public static Typeface getRobotoNormal() {
+ Assert.isMainThread();
+ if (sRobotoNormal == null) {
+ sRobotoNormal = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
+ }
+ return sRobotoNormal;
+ }
+}
diff --git a/src/com/android/messaging/util/UiUtils.java b/src/com/android/messaging/util/UiUtils.java
new file mode 100644
index 0000000..84fe353
--- /dev/null
+++ b/src/com/android/messaging/util/UiUtils.java
@@ -0,0 +1,438 @@
+/*
+ * 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.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.URLSpan;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.ui.SnackBar;
+import com.android.messaging.ui.SnackBar.Placement;
+import com.android.messaging.ui.conversationlist.ConversationListActivity;
+import com.android.messaging.ui.SnackBarInteraction;
+import com.android.messaging.ui.SnackBarManager;
+import com.android.messaging.ui.UIIntents;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+public class UiUtils {
+ /** MediaPicker transition duration in ms */
+ public static final int MEDIAPICKER_TRANSITION_DURATION =
+ getApplicationContext().getResources().getInteger(
+ R.integer.mediapicker_transition_duration);
+ /** Short transition duration in ms */
+ public static final int ASYNCIMAGE_TRANSITION_DURATION =
+ getApplicationContext().getResources().getInteger(
+ R.integer.asyncimage_transition_duration);
+ /** Compose transition duration in ms */
+ public static final int COMPOSE_TRANSITION_DURATION =
+ getApplicationContext().getResources().getInteger(
+ R.integer.compose_transition_duration);
+ /** Generic duration for revealing/hiding a view */
+ public static final int REVEAL_ANIMATION_DURATION =
+ getApplicationContext().getResources().getInteger(
+ R.integer.reveal_view_animation_duration);
+
+ public static final Interpolator DEFAULT_INTERPOLATOR = new CubicBezierInterpolator(
+ 0.4f, 0.0f, 0.2f, 1.0f);
+
+ public static final Interpolator EASE_IN_INTERPOLATOR = new CubicBezierInterpolator(
+ 0.4f, 0.0f, 0.8f, 0.5f);
+
+ public static final Interpolator EASE_OUT_INTERPOLATOR = new CubicBezierInterpolator(
+ 0.0f, 0.0f, 0.2f, 1f);
+
+ /** Show a simple toast at the bottom */
+ public static void showToastAtBottom(final int messageId) {
+ UiUtils.showToastAtBottom(getApplicationContext().getString(messageId));
+ }
+
+ /** Show a simple toast at the bottom */
+ public static void showToastAtBottom(final String message) {
+ final Toast toast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
+ toast.show();
+ }
+
+ /** Show a simple toast at the default position */
+ public static void showToast(final int messageId) {
+ final Toast toast = Toast.makeText(getApplicationContext(),
+ getApplicationContext().getString(messageId), Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
+ toast.show();
+ }
+
+ /** Show a simple toast at the default position */
+ public static void showToast(final int pluralsMessageId, final int count) {
+ final Toast toast = Toast.makeText(getApplicationContext(),
+ getApplicationContext().getResources().getQuantityString(pluralsMessageId, count),
+ Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
+ toast.show();
+ }
+
+ public static void showSnackBar(final Context context, @NonNull final View parentView,
+ final String message, @Nullable final Runnable runnable, final int runnableLabel,
+ @Nullable final List<SnackBarInteraction> interactions) {
+ Assert.notNull(context);
+ SnackBar.Action action = null;
+ switch (runnableLabel) {
+ case SnackBar.Action.SNACK_BAR_UNDO:
+ action = SnackBar.Action.createUndoAction(runnable);
+ break;
+ case SnackBar.Action.SNACK_BAR_RETRY:
+ action = SnackBar.Action.createRetryAction(runnable);
+ break;
+ default :
+ break;
+ }
+
+ showSnackBarWithCustomAction(context, parentView, message, action, interactions,
+ null /* placement */);
+ }
+
+ public static void showSnackBarWithCustomAction(final Context context,
+ @NonNull final View parentView,
+ @NonNull final String message,
+ @NonNull final SnackBar.Action action,
+ @Nullable final List<SnackBarInteraction> interactions,
+ @Nullable final Placement placement) {
+ Assert.notNull(context);
+ Assert.isTrue(!TextUtils.isEmpty(message));
+ Assert.notNull(action);
+ SnackBarManager.get()
+ .newBuilder(parentView)
+ .setText(message)
+ .setAction(action)
+ .withInteractions(interactions)
+ .withPlacement(placement)
+ .show();
+ }
+
+ /**
+ * Run the given runnable once after the next layout pass of the view.
+ */
+ public static void doOnceAfterLayoutChange(final View view, final Runnable runnable) {
+ final OnLayoutChangeListener listener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(final View v, final int left, final int top, final int right,
+ final int bottom, final int oldLeft, final int oldTop, final int oldRight,
+ final int oldBottom) {
+ // Call the runnable outside the layout pass because very few actions are allowed in
+ // the layout pass
+ ThreadUtil.getMainThreadHandler().post(runnable);
+ view.removeOnLayoutChangeListener(this);
+ }
+ };
+ view.addOnLayoutChangeListener(listener);
+ }
+
+ public static boolean isLandscapeMode() {
+ return Factory.get().getApplicationContext().getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
+ private static Context getApplicationContext() {
+ return Factory.get().getApplicationContext();
+ }
+
+ public static CharSequence commaEllipsize(
+ final String text,
+ final TextPaint paint,
+ final int width,
+ final String oneMore,
+ final String more) {
+ CharSequence ellipsized = TextUtils.commaEllipsize(
+ text,
+ paint,
+ width,
+ oneMore,
+ more);
+ if (TextUtils.isEmpty(ellipsized)) {
+ ellipsized = text;
+ }
+ return ellipsized;
+ }
+
+ /**
+ * Reveals/Hides a view with a scale animation from view center.
+ * @param view the view to animate
+ * @param desiredVisibility desired visibility (e.g. View.GONE) for the animated view.
+ * @param onFinishRunnable an optional runnable called at the end of the animation
+ */
+ public static void revealOrHideViewWithAnimation(final View view, final int desiredVisibility,
+ @Nullable final Runnable onFinishRunnable) {
+ final boolean needAnimation = view.getVisibility() != desiredVisibility;
+ if (needAnimation) {
+ final float fromScale = desiredVisibility == View.VISIBLE ? 0F : 1F;
+ final float toScale = desiredVisibility == View.VISIBLE ? 1F : 0F;
+ final ScaleAnimation showHideAnimation =
+ new ScaleAnimation(fromScale, toScale, fromScale, toScale,
+ ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
+ ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
+ showHideAnimation.setDuration(REVEAL_ANIMATION_DURATION);
+ showHideAnimation.setInterpolator(DEFAULT_INTERPOLATOR);
+ showHideAnimation.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(final Animation animation) {
+ }
+
+ @Override
+ public void onAnimationRepeat(final Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(final Animation animation) {
+ if (onFinishRunnable != null) {
+ // Rather than running this immediately, we post it to happen next so that
+ // the animation will be completed so that the view can be detached from
+ // it's window. Otherwise, we may leak memory.
+ ThreadUtil.getMainThreadHandler().post(onFinishRunnable);
+ }
+ }
+ });
+ view.clearAnimation();
+ view.startAnimation(showHideAnimation);
+ // We are playing a view Animation; unlike view property animations, we can commit the
+ // visibility immediately instead of waiting for animation end.
+ view.setVisibility(desiredVisibility);
+ } else if (onFinishRunnable != null) {
+ // Make sure onFinishRunnable is always executed.
+ ThreadUtil.getMainThreadHandler().post(onFinishRunnable);
+ }
+ }
+
+ public static Rect getMeasuredBoundsOnScreen(final View view) {
+ final int[] location = new int[2];
+ view.getLocationOnScreen(location);
+ return new Rect(location[0], location[1],
+ location[0] + view.getMeasuredWidth(), location[1] + view.getMeasuredHeight());
+ }
+
+ public static void setStatusBarColor(final Activity activity, final int color) {
+ if (OsUtil.isAtLeastL()) {
+ // To achieve the appearance of an 80% opacity blend against a black background,
+ // each color channel is reduced in value by 20%.
+ final int blendedRed = (int) Math.floor(0.8 * Color.red(color));
+ final int blendedGreen = (int) Math.floor(0.8 * Color.green(color));
+ final int blendedBlue = (int) Math.floor(0.8 * Color.blue(color));
+
+ activity.getWindow().setStatusBarColor(
+ Color.rgb(blendedRed, blendedGreen, blendedBlue));
+ }
+ }
+
+ public static void lockOrientation(final Activity activity) {
+ final int orientation = activity.getResources().getConfiguration().orientation;
+ final int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+
+ // rotation tracks the rotation of the device from its natural orientation
+ // orientation tracks whether the screen is landscape or portrait.
+ // It is possible to have a rotation of 0 (device in its natural orientation) in portrait
+ // (phone), or in landscape (tablet), so we have to check both values to determine what to
+ // pass to setRequestedOrientation.
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+ } else if (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ }
+ }
+ }
+
+ public static void unlockOrientation(final Activity activity) {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
+ }
+
+ public static int getPaddingStart(final View view) {
+ return OsUtil.isAtLeastJB_MR1() ? view.getPaddingStart() : view.getPaddingLeft();
+ }
+
+ public static int getPaddingEnd(final View view) {
+ return OsUtil.isAtLeastJB_MR1() ? view.getPaddingEnd() : view.getPaddingRight();
+ }
+
+ public static boolean isRtlMode() {
+ return OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources()
+ .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ }
+
+ /**
+ * Check if the activity needs to be redirected to permission check
+ * @return true if {@link Activity#finish()} was called because redirection was performed
+ */
+ public static boolean redirectToPermissionCheckIfNeeded(final Activity activity) {
+ if (!OsUtil.hasRequiredPermissions()) {
+ UIIntents.get().launchPermissionCheckActivity(activity);
+ } else {
+ // No redirect performed
+ return false;
+ }
+
+ // Redirect performed
+ activity.finish();
+ return true;
+ }
+
+ /**
+ * Called to check if all conditions are nominal and a "go" for some action, such as deleting
+ * a message, that requires this app to be the default app. This is also a precondition
+ * required for sending a draft.
+ * @return true if all conditions are nominal and we're ready to send a message
+ */
+ public static boolean isReadyForAction() {
+ final PhoneUtils phoneUtils = PhoneUtils.getDefault();
+
+ // Have all the conditions been met:
+ // Supports SMS?
+ // Has a preferred sim?
+ // Is the default sms app?
+ return phoneUtils.isSmsCapable() &&
+ phoneUtils.getHasPreferredSmsSim() &&
+ phoneUtils.isDefaultSmsApp();
+ }
+
+ /*
+ * Removes all html markup from the text and replaces links with the the text and a text version
+ * of the href.
+ * @param htmlText HTML markup text
+ * @return Sanitized string with link hrefs inlined
+ */
+ public static String stripHtml(final String htmlText) {
+ final StringBuilder result = new StringBuilder();
+ final Spanned markup = Html.fromHtml(htmlText);
+ final String strippedText = markup.toString();
+
+ final URLSpan[] links = markup.getSpans(0, markup.length() - 1, URLSpan.class);
+ int currentIndex = 0;
+ for (final URLSpan link : links) {
+ final int spanStart = markup.getSpanStart(link);
+ final int spanEnd = markup.getSpanEnd(link);
+ if (spanStart > currentIndex) {
+ result.append(strippedText, currentIndex, spanStart);
+ }
+ final String displayText = strippedText.substring(spanStart, spanEnd);
+ final String linkText = link.getURL();
+ result.append(getApplicationContext().getString(R.string.link_display_format,
+ displayText, linkText));
+ currentIndex = spanEnd;
+ }
+ if (strippedText.length() > currentIndex) {
+ result.append(strippedText, currentIndex, strippedText.length());
+ }
+ return result.toString();
+ }
+
+ public static void setActionBarShadowVisibility(final ActionBarActivity activity, final boolean visible) {
+ final ActionBar actionBar = activity.getSupportActionBar();
+ actionBar.setElevation(visible ?
+ activity.getResources().getDimensionPixelSize(R.dimen.action_bar_elevation) :
+ 0);
+ final View actionBarView = activity.getWindow().getDecorView().findViewById(
+ android.support.v7.appcompat.R.id.decor_content_parent);
+ if (actionBarView != null) {
+ // AppCompatActionBar has one drawable Field, which is the shadow for the action bar
+ // set the alpha on that drawable manually
+ final Field[] fields = actionBarView.getClass().getDeclaredFields();
+ try {
+ for (final Field field : fields) {
+ if (field.getType().equals(Drawable.class)) {
+ field.setAccessible(true);
+ final Drawable shadowDrawable = (Drawable) field.get(actionBarView);
+ if (shadowDrawable != null) {
+ shadowDrawable.setAlpha(visible ? 255 : 0);
+ actionBarView.invalidate();
+ return;
+ }
+ }
+ }
+ } catch (final IllegalAccessException ex) {
+ // Not expected, we should avoid this via field.setAccessible(true) above
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error setting shadow visibility", ex);
+ }
+ }
+ }
+
+ /**
+ * Get the activity that's hosting the view, typically casting view.getContext() as an Activity
+ * is sufficient, but sometimes the context is a context wrapper, in which case we need to case
+ * the base context
+ */
+ public static Activity getActivity(final View view) {
+ if (view == null) {
+ return null;
+ }
+ return getActivity(view.getContext());
+ }
+
+ /**
+ * Get the activity for the supplied context, typically casting context as an Activity
+ * is sufficient, but sometimes the context is a context wrapper, in which case we need to case
+ * the base context
+ */
+ public static Activity getActivity(final Context context) {
+ if (context == null) {
+ return null;
+ }
+ if (context instanceof Activity) {
+ return (Activity) context;
+ }
+ if (context instanceof ContextWrapper) {
+ return getActivity(((ContextWrapper) context).getBaseContext());
+ }
+
+ // We've hit a non-activity context such as an app-context
+ return null;
+ }
+
+ public static RemoteViews getWidgetMissingPermissionView(final Context context) {
+ return new RemoteViews(context.getPackageName(), R.layout.widget_missing_permission);
+ }
+}
diff --git a/src/com/android/messaging/util/UriUtil.java b/src/com/android/messaging/util/UriUtil.java
new file mode 100644
index 0000000..4bbc80d
--- /dev/null
+++ b/src/com/android/messaging/util/UriUtil.java
@@ -0,0 +1,393 @@
+/*
+ * 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.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+import com.android.messaging.Factory;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import com.google.common.io.Resources;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.HashSet;
+
+public class UriUtil {
+ private static final String SCHEME_SMS = "sms";
+ private static final String SCHEME_SMSTO = "smsto";
+ private static final String SCHEME_MMS = "mms";
+ private static final String SCHEME_MMSTO = "smsto";
+ public static final HashSet<String> SMS_MMS_SCHEMES = new HashSet<String>(
+ Arrays.asList(SCHEME_SMS, SCHEME_MMS, SCHEME_SMSTO, SCHEME_MMSTO));
+
+ public static final String SCHEME_BUGLE = "bugle";
+ public static final HashSet<String> SUPPORTED_SCHEME = new HashSet<String>(
+ Arrays.asList(ContentResolver.SCHEME_ANDROID_RESOURCE,
+ ContentResolver.SCHEME_CONTENT,
+ ContentResolver.SCHEME_FILE,
+ SCHEME_BUGLE));
+
+ public static final String SCHEME_TEL = "tel:";
+
+ /**
+ * Get a Uri representation of the file path of a resource file.
+ */
+ public static Uri getUriForResourceFile(final String path) {
+ return TextUtils.isEmpty(path) ? null : Uri.fromFile(new File(path));
+ }
+
+ /**
+ * Extract the path from a file:// Uri, or null if the uri is of other scheme.
+ */
+ public static String getFilePathFromUri(final Uri uri) {
+ if (!isFileUri(uri)) {
+ return null;
+ }
+ return uri.getPath();
+ }
+
+ /**
+ * Returns whether the given Uri is local or remote.
+ */
+ public static boolean isLocalResourceUri(final Uri uri) {
+ final String scheme = uri.getScheme();
+ return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE) ||
+ TextUtils.equals(scheme, ContentResolver.SCHEME_CONTENT) ||
+ TextUtils.equals(scheme, ContentResolver.SCHEME_FILE);
+ }
+
+ /**
+ * Returns whether the given Uri is part of Bugle's app package
+ */
+ public static boolean isBugleAppResource(final Uri uri) {
+ final String scheme = uri.getScheme();
+ return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE);
+ }
+
+ public static boolean isFileUri(final Uri uri) {
+ return uri != null && TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE);
+ }
+
+ /**
+ * Constructs an android.resource:// uri for the given resource id.
+ */
+ public static Uri getUriForResourceId(final Context context, final int resId) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(context.getPackageName())
+ .appendPath(String.valueOf(resId))
+ .build();
+ }
+
+ /**
+ * Returns whether the given Uri string is local.
+ */
+ public static boolean isLocalUri(@NonNull final Uri uri) {
+ Assert.notNull(uri);
+ return SUPPORTED_SCHEME.contains(uri.getScheme());
+ }
+
+ private static final String MEDIA_STORE_URI_KLP = "com.android.providers.media.documents";
+
+ /**
+ * Check if a URI is from the MediaStore
+ */
+ public static boolean isMediaStoreUri(final Uri uri) {
+ final String uriAuthority = uri.getAuthority();
+ return TextUtils.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())
+ && (TextUtils.equals(MediaStore.AUTHORITY, uriAuthority) ||
+ // KK changed the media store authority name
+ TextUtils.equals(MEDIA_STORE_URI_KLP, uriAuthority));
+ }
+
+ /**
+ * Gets the size in bytes for the content uri. Currently we only support content in the
+ * scratch space.
+ */
+ @DoesNotRunOnMainThread
+ public static long getContentSize(final Uri uri) {
+ Assert.isNotMainThread();
+ if (isLocalResourceUri(uri)) {
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd = Factory.get().getApplicationContext()
+ .getContentResolver().openFileDescriptor(uri, "r");
+ return Math.max(pfd.getStatSize(), 0);
+ } catch (final FileNotFoundException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error getting content size", e);
+ } finally {
+ if (pfd != null) {
+ try {
+ pfd.close();
+ } catch (final IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+ } else {
+ Assert.fail("Unsupported uri type!");
+ }
+ return 0;
+ }
+
+ /** @return duration in milliseconds or 0 if not able to determine */
+ public static int getMediaDurationMs(final Uri uri) {
+ final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
+ try {
+ retriever.setDataSource(uri);
+ return retriever.extractInteger(MediaMetadataRetriever.METADATA_KEY_DURATION, 0);
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Unable extract duration from media file: " + uri, e);
+ return 0;
+ } finally {
+ retriever.release();
+ }
+ }
+
+ /**
+ * Persist a piece of content from the given input stream, byte by byte to the scratch
+ * directory.
+ * @return the output Uri if the operation succeeded, or null if failed.
+ */
+ @DoesNotRunOnMainThread
+ public static Uri persistContentToScratchSpace(final InputStream inputStream) {
+ final Context context = Factory.get().getApplicationContext();
+ final Uri scratchSpaceUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
+ return copyContent(context, inputStream, scratchSpaceUri);
+ }
+
+ /**
+ * Persist a piece of content from the given sourceUri, byte by byte to the scratch
+ * directory.
+ * @return the output Uri if the operation succeeded, or null if failed.
+ */
+ @DoesNotRunOnMainThread
+ public static Uri persistContentToScratchSpace(final Uri sourceUri) {
+ InputStream inputStream = null;
+ final Context context = Factory.get().getApplicationContext();
+ try {
+ if (UriUtil.isLocalResourceUri(sourceUri)) {
+ inputStream = context.getContentResolver().openInputStream(sourceUri);
+ } else {
+ // The content is remote. Download it.
+ final URL url = new URL(sourceUri.toString());
+ final URLConnection ucon = url.openConnection();
+ inputStream = new BufferedInputStream(ucon.getInputStream());
+ }
+ return persistContentToScratchSpace(inputStream);
+ } catch (final Exception ex) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
+ return null;
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Persist a piece of content from the given input stream, byte by byte to the specified
+ * directory.
+ * @return the output Uri if the operation succeeded, or null if failed.
+ */
+ @DoesNotRunOnMainThread
+ public static Uri persistContent(
+ final InputStream inputStream, final File outputDir, final String contentType) {
+ if (!outputDir.exists() && !outputDir.mkdirs()) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error creating " + outputDir.getAbsolutePath());
+ return null;
+ }
+
+ final Context context = Factory.get().getApplicationContext();
+ try {
+ final Uri targetUri = Uri.fromFile(FileUtil.getNewFile(outputDir, contentType));
+ return copyContent(context, inputStream, targetUri);
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error creating file in " + outputDir.getAbsolutePath());
+ return null;
+ }
+ }
+
+ /**
+ * Persist a piece of content from the given sourceUri, byte by byte to the
+ * specified output directory.
+ * @return the output Uri if the operation succeeded, or null if failed.
+ */
+ @DoesNotRunOnMainThread
+ public static Uri persistContent(
+ final Uri sourceUri, final File outputDir, final String contentType) {
+ InputStream inputStream = null;
+ final Context context = Factory.get().getApplicationContext();
+ try {
+ if (UriUtil.isLocalResourceUri(sourceUri)) {
+ inputStream = context.getContentResolver().openInputStream(sourceUri);
+ } else {
+ // The content is remote. Download it.
+ final URL url = new URL(sourceUri.toString());
+ final URLConnection ucon = url.openConnection();
+ inputStream = new BufferedInputStream(ucon.getInputStream());
+ }
+ return persistContent(inputStream, outputDir, contentType);
+ } catch (final Exception ex) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
+ return null;
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
+ }
+ }
+ }
+ }
+
+ /** @return uri of target file, or null on error */
+ @DoesNotRunOnMainThread
+ private static Uri copyContent(
+ final Context context, final InputStream inputStream, final Uri targetUri) {
+ Assert.isNotMainThread();
+ OutputStream outputStream = null;
+ try {
+ outputStream = context.getContentResolver().openOutputStream(targetUri);
+ ByteStreams.copy(inputStream, outputStream);
+ } catch (final Exception ex) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error while copying content ", ex);
+ return null;
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.flush();
+ } catch (final IOException e) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "error trying to flush the outputStream", e);
+ return null;
+ } finally {
+ try {
+ outputStream.close();
+ } catch (final IOException e) {
+ // Do nothing.
+ }
+ }
+ }
+ }
+ return targetUri;
+ }
+
+ public static boolean isSmsMmsUri(final Uri uri) {
+ return uri != null && SMS_MMS_SCHEMES.contains(uri.getScheme());
+ }
+
+ /**
+ * Extract recipient destinations from Uri of form
+ * SCHEME:destionation[,destination]?otherstuff
+ * where SCHEME is one of the supported sms/mms schemes.
+ *
+ * @param uri sms/mms uri
+ * @return recipient destinations or null
+ */
+ public static String[] parseRecipientsFromSmsMmsUri(final Uri uri) {
+ if (!isSmsMmsUri(uri)) {
+ return null;
+ }
+ final String[] parts = uri.getSchemeSpecificPart().split("\\?");
+ if (TextUtils.isEmpty(parts[0])) {
+ return null;
+ }
+ // replaceUnicodeDigits will replace digits typed in other languages (i.e. Egyptian) with
+ // the usual ascii equivalents.
+ return TextUtil.replaceUnicodeDigits(parts[0]).replace(';', ',').split(",");
+ }
+
+ /**
+ * Return the length of the file to which contentUri refers
+ *
+ * @param contentUri URI for the file of which we want the length
+ * @return Length of the file or AssetFileDescriptor.UNKNOWN_LENGTH
+ */
+ public static long getUriContentLength(final Uri contentUri) {
+ final Context context = Factory.get().getApplicationContext();
+ AssetFileDescriptor afd = null;
+ try {
+ afd = context.getContentResolver().openAssetFileDescriptor(contentUri, "r");
+ return afd.getLength();
+ } catch (final FileNotFoundException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Failed to query length of " + contentUri);
+ } finally {
+ if (afd != null) {
+ try {
+ afd.close();
+ } catch (final IOException e) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "Failed to close afd for " + contentUri);
+ }
+ }
+ }
+ return AssetFileDescriptor.UNKNOWN_LENGTH;
+ }
+
+ /**
+ * Download data from the given url to the given local file.
+ * @return true if the download was successful.
+ *
+ * TODO: Add retry/exponential backoff logic.
+ */
+ @DoesNotRunOnMainThread
+ public static boolean downloadDataFromUrl(final String urlString, final File localFile) {
+ Assert.isNotMainThread();
+ LogUtil.i(LogUtil.BUGLE_TAG, "Downloading from " + urlString + " to " + localFile);
+ try {
+ Files.createParentDirs(localFile);
+ final URL inUrl = new URL(urlString);
+ ByteStreams.copy(Resources.newInputStreamSupplier(inUrl),
+ Files.newOutputStreamSupplier(localFile));
+ return true;
+ } catch (final IOException ex) {
+ LogUtil.e(LogUtil.BUGLE_TAG, "Error downloading from " + urlString, ex);
+ return false;
+ }
+ }
+
+ /** @return string representation of URI or null if URI was null */
+ public static String stringFromUri(final Uri uri) {
+ return uri == null ? null : uri.toString();
+ }
+
+ /** @return URI created from string or null if string was null or empty */
+ public static Uri uriFromString(final String uriString) {
+ return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString);
+ }
+}
diff --git a/src/com/android/messaging/util/VersionUtil.java b/src/com/android/messaging/util/VersionUtil.java
new file mode 100644
index 0000000..b87aa55
--- /dev/null
+++ b/src/com/android/messaging/util/VersionUtil.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+import java.util.Locale;
+
+public final class VersionUtil {
+ private static final Object sLock = new Object();
+ private static VersionUtil sInstance;
+ private final String mSimpleVersionName;
+ private final int mVersionCode;
+
+ public static VersionUtil getInstance(final Context context) {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new VersionUtil(context);
+ }
+ }
+ return sInstance;
+ }
+
+ private VersionUtil(final Context context) {
+ int versionCode;
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(
+ context.getPackageName(), 0);
+ versionCode = pi.versionCode;
+ } catch (final NameNotFoundException exception) {
+ Assert.fail("couldn't get package info " + exception);
+ versionCode = -1;
+ }
+ mVersionCode = versionCode;
+ final int majorBuildNumber = versionCode / 1000;
+ // Use US locale to format version number so that other language characters don't
+ // show up in version string.
+ mSimpleVersionName = String.format(Locale.US, "%d.%d.%03d",
+ majorBuildNumber / 10000,
+ (majorBuildNumber / 1000) % 10,
+ majorBuildNumber % 1000);
+ }
+
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ public String getSimpleName() {
+ return mSimpleVersionName;
+ }
+}
diff --git a/src/com/android/messaging/util/WakeLockHelper.java b/src/com/android/messaging/util/WakeLockHelper.java
new file mode 100644
index 0000000..c9a9152
--- /dev/null
+++ b/src/com/android/messaging/util/WakeLockHelper.java
@@ -0,0 +1,121 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Debug;
+import android.os.PowerManager;
+import android.os.Process;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Helper class used to manage wakelock state
+ */
+public class WakeLockHelper {
+ private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
+ private static final boolean VERBOSE = false;
+
+ @VisibleForTesting
+ public static final String EXTRA_CALLING_PID = "pid";
+
+ private final Object mLock = new Object();
+ private final String mWakeLockId;
+ private final int mMyPid;
+
+ private PowerManager.WakeLock mWakeLock;
+
+ public WakeLockHelper(final String wakeLockId) {
+ mWakeLockId = wakeLockId;
+ mMyPid = Process.myPid();
+ }
+
+ /**
+ * Acquire the wakelock
+ */
+ public void acquire(final Context context, final Intent intent, final int opcode) {
+ synchronized (mLock) {
+ if (mWakeLock == null) {
+ if (VERBOSE) {
+ LogUtil.v(TAG, "initializing wakelock");
+ }
+ final PowerManager pm = (PowerManager)
+ context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mWakeLockId);
+ }
+ }
+ if (VERBOSE) {
+ LogUtil.v(TAG, "acquiring " + mWakeLockId + " for opcode " + opcode);
+ }
+ mWakeLock.acquire();
+ intent.putExtra(EXTRA_CALLING_PID, mMyPid);
+ }
+
+ /**
+ * Check if wakelock held by this process
+ */
+ public boolean isHeld(final Intent intent) {
+ final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1));
+ return (respectWakeLock && mWakeLock.isHeld());
+ }
+
+ /**
+ * Ensure that wakelock is held by this process
+ */
+ public boolean ensure(final Intent intent, final int opcode) {
+ final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1));
+ if (VERBOSE) {
+ LogUtil.v(TAG, "WakeLockHelper.ensure Intent " + intent + " "
+ + intent.getAction() + " opcode: " + opcode
+ + " respectWakeLock " + respectWakeLock);
+ }
+
+ if (respectWakeLock) {
+ final boolean isHeld = (respectWakeLock && isHeld(intent));
+ if (!isHeld) {
+ LogUtil.e(TAG, "WakeLockHelper.ensure called " + intent + " " + intent.getAction()
+ + " opcode: " + opcode + " sWakeLock: " + mWakeLock + " isHeld: "
+ + ((mWakeLock == null) ? "(null)" : mWakeLock.isHeld()));
+ if (!Debug.isDebuggerConnected()) {
+ Assert.fail("WakeLock dropped prior to service starting");
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Release wakelock (if it is held by this process)
+ */
+ public void release(final Intent intent, final int opcode) {
+ final boolean respectWakeLock = (mMyPid == intent.getIntExtra(EXTRA_CALLING_PID, -1));
+ if (respectWakeLock) {
+ try {
+ mWakeLock.release();
+ } catch (final RuntimeException ex) {
+ LogUtil.e(TAG, "KeepAliveService.onHandleIntent exit crash " + intent + " "
+ + intent.getAction() + " opcode: " + opcode + " sWakeLock: " + mWakeLock
+ + " isHeld: " + ((mWakeLock == null) ? "(null)" : mWakeLock.isHeld()));
+ if (!Debug.isDebuggerConnected()) {
+ Assert.fail("WakeLock no longer held at end of handler");
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/YouTubeUtil.java b/src/com/android/messaging/util/YouTubeUtil.java
new file mode 100644
index 0000000..203a666
--- /dev/null
+++ b/src/com/android/messaging/util/YouTubeUtil.java
@@ -0,0 +1,98 @@
+/*
+ * 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.util;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+public class YouTubeUtil {
+ private static final String YOUTUBE_HOST_1 = "www.youtube.com";
+ private static final String YOUTUBE_HOST_2 = "youtube.com";
+ private static final String YOUTUBE_HOST_3 = "m.youtube.com";
+ private static final String YOUTUBE_HOST_4 = "youtube.googleapis.com";
+ private static final String YOUTUBE_HOST_5 = "youtu.be";
+
+ private static final String YOUTUBE_PATH_1 = "/watch";
+ private static final String YOUTUBE_PATH_2 = "/embed/";
+ private static final String YOUTUBE_PATH_3 = "/v/";
+ private static final String YOUTUBE_PATH_4 = "/apiplayer";
+
+ public static final String YOUTUBE_STATIC_THUMBNAIL_PREFIX = "https://img.youtube.com/vi/";
+ public static final String YOUTUBE_STATIC_THUMBNAIL_END = "/hqdefault.jpg";
+
+ public static String getYoutubePreviewImageLink(String urlString) {
+ // Types of youtube urls:
+ // 1.) http://www.youtube.com/watch?v=VIDEOID
+ // 2.) http://www.youtube.com/embed/VIDEOID
+ // 3.) http://www.youtube.com/v/VIDEOID
+ // 3a.) https://youtube.googleapis.com/v/VIDEOID
+ // 4.) http://www.youtube.com/apiplayer?video_id=VIDEO_ID
+ // 5.) http://youtu.be/VIDEOID
+ if (!urlString.startsWith("http")) {
+ // Apparently the url is not an RFC 2396 compliant uri without the port
+ urlString = "http://" + urlString;
+ }
+ final Uri uri = Uri.parse(urlString);
+ final String host = uri.getHost();
+ if (YOUTUBE_HOST_1.equalsIgnoreCase(host)
+ || YOUTUBE_HOST_2.equalsIgnoreCase(host)
+ || YOUTUBE_HOST_3.equalsIgnoreCase(host)
+ || YOUTUBE_HOST_4.equalsIgnoreCase(host)
+ || YOUTUBE_HOST_5.equalsIgnoreCase(host)) {
+ final String videoId = getYouTubeVideoId(uri);
+ if (!TextUtils.isEmpty(videoId)) {
+ return YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId + YOUTUBE_STATIC_THUMBNAIL_END;
+ }
+ return null;
+ }
+ return null;
+ }
+
+ private static String getYouTubeVideoId(Uri uri) {
+ final String urlPath = uri.getPath();
+
+ if (TextUtils.isEmpty(urlPath)) {
+ // There is no path so no need to continue.
+ return null;
+ }
+ // Case 1
+ if (urlPath.startsWith(YOUTUBE_PATH_1)) {
+ return uri.getQueryParameter("v");
+ }
+ // Case 2
+ if (urlPath.startsWith(YOUTUBE_PATH_2)) {
+ return getVideoIdFromPath(YOUTUBE_PATH_2, urlPath);
+ }
+ // Case 3
+ if (urlPath.startsWith(YOUTUBE_PATH_3)) {
+ return getVideoIdFromPath(YOUTUBE_PATH_3, urlPath);
+ }
+ // Case 4
+ if (urlPath.startsWith(YOUTUBE_PATH_4)) {
+ return uri.getQueryParameter("video_id");
+ }
+ // Case 5
+ if (YOUTUBE_HOST_5.equalsIgnoreCase(uri.getHost())) {
+ return getVideoIdFromPath("/", urlPath);
+ }
+ return null;
+ }
+
+ private static String getVideoIdFromPath(String prefixSubstring, String urlPath) {
+ return urlPath.substring(prefixSubstring.length());
+ }
+
+}
diff --git a/src/com/android/messaging/util/exif/ByteBufferInputStream.java b/src/com/android/messaging/util/exif/ByteBufferInputStream.java
new file mode 100644
index 0000000..9db92ef
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ByteBufferInputStream.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+class ByteBufferInputStream extends InputStream {
+
+ private final ByteBuffer mBuf;
+
+ public ByteBufferInputStream(ByteBuffer buf) {
+ mBuf = buf;
+ }
+
+ @Override
+ public int read() {
+ if (!mBuf.hasRemaining()) {
+ return -1;
+ }
+ return mBuf.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) {
+ if (!mBuf.hasRemaining()) {
+ return -1;
+ }
+
+ len = Math.min(len, mBuf.remaining());
+ mBuf.get(bytes, off, len);
+ return len;
+ }
+}
diff --git a/src/com/android/messaging/util/exif/CountedDataInputStream.java b/src/com/android/messaging/util/exif/CountedDataInputStream.java
new file mode 100644
index 0000000..ce766d9
--- /dev/null
+++ b/src/com/android/messaging/util/exif/CountedDataInputStream.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+class CountedDataInputStream extends FilterInputStream {
+
+ private int mCount = 0;
+
+ // allocate a byte buffer for a long value;
+ private final byte mByteArray[] = new byte[8];
+ private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
+
+ protected CountedDataInputStream(InputStream in) {
+ super(in);
+ }
+
+ public int getReadByteCount() {
+ return mCount;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ int r = in.read(b);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int r = in.read(b, off, len);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int r = in.read();
+ mCount += (r >= 0) ? 1 : 0;
+ return r;
+ }
+
+ @Override
+ public long skip(long length) throws IOException {
+ long skip = in.skip(length);
+ mCount += skip;
+ return skip;
+ }
+
+ public void skipOrThrow(long length) throws IOException {
+ if (skip(length) != length) {
+ throw new EOFException();
+ }
+ }
+
+ public void skipTo(long target) throws IOException {
+ long cur = mCount;
+ long diff = target - cur;
+ assert(diff >= 0);
+ skipOrThrow(diff);
+ }
+
+ public void readOrThrow(byte[] b, int off, int len) throws IOException {
+ int r = read(b, off, len);
+ if (r != len) {
+ throw new EOFException();
+ }
+ }
+
+ public void readOrThrow(byte[] b) throws IOException {
+ readOrThrow(b, 0, b.length);
+ }
+
+ public void setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ }
+
+ public ByteOrder getByteOrder() {
+ return mByteBuffer.order();
+ }
+
+ public short readShort() throws IOException {
+ readOrThrow(mByteArray, 0 , 2);
+ mByteBuffer.rewind();
+ return mByteBuffer.getShort();
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ public int readInt() throws IOException {
+ readOrThrow(mByteArray, 0 , 4);
+ mByteBuffer.rewind();
+ return mByteBuffer.getInt();
+ }
+
+ public long readUnsignedInt() throws IOException {
+ return readInt() & 0xffffffffL;
+ }
+
+ public long readLong() throws IOException {
+ readOrThrow(mByteArray, 0 , 8);
+ mByteBuffer.rewind();
+ return mByteBuffer.getLong();
+ }
+
+ public String readString(int n) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, "UTF8");
+ }
+
+ public String readString(int n, Charset charset) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, charset);
+ }
+}
diff --git a/src/com/android/messaging/util/exif/ExifData.java b/src/com/android/messaging/util/exif/ExifData.java
new file mode 100644
index 0000000..77ba4e9
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifData.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import android.util.Log;
+import com.android.messaging.util.LogUtil;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class stores the EXIF header in IFDs according to the JPEG
+ * specification. It is the result produced by {@link ExifReader}.
+ *
+ * @see ExifReader
+ * @see IfdData
+ */
+class ExifData {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final byte[] USER_COMMENT_ASCII = {
+ 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
+ };
+ private static final byte[] USER_COMMENT_JIS = {
+ 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ private static final byte[] USER_COMMENT_UNICODE = {
+ 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
+ };
+
+ private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
+ private byte[] mThumbnail;
+ private final ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
+ private final ByteOrder mByteOrder;
+
+ ExifData(ByteOrder order) {
+ mByteOrder = order;
+ }
+
+ /**
+ * Gets the compressed thumbnail. Returns null if there is no compressed
+ * thumbnail.
+ *
+ * @see #hasCompressedThumbnail()
+ */
+ protected byte[] getCompressedThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * Sets the compressed thumbnail.
+ */
+ protected void setCompressedThumbnail(byte[] thumbnail) {
+ mThumbnail = thumbnail;
+ }
+
+ /**
+ * Returns true it this header contains a compressed thumbnail.
+ */
+ protected boolean hasCompressedThumbnail() {
+ return mThumbnail != null;
+ }
+
+ /**
+ * Adds an uncompressed strip.
+ */
+ protected void setStripBytes(int index, byte[] strip) {
+ if (index < mStripBytes.size()) {
+ mStripBytes.set(index, strip);
+ } else {
+ for (int i = mStripBytes.size(); i < index; i++) {
+ mStripBytes.add(null);
+ }
+ mStripBytes.add(strip);
+ }
+ }
+
+ /**
+ * Gets the strip count.
+ */
+ protected int getStripCount() {
+ return mStripBytes.size();
+ }
+
+ /**
+ * Gets the strip at the specified index.
+ *
+ * @exceptions #IndexOutOfBoundException
+ */
+ protected byte[] getStrip(int index) {
+ return mStripBytes.get(index);
+ }
+
+ /**
+ * Returns true if this header contains uncompressed strip.
+ */
+ protected boolean hasUncompressedStrip() {
+ return mStripBytes.size() != 0;
+ }
+
+ /**
+ * Gets the byte order.
+ */
+ protected ByteOrder getByteOrder() {
+ return mByteOrder;
+ }
+
+ /**
+ * Returns the {@link IfdData} object corresponding to a given IFD if it
+ * exists or null.
+ */
+ protected IfdData getIfdData(int ifdId) {
+ if (ExifTag.isValidIfd(ifdId)) {
+ return mIfdDatas[ifdId];
+ }
+ return null;
+ }
+
+ /**
+ * Adds IFD data. If IFD data of the same type already exists, it will be
+ * replaced by the new data.
+ */
+ protected void addIfdData(IfdData data) {
+ mIfdDatas[data.getId()] = data;
+ }
+
+ /**
+ * Returns the {@link IfdData} object corresponding to a given IFD or
+ * generates one if none exist.
+ */
+ protected IfdData getOrCreateIfdData(int ifdId) {
+ IfdData ifdData = mIfdDatas[ifdId];
+ if (ifdData == null) {
+ ifdData = new IfdData(ifdId);
+ mIfdDatas[ifdId] = ifdData;
+ }
+ return ifdData;
+ }
+
+ /**
+ * Returns the tag with a given TID in the given IFD if the tag exists.
+ * Otherwise returns null.
+ */
+ protected ExifTag getTag(short tag, int ifd) {
+ IfdData ifdData = mIfdDatas[ifd];
+ return (ifdData == null) ? null : ifdData.getTag(tag);
+ }
+
+ /**
+ * Adds the given ExifTag to its default IFD and returns an existing ExifTag
+ * with the same TID or null if none exist.
+ */
+ protected ExifTag addTag(ExifTag tag) {
+ if (tag != null) {
+ int ifd = tag.getIfd();
+ return addTag(tag, ifd);
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given ExifTag to the given IFD and returns an existing ExifTag
+ * with the same TID or null if none exist.
+ */
+ protected ExifTag addTag(ExifTag tag, int ifdId) {
+ if (tag != null && ExifTag.isValidIfd(ifdId)) {
+ IfdData ifdData = getOrCreateIfdData(ifdId);
+ return ifdData.setTag(tag);
+ }
+ return null;
+ }
+
+ protected void clearThumbnailAndStrips() {
+ mThumbnail = null;
+ mStripBytes.clear();
+ }
+
+ /**
+ * Removes the thumbnail and its related tags. IFD1 will be removed.
+ */
+ protected void removeThumbnailData() {
+ clearThumbnailAndStrips();
+ mIfdDatas[IfdId.TYPE_IFD_1] = null;
+ }
+
+ /**
+ * Removes the tag with a given TID and IFD.
+ */
+ protected void removeTag(short tagId, int ifdId) {
+ IfdData ifdData = mIfdDatas[ifdId];
+ if (ifdData == null) {
+ return;
+ }
+ ifdData.removeTag(tagId);
+ }
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ protected String getUserComment() {
+ IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
+ if (ifdData == null) {
+ return null;
+ }
+ ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
+ if (tag == null) {
+ return null;
+ }
+ if (tag.getComponentCount() < 8) {
+ return null;
+ }
+
+ byte[] buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+
+ byte[] code = new byte[8];
+ System.arraycopy(buf, 0, code, 0, 8);
+
+ try {
+ if (Arrays.equals(code, USER_COMMENT_ASCII)) {
+ return new String(buf, 8, buf.length - 8, "US-ASCII");
+ } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
+ return new String(buf, 8, buf.length - 8, "EUC-JP");
+ } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
+ return new String(buf, 8, buf.length - 8, "UTF-16");
+ } else {
+ return null;
+ }
+ } catch (UnsupportedEncodingException e) {
+ Log.w(TAG, "Failed to decode the user comment");
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s in the ExifData or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTags() {
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+ for (IfdData d : mIfdDatas) {
+ if (d != null) {
+ ExifTag[] tags = d.getAllTags();
+ if (tags != null) {
+ for (ExifTag t : tags) {
+ ret.add(t);
+ }
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s in a given IFD or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTagsForIfd(int ifd) {
+ IfdData d = mIfdDatas[ifd];
+ if (d == null) {
+ return null;
+ }
+ ExifTag[] tags = d.getAllTags();
+ if (tags == null) {
+ return null;
+ }
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
+ for (ExifTag t : tags) {
+ ret.add(t);
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s with a given TID or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTagsForTagId(short tag) {
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+ for (IfdData d : mIfdDatas) {
+ if (d != null) {
+ ExifTag t = d.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof ExifData) {
+ ExifData data = (ExifData) obj;
+ if (data.mByteOrder != mByteOrder ||
+ data.mStripBytes.size() != mStripBytes.size() ||
+ !Arrays.equals(data.mThumbnail, mThumbnail)) {
+ return false;
+ }
+ for (int i = 0; i < mStripBytes.size(); i++) {
+ if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
+ return false;
+ }
+ }
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ IfdData ifd1 = data.getIfdData(i);
+ IfdData ifd2 = getIfdData(i);
+ if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/messaging/util/exif/ExifInterface.java b/src/com/android/messaging/util/exif/ExifInterface.java
new file mode 100644
index 0000000..b556748
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifInterface.java
@@ -0,0 +1,2448 @@
+/*
+ * Copyright (C) 2013 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.util.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.SparseIntArray;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ * <p>
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+ public static final int TAG_NULL = -1;
+ public static final int IFD_NULL = -1;
+ public static final int DEFINITION_NULL = 0;
+
+ /**
+ * Tag constants for Jeita EXIF 2.2
+ */
+
+ // IFD 0
+ public static final int TAG_IMAGE_WIDTH =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
+ public static final int TAG_IMAGE_LENGTH =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
+ public static final int TAG_BITS_PER_SAMPLE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
+ public static final int TAG_COMPRESSION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
+ public static final int TAG_PHOTOMETRIC_INTERPRETATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
+ public static final int TAG_IMAGE_DESCRIPTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
+ public static final int TAG_MAKE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
+ public static final int TAG_MODEL =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
+ public static final int TAG_STRIP_OFFSETS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+ public static final int TAG_ORIENTATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+ public static final int TAG_SAMPLES_PER_PIXEL =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
+ public static final int TAG_ROWS_PER_STRIP =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
+ public static final int TAG_STRIP_BYTE_COUNTS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+ public static final int TAG_X_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
+ public static final int TAG_Y_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
+ public static final int TAG_PLANAR_CONFIGURATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
+ public static final int TAG_RESOLUTION_UNIT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
+ public static final int TAG_TRANSFER_FUNCTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
+ public static final int TAG_SOFTWARE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
+ public static final int TAG_DATE_TIME =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
+ public static final int TAG_ARTIST =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
+ public static final int TAG_WHITE_POINT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
+ public static final int TAG_PRIMARY_CHROMATICITIES =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
+ public static final int TAG_Y_CB_CR_COEFFICIENTS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
+ public static final int TAG_Y_CB_CR_SUB_SAMPLING =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
+ public static final int TAG_Y_CB_CR_POSITIONING =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
+ public static final int TAG_REFERENCE_BLACK_WHITE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
+ public static final int TAG_COPYRIGHT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
+ public static final int TAG_EXIF_IFD =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+ public static final int TAG_GPS_IFD =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+ // IFD 1
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT =
+ defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+ defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+ // IFD Exif Tags
+ public static final int TAG_EXPOSURE_TIME =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
+ public static final int TAG_F_NUMBER =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
+ public static final int TAG_EXPOSURE_PROGRAM =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
+ public static final int TAG_SPECTRAL_SENSITIVITY =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
+ public static final int TAG_ISO_SPEED_RATINGS =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
+ public static final int TAG_OECF =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
+ public static final int TAG_EXIF_VERSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
+ public static final int TAG_DATE_TIME_ORIGINAL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
+ public static final int TAG_DATE_TIME_DIGITIZED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
+ public static final int TAG_COMPONENTS_CONFIGURATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
+ public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
+ public static final int TAG_SHUTTER_SPEED_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
+ public static final int TAG_APERTURE_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
+ public static final int TAG_BRIGHTNESS_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
+ public static final int TAG_EXPOSURE_BIAS_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
+ public static final int TAG_MAX_APERTURE_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
+ public static final int TAG_SUBJECT_DISTANCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
+ public static final int TAG_METERING_MODE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
+ public static final int TAG_LIGHT_SOURCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
+ public static final int TAG_FLASH =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
+ public static final int TAG_FOCAL_LENGTH =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
+ public static final int TAG_SUBJECT_AREA =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
+ public static final int TAG_MAKER_NOTE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
+ public static final int TAG_USER_COMMENT =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
+ public static final int TAG_SUB_SEC_TIME =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
+ public static final int TAG_SUB_SEC_TIME_ORIGINAL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
+ public static final int TAG_SUB_SEC_TIME_DIGITIZED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
+ public static final int TAG_FLASHPIX_VERSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
+ public static final int TAG_COLOR_SPACE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
+ public static final int TAG_PIXEL_X_DIMENSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
+ public static final int TAG_PIXEL_Y_DIMENSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
+ public static final int TAG_RELATED_SOUND_FILE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
+ public static final int TAG_INTEROPERABILITY_IFD =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+ public static final int TAG_FLASH_ENERGY =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
+ public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
+ public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
+ public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
+ public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
+ public static final int TAG_SUBJECT_LOCATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
+ public static final int TAG_EXPOSURE_INDEX =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
+ public static final int TAG_SENSING_METHOD =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
+ public static final int TAG_FILE_SOURCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
+ public static final int TAG_SCENE_TYPE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
+ public static final int TAG_CFA_PATTERN =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
+ public static final int TAG_CUSTOM_RENDERED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
+ public static final int TAG_EXPOSURE_MODE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
+ public static final int TAG_WHITE_BALANCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
+ public static final int TAG_DIGITAL_ZOOM_RATIO =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
+ public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
+ public static final int TAG_SCENE_CAPTURE_TYPE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
+ public static final int TAG_GAIN_CONTROL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
+ public static final int TAG_CONTRAST =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
+ public static final int TAG_SATURATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
+ public static final int TAG_SHARPNESS =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
+ public static final int TAG_DEVICE_SETTING_DESCRIPTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
+ public static final int TAG_SUBJECT_DISTANCE_RANGE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
+ public static final int TAG_IMAGE_UNIQUE_ID =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
+ // IFD GPS tags
+ public static final int TAG_GPS_VERSION_ID =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
+ public static final int TAG_GPS_LATITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
+ public static final int TAG_GPS_LATITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
+ public static final int TAG_GPS_LONGITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
+ public static final int TAG_GPS_LONGITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
+ public static final int TAG_GPS_ALTITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
+ public static final int TAG_GPS_ALTITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
+ public static final int TAG_GPS_TIME_STAMP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
+ public static final int TAG_GPS_SATTELLITES =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
+ public static final int TAG_GPS_STATUS =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
+ public static final int TAG_GPS_MEASURE_MODE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
+ public static final int TAG_GPS_DOP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
+ public static final int TAG_GPS_SPEED_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
+ public static final int TAG_GPS_SPEED =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
+ public static final int TAG_GPS_TRACK_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
+ public static final int TAG_GPS_TRACK =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
+ public static final int TAG_GPS_IMG_DIRECTION_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
+ public static final int TAG_GPS_IMG_DIRECTION =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
+ public static final int TAG_GPS_MAP_DATUM =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
+ public static final int TAG_GPS_DEST_LATITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
+ public static final int TAG_GPS_DEST_LATITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
+ public static final int TAG_GPS_DEST_LONGITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
+ public static final int TAG_GPS_DEST_LONGITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
+ public static final int TAG_GPS_DEST_BEARING_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
+ public static final int TAG_GPS_DEST_BEARING =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
+ public static final int TAG_GPS_DEST_DISTANCE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
+ public static final int TAG_GPS_DEST_DISTANCE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
+ public static final int TAG_GPS_PROCESSING_METHOD =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
+ public static final int TAG_GPS_AREA_INFORMATION =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
+ public static final int TAG_GPS_DATE_STAMP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
+ public static final int TAG_GPS_DIFFERENTIAL =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
+ // IFD Interoperability tags
+ public static final int TAG_INTEROPERABILITY_INDEX =
+ defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+
+ /**
+ * Tags that contain offset markers. These are included in the banned
+ * defines.
+ */
+ private static HashSet<Short> sOffsetTags = new HashSet<Short>();
+ static {
+ sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+ sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+ }
+
+ /**
+ * Tags with definitions that cannot be overridden (banned defines).
+ */
+ protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
+ static {
+ sBannedDefines.add(getTrueTagKey(TAG_NULL));
+ sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
+ }
+
+ /**
+ * Returns the constant representing a tag with a given TID and default IFD.
+ */
+ public static int defineTag(int ifdId, short tagId) {
+ return (tagId & 0x0000ffff) | (ifdId << 16);
+ }
+
+ /**
+ * Returns the TID for a tag constant.
+ */
+ public static short getTrueTagKey(int tag) {
+ // Truncate
+ return (short) tag;
+ }
+
+ /**
+ * Returns the default IFD for a tag constant.
+ */
+ public static int getTrueIfd(int tag) {
+ return tag >>> 16;
+ }
+
+ /**
+ * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
+ * follows:
+ * <ul>
+ * <li>TOP_LEFT is the normal orientation.</li>
+ * <li>TOP_RIGHT is a left-right mirror.</li>
+ * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
+ * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
+ * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
+ * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
+ * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
+ * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
+ * </ul>
+ */
+ public static interface Orientation {
+ public static final short TOP_LEFT = 1;
+ public static final short TOP_RIGHT = 2;
+ public static final short BOTTOM_LEFT = 3;
+ public static final short BOTTOM_RIGHT = 4;
+ public static final short LEFT_TOP = 5;
+ public static final short RIGHT_TOP = 6;
+ public static final short LEFT_BOTTOM = 7;
+ public static final short RIGHT_BOTTOM = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_Y_CB_CR_POSITIONING}
+ */
+ public static interface YCbCrPositioning {
+ public static final short CENTERED = 1;
+ public static final short CO_SITED = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_COMPRESSION}
+ */
+ public static interface Compression {
+ public static final short UNCOMPRESSION = 1;
+ public static final short JPEG = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_RESOLUTION_UNIT}
+ */
+ public static interface ResolutionUnit {
+ public static final short INCHES = 2;
+ public static final short CENTIMETERS = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
+ */
+ public static interface PhotometricInterpretation {
+ public static final short RGB = 2;
+ public static final short YCBCR = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_PLANAR_CONFIGURATION}
+ */
+ public static interface PlanarConfiguration {
+ public static final short CHUNKY = 1;
+ public static final short PLANAR = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_EXPOSURE_PROGRAM}
+ */
+ public static interface ExposureProgram {
+ public static final short NOT_DEFINED = 0;
+ public static final short MANUAL = 1;
+ public static final short NORMAL_PROGRAM = 2;
+ public static final short APERTURE_PRIORITY = 3;
+ public static final short SHUTTER_PRIORITY = 4;
+ public static final short CREATIVE_PROGRAM = 5;
+ public static final short ACTION_PROGRAM = 6;
+ public static final short PROTRAIT_MODE = 7;
+ public static final short LANDSCAPE_MODE = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_METERING_MODE}
+ */
+ public static interface MeteringMode {
+ public static final short UNKNOWN = 0;
+ public static final short AVERAGE = 1;
+ public static final short CENTER_WEIGHTED_AVERAGE = 2;
+ public static final short SPOT = 3;
+ public static final short MULTISPOT = 4;
+ public static final short PATTERN = 5;
+ public static final short PARTAIL = 6;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
+ * standard, we can treat this constant as bitwise flag.
+ * <p>
+ * e.g.
+ * <p>
+ * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
+ * MODE_AUTO_MODE
+ */
+ public static interface Flash {
+ // LSB
+ public static final short DID_NOT_FIRED = 0;
+ public static final short FIRED = 1;
+ // 1st~2nd bits
+ public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
+ public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
+ public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
+ // 3rd~4th bits
+ public static final short MODE_UNKNOWN = 0 << 3;
+ public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
+ public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
+ public static final short MODE_AUTO_MODE = 3 << 3;
+ // 5th bit
+ public static final short FUNCTION_PRESENT = 0 << 5;
+ public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+ // 6th bit
+ public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+ public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+ }
+
+ /**
+ * Constants for {@link TAG_COLOR_SPACE}
+ */
+ public static interface ColorSpace {
+ public static final short SRGB = 1;
+ public static final short UNCALIBRATED = (short) 0xFFFF;
+ }
+
+ /**
+ * Constants for {@link TAG_EXPOSURE_MODE}
+ */
+ public static interface ExposureMode {
+ public static final short AUTO_EXPOSURE = 0;
+ public static final short MANUAL_EXPOSURE = 1;
+ public static final short AUTO_BRACKET = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_WHITE_BALANCE}
+ */
+ public static interface WhiteBalance {
+ public static final short AUTO = 0;
+ public static final short MANUAL = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
+ */
+ public static interface SceneCapture {
+ public static final short STANDARD = 0;
+ public static final short LANDSCAPE = 1;
+ public static final short PROTRAIT = 2;
+ public static final short NIGHT_SCENE = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
+ */
+ public static interface ComponentsConfiguration {
+ public static final short NOT_EXIST = 0;
+ public static final short Y = 1;
+ public static final short CB = 2;
+ public static final short CR = 3;
+ public static final short R = 4;
+ public static final short G = 5;
+ public static final short B = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_LIGHT_SOURCE}
+ */
+ public static interface LightSource {
+ public static final short UNKNOWN = 0;
+ public static final short DAYLIGHT = 1;
+ public static final short FLUORESCENT = 2;
+ public static final short TUNGSTEN = 3;
+ public static final short FLASH = 4;
+ public static final short FINE_WEATHER = 9;
+ public static final short CLOUDY_WEATHER = 10;
+ public static final short SHADE = 11;
+ public static final short DAYLIGHT_FLUORESCENT = 12;
+ public static final short DAY_WHITE_FLUORESCENT = 13;
+ public static final short COOL_WHITE_FLUORESCENT = 14;
+ public static final short WHITE_FLUORESCENT = 15;
+ public static final short STANDARD_LIGHT_A = 17;
+ public static final short STANDARD_LIGHT_B = 18;
+ public static final short STANDARD_LIGHT_C = 19;
+ public static final short D55 = 20;
+ public static final short D65 = 21;
+ public static final short D75 = 22;
+ public static final short D50 = 23;
+ public static final short ISO_STUDIO_TUNGSTEN = 24;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link TAG_SENSING_METHOD}
+ */
+ public static interface SensingMethod {
+ public static final short NOT_DEFINED = 1;
+ public static final short ONE_CHIP_COLOR = 2;
+ public static final short TWO_CHIP_COLOR = 3;
+ public static final short THREE_CHIP_COLOR = 4;
+ public static final short COLOR_SEQUENTIAL_AREA = 5;
+ public static final short TRILINEAR = 7;
+ public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_FILE_SOURCE}
+ */
+ public static interface FileSource {
+ public static final short DSC = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_SCENE_TYPE}
+ */
+ public static interface SceneType {
+ public static final short DIRECT_PHOTOGRAPHED = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_GAIN_CONTROL}
+ */
+ public static interface GainControl {
+ public static final short NONE = 0;
+ public static final short LOW_UP = 1;
+ public static final short HIGH_UP = 2;
+ public static final short LOW_DOWN = 3;
+ public static final short HIGH_DOWN = 4;
+ }
+
+ /**
+ * Constants for {@link TAG_CONTRAST}
+ */
+ public static interface Contrast {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SATURATION}
+ */
+ public static interface Saturation {
+ public static final short NORMAL = 0;
+ public static final short LOW = 1;
+ public static final short HIGH = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SHARPNESS}
+ */
+ public static interface Sharpness {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SUBJECT_DISTANCE}
+ */
+ public static interface SubjectDistance {
+ public static final short UNKNOWN = 0;
+ public static final short MACRO = 1;
+ public static final short CLOSE_VIEW = 2;
+ public static final short DISTANT_VIEW = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_LATITUDE_REF},
+ * {@link TAG_GPS_DEST_LATITUDE_REF}
+ */
+ public static interface GpsLatitudeRef {
+ public static final String NORTH = "N";
+ public static final String SOUTH = "S";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_LONGITUDE_REF},
+ * {@link TAG_GPS_DEST_LONGITUDE_REF}
+ */
+ public static interface GpsLongitudeRef {
+ public static final String EAST = "E";
+ public static final String WEST = "W";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_ALTITUDE_REF}
+ */
+ public static interface GpsAltitudeRef {
+ public static final short SEA_LEVEL = 0;
+ public static final short SEA_LEVEL_NEGATIVE = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_STATUS}
+ */
+ public static interface GpsStatus {
+ public static final String IN_PROGRESS = "A";
+ public static final String INTEROPERABILITY = "V";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_MEASURE_MODE}
+ */
+ public static interface GpsMeasureMode {
+ public static final String MODE_2_DIMENSIONAL = "2";
+ public static final String MODE_3_DIMENSIONAL = "3";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_SPEED_REF},
+ * {@link TAG_GPS_DEST_DISTANCE_REF}
+ */
+ public static interface GpsSpeedRef {
+ public static final String KILOMETERS = "K";
+ public static final String MILES = "M";
+ public static final String KNOTS = "N";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_TRACK_REF},
+ * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
+ */
+ public static interface GpsTrackRef {
+ public static final String TRUE_DIRECTION = "T";
+ public static final String MAGNETIC_DIRECTION = "M";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_DIFFERENTIAL}
+ */
+ public static interface GpsDifferential {
+ public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+ public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+ }
+
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+ private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
+ public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+ public ExifInterface() {
+ mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ /**
+ * Reads the exif tags from a byte array, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @throws java.io.IOException
+ */
+ public void readExif(byte[] jpeg) throws IOException {
+ readExif(new ByteArrayInputStream(jpeg));
+ }
+
+ /**
+ * Reads the exif tags from an InputStream, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param inStream an InputStream containing a jpeg compressed image.
+ * @throws java.io.IOException
+ */
+ public void readExif(InputStream inStream) throws IOException {
+ if (inStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifData d = null;
+ try {
+ d = new ExifReader(this).read(inStream);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ mData = d;
+ }
+
+ /**
+ * Reads the exif tags from a file, clearing this ExifInterface object's
+ * existing exif tags.
+ *
+ * @param inFileName a string representing the filepath to jpeg file.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public void readExif(String inFileName) throws FileNotFoundException, IOException {
+ if (inFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(new FileInputStream(inFileName));
+ readExif(is);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Sets the exif tags, clearing this ExifInterface object's existing exif
+ * tags.
+ *
+ * @param tags a collection of exif tags to set.
+ */
+ public void setExif(Collection<ExifTag> tags) {
+ clearExif();
+ setTags(tags);
+ }
+
+ /**
+ * Clears this ExifInterface object's existing exif tags.
+ */
+ public void clearExif() {
+ mData = new ExifData(DEFAULT_BYTE_ORDER);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg image,
+ * removing prior exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param exifOutStream an OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws java.io.IOException
+ */
+ public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
+ if (jpeg == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ s.write(jpeg, 0, jpeg.length);
+ s.flush();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed
+ * bitmap, removing prior exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutStream the OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws java.io.IOException
+ */
+ public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+ if (bmap == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ s.flush();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg stream,
+ * removing prior exif tags.
+ *
+ * @param jpegStream an InputStream containing a jpeg compressed image.
+ * @param exifOutStream an OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws java.io.IOException
+ */
+ public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
+ if (jpegStream == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ doExifStreamIO(jpegStream, s);
+ s.flush();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg image,
+ * removing prior exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
+ IOException {
+ if (jpeg == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ s.write(jpeg, 0, jpeg.length);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed
+ * bitmap, removing prior exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
+ IOException {
+ if (bmap == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg stream,
+ * removing prior exif tags.
+ *
+ * @param jpegStream an InputStream containing a jpeg compressed image.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public void writeExif(InputStream jpegStream, String exifOutFileName)
+ throws FileNotFoundException, IOException {
+ if (jpegStream == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ doExifStreamIO(jpegStream, s);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg file, removing
+ * prior exif tags.
+ *
+ * @param jpegFileName a String containing the filepath for a jpeg file.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public void writeExif(String jpegFileName, String exifOutFileName)
+ throws FileNotFoundException, IOException {
+ if (jpegFileName == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ InputStream is = null;
+ try {
+ is = new FileInputStream(jpegFileName);
+ writeExif(is, exifOutFileName);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
+ * ExifInterface object will be added to a jpeg image written to this
+ * stream, removing prior exif tags. Other methods of this ExifInterface
+ * object should not be called until the returned OutputStream has been
+ * closed.
+ *
+ * @param outStream an OutputStream to wrap.
+ * @return an OutputStream that wraps the outStream parameter, and adds exif
+ * metadata. A jpeg image should be written to this stream.
+ */
+ public OutputStream getExifWriterStream(OutputStream outStream) {
+ if (outStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifOutputStream eos = new ExifOutputStream(outStream, this);
+ eos.setExifData(mData);
+ return eos;
+ }
+
+ /**
+ * Returns an OutputStream object that writes to a file. Exif tags in this
+ * ExifInterface object will be added to a jpeg image written to this
+ * stream, removing prior exif tags. Other methods of this ExifInterface
+ * object should not be called until the returned OutputStream has been
+ * closed.
+ *
+ * @param exifOutFileName an String containing a filepath for a jpeg file.
+ * @return an OutputStream that writes to the exifOutFileName file, and adds
+ * exif metadata. A jpeg image should be written to this stream.
+ * @throws java.io.FileNotFoundException
+ */
+ public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
+ if (exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(exifOutFileName);
+ } catch (FileNotFoundException e) {
+ closeSilently(out);
+ throw e;
+ }
+ return getExifWriterStream(out);
+ }
+
+ /**
+ * Attempts to do an in-place rewrite the exif metadata in a file for the
+ * given tags. If tags do not exist or do not have the same size as the
+ * existing exif tags, this method will fail.
+ *
+ * @param filename a String containing a filepath for a jpeg file with exif
+ * tags to rewrite.
+ * @param tags tags that will be written into the jpeg file over existing
+ * tags if possible.
+ * @return true if success, false if could not overwrite. If false, no
+ * changes are made to the file.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ */
+ public boolean rewriteExif(String filename, Collection<ExifTag> tags)
+ throws FileNotFoundException, IOException {
+ RandomAccessFile file = null;
+ InputStream is = null;
+ boolean ret;
+ try {
+ File temp = new File(filename);
+ is = new BufferedInputStream(new FileInputStream(temp));
+
+ // Parse beginning of APP1 in exif to find size of exif header.
+ ExifParser parser = null;
+ try {
+ parser = ExifParser.parse(is, this);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : ", e);
+ }
+ long exifSize = parser.getOffsetToExifEndFromSOF();
+
+ // Free up resources
+ is.close();
+ is = null;
+
+ // Open file for memory mapping.
+ file = new RandomAccessFile(temp, "rw");
+ long fileLength = file.length();
+ if (fileLength < exifSize) {
+ throw new IOException("Filesize changed during operation");
+ }
+
+ // Map only exif header into memory.
+ ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
+
+ // Attempt to overwrite tag values without changing lengths (avoids
+ // file copy).
+ ret = rewriteExif(buf, tags);
+ } catch (IOException e) {
+ closeSilently(file);
+ throw e;
+ } finally {
+ closeSilently(is);
+ }
+ file.close();
+ return ret;
+ }
+
+ /**
+ * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
+ * the given tags. If tags do not exist or do not have the same size as the
+ * existing exif tags, this method will fail.
+ *
+ * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
+ * rewrite.
+ * @param tags tags that will be written into the jpeg ByteBuffer over
+ * existing tags if possible.
+ * @return true if success, false if could not overwrite. If false, no
+ * changes are made to the ByteBuffer.
+ * @throws java.io.IOException
+ */
+ public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
+ ExifModifier mod = null;
+ try {
+ mod = new ExifModifier(buf, this);
+ for (ExifTag t : tags) {
+ mod.modifyTag(t);
+ }
+ return mod.commit();
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ }
+
+ /**
+ * Attempts to do an in-place rewrite of the exif metadata. If this fails,
+ * fall back to overwriting file. This preserves tags that are not being
+ * rewritten.
+ *
+ * @param filename a String containing a filepath for a jpeg file.
+ * @param tags tags that will be written into the jpeg file over existing
+ * tags if possible.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ * @see #rewriteExif
+ */
+ public void forceRewriteExif(String filename, Collection<ExifTag> tags)
+ throws FileNotFoundException,
+ IOException {
+ // Attempt in-place write
+ if (!rewriteExif(filename, tags)) {
+ // Fall back to doing a copy
+ ExifData tempData = mData;
+ mData = new ExifData(DEFAULT_BYTE_ORDER);
+ FileInputStream is = null;
+ ByteArrayOutputStream bytes = null;
+ try {
+ is = new FileInputStream(filename);
+ bytes = new ByteArrayOutputStream();
+ doExifStreamIO(is, bytes);
+ byte[] imageBytes = bytes.toByteArray();
+ readExif(imageBytes);
+ setTags(tags);
+ writeExif(imageBytes, filename);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ } finally {
+ is.close();
+ // Prevent clobbering of mData
+ mData = tempData;
+ }
+ }
+ }
+
+ /**
+ * Attempts to do an in-place rewrite of the exif metadata using the tags in
+ * this ExifInterface object. If this fails, fall back to overwriting file.
+ * This preserves tags that are not being rewritten.
+ *
+ * @param filename a String containing a filepath for a jpeg file.
+ * @throws java.io.FileNotFoundException
+ * @throws java.io.IOException
+ * @see #rewriteExif
+ */
+ public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
+ forceRewriteExif(filename, getAllTags());
+ }
+
+ /**
+ * Get the exif tags in this ExifInterface object or null if none exist.
+ *
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getAllTags() {
+ return mData.getAllTags();
+ }
+
+ /**
+ * Returns a list of ExifTags that share a TID (which can be obtained by
+ * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param tagId a TID as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getTagsForTagId(short tagId) {
+ return mData.getAllTagsForTagId(tagId);
+ }
+
+ /**
+ * Returns a list of ExifTags that share an IFD (which can be obtained by
+ * calling {@link #getTrueIFD} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param ifdId an IFD as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getTagsForIfdId(int ifdId) {
+ return mData.getAllTagsForIfd(ifdId);
+ }
+
+ /**
+ * Gets an ExifTag for an IFD other than the tag's default.
+ *
+ * @see #getTag
+ */
+ public ExifTag getTag(int tagId, int ifdId) {
+ if (!ExifTag.isValidIfd(ifdId)) {
+ return null;
+ }
+ return mData.getTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ /**
+ * Returns the ExifTag in that tag's default IFD for a defined tag constant
+ * or null if none exists.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return an {@link ExifTag} or null if none exists.
+ */
+ public ExifTag getTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTag(tagId, ifdId);
+ }
+
+ /**
+ * Gets a tag value for an IFD other than the tag's default.
+ *
+ * @see #getTagValue
+ */
+ public Object getTagValue(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ return (t == null) ? null : t.getValue();
+ }
+
+ /**
+ * Returns the value of the ExifTag in that tag's default IFD for a defined
+ * tag constant or null if none exists or the value could not be cast into
+ * the return type.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the value of the ExifTag or null if none exists.
+ */
+ public Object getTagValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagValue(tagId, ifdId);
+ }
+
+ /*
+ * Getter methods that are similar to getTagValue. Null is returned if the
+ * tag value cannot be cast into the return type.
+ */
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsString();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagStringValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue(int tagId, int ifdId) {
+ long[] l = getTagLongValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Long(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagLongValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue(int tagId, int ifdId) {
+ int[] l = getTagIntValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Integer(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue(int tagId, int ifdId) {
+ byte[] l = getTagByteValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Byte(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagByteValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue(int tagId, int ifdId) {
+ Rational[] l = getTagRationalValues(tagId, ifdId);
+ if (l == null || l.length == 0) {
+ return null;
+ }
+ return new Rational(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagRationalValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsLongs();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagLongValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsInts();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsBytes();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagByteValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsRationals();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagRationalValues(tagId, ifdId);
+ }
+
+ /**
+ * Checks whether a tag has a defined number of elements.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return true if the tag has a defined number of elements.
+ */
+ public boolean isTagCountDefined(int tagId) {
+ int info = getTagInfo().get(tagId);
+ // No value in info can be zero, as all tags have a non-zero type
+ if (info == 0) {
+ return false;
+ }
+ return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
+ }
+
+ /**
+ * Gets the defined number of elements for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+ * tag or the number of elements is not defined.
+ */
+ public int getDefinedTagCount(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return ExifTag.SIZE_UNDEFINED;
+ }
+ return getComponentCountFromInfo(info);
+ }
+
+ /**
+ * Gets the number of elements for an ExifTag in a given IFD.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD containing the ExifTag to check.
+ * @return the number of elements in the ExifTag, if the tag's size is
+ * undefined this will return the actual number of elements that is
+ * in the ExifTag's value.
+ */
+ public int getActualTagCount(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return 0;
+ }
+ return t.getComponentCount();
+ }
+
+ /**
+ * Gets the default IFD for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+ * definition exists.
+ */
+ public int getDefinedTagDefaultIfd(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == DEFINITION_NULL) {
+ return IFD_NULL;
+ }
+ return getTrueIfd(tagId);
+ }
+
+ /**
+ * Gets the defined type for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the type.
+ * @see ExifTag#getDataType()
+ */
+ public short getDefinedTagType(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return -1;
+ }
+ return getTypeFromInfo(info);
+ }
+
+ /**
+ * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
+ * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
+ * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
+ * <p>
+ * Note: defining tags with these TID's is disallowed.
+ *
+ * @param tag a tag's TID (can be obtained from a defined tag constant with
+ * {@link #getTrueTagKey}).
+ * @return true if the TID is that of an offset tag.
+ */
+ protected static boolean isOffsetTag(short tag) {
+ return sOffsetTags.contains(tag);
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in a given IFD if that IFD is
+ * allowed for the tag. This method will fail anytime the appropriate
+ * {@link ExifTag#setValue} for this tag's datatype would fail.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the tag should be in.
+ * @param val the value of the tag to set.
+ * @return an ExifTag object or null if one could not be constructed.
+ * @see #buildTag
+ */
+ public ExifTag buildTag(int tagId, int ifdId, Object val) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0 || val == null) {
+ return null;
+ }
+ short type = getTypeFromInfo(info);
+ int definedCount = getComponentCountFromInfo(info);
+ boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+ if (!ExifInterface.isIfdAllowed(info, ifdId)) {
+ return null;
+ }
+ ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+ if (!t.setValue(val)) {
+ return null;
+ }
+ return t;
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in the tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the tag's value.
+ * @return an ExifTag object.
+ */
+ public ExifTag buildTag(int tagId, Object val) {
+ int ifdId = getTrueIfd(tagId);
+ return buildTag(tagId, ifdId, val);
+ }
+
+ protected ExifTag buildUninitializedTag(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return null;
+ }
+ short type = getTypeFromInfo(info);
+ int definedCount = getComponentCountFromInfo(info);
+ boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+ int ifdId = getTrueIfd(tagId);
+ ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+ return t;
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists in the given IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the ExifTag is in.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ * @see #setTagValue
+ */
+ public boolean setTagValue(int tagId, int ifdId, Object val) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return false;
+ }
+ return t.setValue(val);
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists it's default IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ */
+ public boolean setTagValue(int tagId, Object val) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return setTagValue(tagId, ifdId, val);
+ }
+
+ /**
+ * Puts an ExifTag into this ExifInterface object's tags, removing a
+ * previous ExifTag with the same TID and IFD. The IFD it is put into will
+ * be the one the tag was created with in {@link #buildTag}.
+ *
+ * @param tag an ExifTag to put into this ExifInterface's tags.
+ * @return the previous ExifTag with the same TID and IFD or null if none
+ * exists.
+ */
+ public ExifTag setTag(ExifTag tag) {
+ return mData.addTag(tag);
+ }
+
+ /**
+ * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+ * previous ExifTags with the same TID and IFDs will be removed.
+ *
+ * @param tags a Collection of ExifTags.
+ * @see #setTag
+ */
+ public void setTags(Collection<ExifTag> tags) {
+ for (ExifTag t : tags) {
+ setTag(t);
+ }
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from the given IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD of the ExifTag to remove.
+ */
+ public void deleteTag(int tagId, int ifdId) {
+ mData.removeTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from that tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void deleteTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ deleteTag(tagId, ifdId);
+ }
+
+ /**
+ * Creates a new tag definition in this ExifInterface object for a given TID
+ * and default IFD. Creating a definition with the same TID and default IFD
+ * as a previous definition will override it.
+ *
+ * @param tagId the TID for the tag.
+ * @param defaultIfd the default IFD for the tag.
+ * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+ * @param defaultComponentCount the number of elements of this tag's type in
+ * the tags value.
+ * @param allowedIfds the IFD's this tag is allowed to be put in.
+ * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+ * {@link #TAG_NULL} if the definition could not be made.
+ */
+ public int setTagDefinition(short tagId, int defaultIfd, short tagType,
+ short defaultComponentCount, int[] allowedIfds) {
+ if (sBannedDefines.contains(tagId)) {
+ return TAG_NULL;
+ }
+ if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+ int tagDef = defineTag(defaultIfd, tagId);
+ if (tagDef == TAG_NULL) {
+ return TAG_NULL;
+ }
+ int[] otherDefs = getTagDefinitionsForTagId(tagId);
+ SparseIntArray infos = getTagInfo();
+ // Make sure defaultIfd is in allowedIfds
+ boolean defaultCheck = false;
+ for (int i : allowedIfds) {
+ if (defaultIfd == i) {
+ defaultCheck = true;
+ }
+ if (!ExifTag.isValidIfd(i)) {
+ return TAG_NULL;
+ }
+ }
+ if (!defaultCheck) {
+ return TAG_NULL;
+ }
+
+ int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
+ // Make sure no identical tags can exist in allowedIfds
+ if (otherDefs != null) {
+ for (int def : otherDefs) {
+ int tagInfo = infos.get(def);
+ int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
+ if ((ifdFlags & allowedFlags) != 0) {
+ return TAG_NULL;
+ }
+ }
+ }
+ getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
+ return tagDef;
+ }
+ return TAG_NULL;
+ }
+
+ protected int getTagDefinition(short tagId, int defaultIfd) {
+ return getTagInfo().get(defineTag(defaultIfd, tagId));
+ }
+
+ protected int[] getTagDefinitionsForTagId(short tagId) {
+ int[] ifds = IfdData.getIfds();
+ int[] defs = new int[ifds.length];
+ int counter = 0;
+ SparseIntArray infos = getTagInfo();
+ for (int i : ifds) {
+ int def = defineTag(i, tagId);
+ if (infos.get(def) != DEFINITION_NULL) {
+ defs[counter++] = def;
+ }
+ }
+ if (counter == 0) {
+ return null;
+ }
+
+ return Arrays.copyOfRange(defs, 0, counter);
+ }
+
+ protected int getTagDefinitionForTag(ExifTag tag) {
+ short type = tag.getDataType();
+ int count = tag.getComponentCount();
+ int ifd = tag.getIfd();
+ return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
+ }
+
+ protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
+ int[] defs = getTagDefinitionsForTagId(tagId);
+ if (defs == null) {
+ return TAG_NULL;
+ }
+ SparseIntArray infos = getTagInfo();
+ int ret = TAG_NULL;
+ for (int i : defs) {
+ int info = infos.get(i);
+ short defType = getTypeFromInfo(info);
+ int defCount = getComponentCountFromInfo(info);
+ int[] defIfds = getAllowedIfdsFromInfo(info);
+ boolean validIfd = false;
+ for (int j : defIfds) {
+ if (j == ifd) {
+ validIfd = true;
+ break;
+ }
+ }
+ if (validIfd && type == defType
+ && (count == defCount || defCount == ExifTag.SIZE_UNDEFINED)) {
+ ret = i;
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a tag definition for given defined tag constant.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void removeTagDefinition(int tagId) {
+ getTagInfo().delete(tagId);
+ }
+
+ /**
+ * Resets tag definitions to the default ones.
+ */
+ public void resetTagDefinitions() {
+ mTagInfo = null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+ *
+ * @return the thumbnail as a bitmap.
+ */
+ public Bitmap getThumbnailBitmap() {
+ if (mData.hasCompressedThumbnail()) {
+ byte[] thumb = mData.getCompressedThumbnail();
+ return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
+ } else if (mData.hasUncompressedStrip()) {
+ // TODO: implement uncompressed
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+ * The bytes may either be an uncompressed strip as specified in the exif
+ * standard or a jpeg compressed image.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnailBytes() {
+ if (mData.hasCompressedThumbnail()) {
+ return mData.getCompressedThumbnail();
+ } else if (mData.hasUncompressedStrip()) {
+ // TODO: implement this
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnail() {
+ return mData.getCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail is compressed.
+ *
+ * @return true if the thumbnail is compressed.
+ */
+ public boolean isThumbnailCompressed() {
+ return mData.hasCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail exists.
+ *
+ * @return true if a compressed thumbnail exists.
+ */
+ public boolean hasThumbnail() {
+ // TODO: add back in uncompressed strip
+ return mData.hasCompressedThumbnail();
+ }
+
+ // TODO: uncompressed thumbnail setters
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a byte array containing a jpeg compressed image.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail(byte[] thumb) {
+ mData.clearThumbnailAndStrips();
+ mData.setCompressedThumbnail(thumb);
+ return true;
+ }
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a bitmap to compress to a jpeg thumbnail.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail(Bitmap thumb) {
+ ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+ if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+ return false;
+ }
+ return setCompressedThumbnail(thumbnail.toByteArray());
+ }
+
+ /**
+ * Clears the compressed thumbnail if it exists.
+ */
+ public void removeCompressedThumbnail() {
+ mData.setCompressedThumbnail(null);
+ }
+
+ // Convenience methods:
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ public String getUserComment() {
+ return mData.getUserComment();
+ }
+
+ /**
+ * Returns the Orientation ExifTag value for a given number of degrees.
+ *
+ * @param degrees the amount an image is rotated in degrees.
+ */
+ public static short getOrientationValueForRotation(int degrees) {
+ degrees %= 360;
+ if (degrees < 0) {
+ degrees += 360;
+ }
+ if (degrees < 90) {
+ return Orientation.TOP_LEFT; // 0 degrees
+ } else if (degrees < 180) {
+ return Orientation.RIGHT_TOP; // 90 degrees cw
+ } else if (degrees < 270) {
+ return Orientation.BOTTOM_LEFT; // 180 degrees
+ } else {
+ return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+ }
+ }
+
+ /**
+ * Returns the rotation degrees corresponding to an ExifTag Orientation
+ * value.
+ *
+ * @param orientation the ExifTag Orientation value.
+ */
+ public static int getRotationForOrientationValue(short orientation) {
+ switch (orientation) {
+ case Orientation.TOP_LEFT:
+ return 0;
+ case Orientation.RIGHT_TOP:
+ return 90;
+ case Orientation.BOTTOM_LEFT:
+ return 180;
+ case Orientation.RIGHT_BOTTOM:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ public static OrientationParams getOrientationParams(int orientation) {
+ OrientationParams params = new OrientationParams();
+ switch (orientation) {
+ case Orientation.TOP_RIGHT: // Flip horizontal
+ params.scaleX = -1;
+ break;
+ case Orientation.BOTTOM_RIGHT: // Flip vertical
+ params.scaleY = -1;
+ break;
+ case Orientation.BOTTOM_LEFT: // Rotate 180
+ params.rotation = 180;
+ break;
+ case Orientation.RIGHT_BOTTOM: // Rotate 270
+ params.rotation = 270;
+ params.invertDimensions = true;
+ break;
+ case Orientation.RIGHT_TOP: // Rotate 90
+ params.rotation = 90;
+ params.invertDimensions = true;
+ break;
+ case Orientation.LEFT_TOP: // Transpose
+ params.rotation = 90;
+ params.scaleX = -1;
+ params.invertDimensions = true;
+ break;
+ case Orientation.LEFT_BOTTOM: // Transverse
+ params.rotation = 270;
+ params.scaleX = -1;
+ params.invertDimensions = true;
+ break;
+ }
+ return params;
+ }
+
+ public static class OrientationParams {
+ public int rotation = 0;
+ public int scaleX = 1;
+ public int scaleY = 1;
+ public boolean invertDimensions = false;
+ }
+
+ /**
+ * Gets the double representation of the GPS latitude or longitude
+ * coordinate.
+ *
+ * @param coordinate an array of 3 Rationals representing the degrees,
+ * minutes, and seconds of the GPS location as defined in the
+ * exif specification.
+ * @param reference a GPS reference reperesented by a String containing "N",
+ * "S", "E", or "W".
+ * @return the GPS coordinate represented as degrees + minutes/60 +
+ * seconds/3600
+ */
+ public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
+ try {
+ double degrees = coordinate[0].toDouble();
+ double minutes = coordinate[1].toDouble();
+ double seconds = coordinate[2].toDouble();
+ double result = degrees + minutes / 60.0 + seconds / 3600.0;
+ if ((reference.equals("S") || reference.equals("W"))) {
+ return -result;
+ }
+ return result;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Gets the GPS latitude and longitude as a pair of doubles from this
+ * ExifInterface object's tags, or null if the necessary tags do not exist.
+ *
+ * @return an array of 2 doubles containing the latitude, and longitude
+ * respectively.
+ * @see #convertLatOrLongToDouble
+ */
+ public double[] getLatLongAsDoubles() {
+ Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
+ String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
+ Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
+ String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
+ if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
+ || latitude.length < 3 || longitude.length < 3) {
+ return null;
+ }
+ double[] latLon = new double[2];
+ latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
+ latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
+ return latLon;
+ }
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+ private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+ private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
+ private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+ private final Calendar mGPSTimeStampCalendar = Calendar
+ .getInstance(TimeZone.getTimeZone("UTC"));
+
+ /**
+ * Creates, formats, and sets the DateTimeStamp tag for one of:
+ * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+ * {@link #TAG_DATE_TIME_ORIGINAL}.
+ *
+ * @param tagId one of the DateTimeStamp tags.
+ * @param timestamp a timestamp to format.
+ * @param timezone a TimeZone object.
+ * @return true if success, false if the tag could not be set.
+ */
+ public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
+ if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
+ || tagId == TAG_DATE_TIME_ORIGINAL) {
+ mDateTimeStampFormat.setTimeZone(timezone);
+ ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Creates and sets all to the GPS tags for a give latitude and longitude.
+ *
+ * @param latitude a GPS latitude coordinate.
+ * @param longitude a GPS longitude coordinate.
+ * @return true if success, false if they could not be created or set.
+ */
+ public boolean addGpsTags(double latitude, double longitude) {
+ ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
+ ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
+ ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
+ latitude >= 0 ? GpsLatitudeRef.NORTH
+ : GpsLatitudeRef.SOUTH);
+ ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
+ longitude >= 0 ? GpsLongitudeRef.EAST
+ : GpsLongitudeRef.WEST);
+ if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+ return false;
+ }
+ setTag(latTag);
+ setTag(longTag);
+ setTag(latRefTag);
+ setTag(longRefTag);
+ return true;
+ }
+
+ /**
+ * Creates and sets the GPS timestamp tag.
+ *
+ * @param timestamp a GPS timestamp.
+ * @return true if success, false if could not be created or set.
+ */
+ public boolean addGpsDateTimeStampTag(long timestamp) {
+ ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ mGPSTimeStampCalendar.setTimeInMillis(timestamp);
+ t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
+ new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
+ new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
+ new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
+ });
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ return true;
+ }
+
+ private static Rational[] toExifLatLong(double value) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs(value);
+ int degrees = (int) value;
+ value = (value - degrees) * 60;
+ int minutes = (int) value;
+ value = (value - minutes) * 6000;
+ int seconds = (int) value;
+ return new Rational[] {
+ new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
+ };
+ }
+
+ private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
+ byte[] buf = new byte[1024];
+ int ret = is.read(buf, 0, 1024);
+ while (ret != -1) {
+ os.write(buf, 0, ret);
+ ret = is.read(buf, 0, 1024);
+ }
+ }
+
+ protected static void closeSilently(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Throwable e) {
+ // ignored
+ }
+ }
+ }
+
+ private SparseIntArray mTagInfo = null;
+
+ protected SparseIntArray getTagInfo() {
+ if (mTagInfo == null) {
+ mTagInfo = new SparseIntArray();
+ initTagInfo();
+ }
+ return mTagInfo;
+ }
+
+ private void initTagInfo() {
+ /**
+ * We put tag information in a 4-bytes integer. The first byte a bitmask
+ * representing the allowed IFDs of the tag, the second byte is the data
+ * type, and the last two byte are a short value indicating the default
+ * component count of this tag.
+ */
+ // IFD0 tags
+ int[] ifdAllowedIfds = {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
+ };
+ int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_MAKE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_COMPRESSION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
+ | 1);
+ mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+ mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_MAKE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_MODEL,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SOFTWARE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ARTIST,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IFD,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // IFD1 tags
+ int[] ifd1AllowedIfds = {
+ IfdId.TYPE_IFD_1
+ };
+ int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // Exif tags
+ int[] exifAllowedIfds = {
+ IfdId.TYPE_IFD_EXIF
+ };
+ int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_F_NUMBER,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_OECF,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_METERING_MODE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FLASH,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_CONTRAST,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SATURATION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SHARPNESS,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
+ | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // GPS tag
+ int[] gpsAllowedIfds = {
+ IfdId.TYPE_IFD_GPS
+ };
+ int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
+ gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
+ gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DOP,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
+ gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
+ gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
+ mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
+ // Interoperability tag
+ int[] interopAllowedIfds = {
+ IfdId.TYPE_IFD_INTEROPERABILITY
+ };
+ int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
+ mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
+ | ExifTag.SIZE_UNDEFINED);
+ }
+
+ protected static int getAllowedIfdFlagsFromInfo(int info) {
+ return info >>> 24;
+ }
+
+ protected static int[] getAllowedIfdsFromInfo(int info) {
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ int[] ifds = IfdData.getIfds();
+ ArrayList<Integer> l = new ArrayList<Integer>();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ int flag = (ifdFlags >> i) & 1;
+ if (flag == 1) {
+ l.add(ifds[i]);
+ }
+ }
+ if (l.size() <= 0) {
+ return null;
+ }
+ int[] ret = new int[l.size()];
+ int j = 0;
+ for (int i : l) {
+ ret[j++] = i;
+ }
+ return ret;
+ }
+
+ protected static boolean isIfdAllowed(int info, int ifd) {
+ int[] ifds = IfdData.getIfds();
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ for (int i = 0; i < ifds.length; i++) {
+ if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+ if (allowedIfds == null || allowedIfds.length == 0) {
+ return 0;
+ }
+ int flags = 0;
+ int[] ifds = IfdData.getIfds();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ for (int j : allowedIfds) {
+ if (ifds[i] == j) {
+ flags |= 1 << i;
+ break;
+ }
+ }
+ }
+ return flags;
+ }
+
+ protected static short getTypeFromInfo(int info) {
+ return (short) ((info >> 16) & 0x0ff);
+ }
+
+ protected static int getComponentCountFromInfo(int info) {
+ return info & 0x0ffff;
+ }
+
+}
diff --git a/src/com/android/messaging/util/exif/ExifInvalidFormatException.java b/src/com/android/messaging/util/exif/ExifInvalidFormatException.java
new file mode 100644
index 0000000..a38f8a3
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+public class ExifInvalidFormatException extends Exception {
+ public ExifInvalidFormatException(String meg) {
+ super(meg);
+ }
+}
diff --git a/src/com/android/messaging/util/exif/ExifModifier.java b/src/com/android/messaging/util/exif/ExifModifier.java
new file mode 100644
index 0000000..274022c
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifModifier.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import android.util.Log;
+import com.android.messaging.util.LogUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+class ExifModifier {
+ public static final String TAG = LogUtil.BUGLE_TAG;
+ public static final boolean DEBUG = false;
+ private final ByteBuffer mByteBuffer;
+ private final ExifData mTagToModified;
+ private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
+ private final ExifInterface mInterface;
+ private int mOffsetBase;
+
+ private static class TagOffset {
+ final int mOffset;
+ final ExifTag mTag;
+
+ TagOffset(ExifTag tag, int offset) {
+ mTag = tag;
+ mOffset = offset;
+ }
+ }
+
+ protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
+ ExifInvalidFormatException {
+ mByteBuffer = byteBuffer;
+ mOffsetBase = byteBuffer.position();
+ mInterface = iRef;
+ InputStream is = null;
+ try {
+ is = new ByteBufferInputStream(byteBuffer);
+ // Do not require any IFD;
+ ExifParser parser = ExifParser.parse(is, mInterface);
+ mTagToModified = new ExifData(parser.getByteOrder());
+ mOffsetBase += parser.getTiffStartPosition();
+ mByteBuffer.position(0);
+ } finally {
+ ExifInterface.closeSilently(is);
+ }
+ }
+
+ protected ByteOrder getByteOrder() {
+ return mTagToModified.getByteOrder();
+ }
+
+ protected boolean commit() throws IOException, ExifInvalidFormatException {
+ InputStream is = null;
+ try {
+ is = new ByteBufferInputStream(mByteBuffer);
+ int flag = 0;
+ IfdData[] ifdDatas = new IfdData[] {
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
+ mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
+ };
+
+ if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
+ flag |= ExifParser.OPTION_IFD_0;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
+ flag |= ExifParser.OPTION_IFD_1;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
+ flag |= ExifParser.OPTION_IFD_EXIF;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
+ flag |= ExifParser.OPTION_IFD_GPS;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
+ flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
+ }
+
+ ExifParser parser = ExifParser.parse(is, flag, mInterface);
+ int event = parser.next();
+ IfdData currIfd = null;
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ currIfd = ifdDatas[parser.getCurrentIfd()];
+ if (currIfd == null) {
+ parser.skipRemainingTagsInCurrentIfd();
+ }
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag oldTag = parser.getTag();
+ ExifTag newTag = currIfd.getTag(oldTag.getTagId());
+ if (newTag != null) {
+ if (newTag.getComponentCount() != oldTag.getComponentCount()
+ || newTag.getDataType() != oldTag.getDataType()) {
+ return false;
+ } else {
+ mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
+ currIfd.removeTag(oldTag.getTagId());
+ if (currIfd.getTagCount() == 0) {
+ parser.skipRemainingTagsInCurrentIfd();
+ }
+ }
+ }
+ break;
+ }
+ event = parser.next();
+ }
+ for (IfdData ifd : ifdDatas) {
+ if (ifd != null && ifd.getTagCount() > 0) {
+ return false;
+ }
+ }
+ modify();
+ } finally {
+ ExifInterface.closeSilently(is);
+ }
+ return true;
+ }
+
+ private void modify() {
+ mByteBuffer.order(getByteOrder());
+ for (TagOffset tagOffset : mTagOffsets) {
+ writeTagValue(tagOffset.mTag, tagOffset.mOffset);
+ }
+ }
+
+ private void writeTagValue(ExifTag tag, int offset) {
+ if (DEBUG) {
+ Log.v(TAG, "modifying tag to: \n" + tag.toString());
+ Log.v(TAG, "at offset: " + offset);
+ }
+ mByteBuffer.position(offset + mOffsetBase);
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ mByteBuffer.put(buf);
+ } else {
+ mByteBuffer.put(buf);
+ mByteBuffer.put((byte) 0);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ mByteBuffer.putInt((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ Rational v = tag.getRational(i);
+ mByteBuffer.putInt((int) v.getNumerator());
+ mByteBuffer.putInt((int) v.getDenominator());
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ mByteBuffer.put(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ mByteBuffer.putShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+
+ public void modifyTag(ExifTag tag) {
+ mTagToModified.addTag(tag);
+ }
+}
diff --git a/src/com/android/messaging/util/exif/ExifOutputStream.java b/src/com/android/messaging/util/exif/ExifOutputStream.java
new file mode 100644
index 0000000..2016da4
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifOutputStream.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import android.util.Log;
+import com.android.messaging.util.LogUtil;
+
+import java.io.BufferedOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+/**
+ * This class provides a way to replace the Exif header of a JPEG image.
+ * <p>
+ * Below is an example of writing EXIF data into a file
+ *
+ * <pre>
+ * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
+ * OutputStream os = null;
+ * try {
+ * os = new FileOutputStream(path);
+ * ExifOutputStream eos = new ExifOutputStream(os);
+ * // Set the exif header
+ * eos.setExifData(exif);
+ * // Write the original jpeg out, the header will be add into the file.
+ * eos.write(jpeg);
+ * } catch (FileNotFoundException e) {
+ * e.printStackTrace();
+ * } catch (IOException e) {
+ * e.printStackTrace();
+ * } finally {
+ * if (os != null) {
+ * try {
+ * os.close();
+ * } catch (IOException e) {
+ * e.printStackTrace();
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ */
+class ExifOutputStream extends FilterOutputStream {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ private static final boolean DEBUG = false;
+ private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+ private static final int STATE_SOI = 0;
+ private static final int STATE_FRAME_HEADER = 1;
+ private static final int STATE_JPEG_DATA = 2;
+
+ private static final int EXIF_HEADER = 0x45786966;
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final short TAG_SIZE = 12;
+ private static final short TIFF_HEADER_SIZE = 8;
+ private static final int MAX_EXIF_SIZE = 65535;
+
+ private ExifData mExifData;
+ private int mState = STATE_SOI;
+ private int mByteToSkip;
+ private int mByteToCopy;
+ private final byte[] mSingleByteArray = new byte[1];
+ private final ByteBuffer mBuffer = ByteBuffer.allocate(4);
+ private final ExifInterface mInterface;
+
+ protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
+ super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+ mInterface = iRef;
+ }
+
+ /**
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
+ */
+ protected void setExifData(ExifData exifData) {
+ mExifData = exifData;
+ }
+
+ /**
+ * Gets the Exif header to be written into the JPEF file.
+ */
+ protected ExifData getExifData() {
+ return mExifData;
+ }
+
+ private int requestByteToBuffer(int requestByteCount, byte[] buffer
+ , int offset, int length) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put(buffer, offset, byteToRead);
+ return byteToRead;
+ }
+
+ /**
+ * Writes the image out. The input data should be a valid JPEG format. After
+ * writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+ && length > 0) {
+ if (mByteToSkip > 0) {
+ int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+ length -= byteToProcess;
+ mByteToSkip -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (mByteToCopy > 0) {
+ int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+ out.write(buffer, offset, byteToProcess);
+ length -= byteToProcess;
+ mByteToCopy -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (length == 0) {
+ return;
+ }
+ switch (mState) {
+ case STATE_SOI:
+ int byteRead = requestByteToBuffer(2, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 2) {
+ return;
+ }
+ mBuffer.rewind();
+ if (mBuffer.getShort() != JpegHeader.SOI) {
+ throw new IOException("Not a valid jpeg image, cannot write exif");
+ }
+ out.write(mBuffer.array(), 0, 2);
+ mState = STATE_FRAME_HEADER;
+ mBuffer.rewind();
+ writeExifData();
+ break;
+ case STATE_FRAME_HEADER:
+ // We ignore the APP1 segment and copy all other segments
+ // until SOF tag.
+ byteRead = requestByteToBuffer(4, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ // Check if this image data doesn't contain SOF.
+ if (mBuffer.position() == 2) {
+ short tag = mBuffer.getShort();
+ if (tag == JpegHeader.EOI) {
+ out.write(mBuffer.array(), 0, 2);
+ mBuffer.rewind();
+ }
+ }
+ if (mBuffer.position() < 4) {
+ return;
+ }
+ mBuffer.rewind();
+ short marker = mBuffer.getShort();
+ if (marker == JpegHeader.APP1) {
+ mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
+ mState = STATE_JPEG_DATA;
+ } else if (!JpegHeader.isSofMarker(marker)) {
+ out.write(mBuffer.array(), 0, 4);
+ mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
+ } else {
+ out.write(mBuffer.array(), 0, 4);
+ mState = STATE_JPEG_DATA;
+ }
+ mBuffer.rewind();
+ }
+ }
+ if (length > 0) {
+ out.write(buffer, offset, length);
+ }
+ }
+
+ /**
+ * Writes the one bytes out. The input data should be a valid JPEG format.
+ * After writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(int oneByte) throws IOException {
+ mSingleByteArray[0] = (byte) (0xff & oneByte);
+ write(mSingleByteArray);
+ }
+
+ /**
+ * Equivalent to calling write(buffer, 0, buffer.length).
+ */
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ write(buffer, 0, buffer.length);
+ }
+
+ private void writeExifData() throws IOException {
+ if (mExifData == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Writing exif data...");
+ }
+ ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
+ createRequiredIfdAndTag();
+ int exifSize = calculateAllOffset();
+ if (exifSize + 8 > MAX_EXIF_SIZE) {
+ throw new IOException("Exif header is too large (>64Kb)");
+ }
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ dataOutputStream.writeShort(JpegHeader.APP1);
+ dataOutputStream.writeShort((short) (exifSize + 8));
+ dataOutputStream.writeInt(EXIF_HEADER);
+ dataOutputStream.writeShort((short) 0x0000);
+ if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+ }
+ dataOutputStream.setByteOrder(mExifData.getByteOrder());
+ dataOutputStream.writeShort(TIFF_HEADER);
+ dataOutputStream.writeInt(8);
+ writeAllTags(dataOutputStream);
+ writeThumbnail(dataOutputStream);
+ for (ExifTag t : nullTags) {
+ mExifData.addTag(t);
+ }
+ }
+
+ private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
+ ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
+ if (data.getAllTags() == null) {
+ return nullTags;
+ }
+ for (ExifTag t : data.getAllTags()) {
+ if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
+ data.removeTag(t.getTagId(), t.getIfd());
+ nullTags.add(t);
+ }
+ }
+ return nullTags;
+ }
+
+ private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+ if (mExifData.hasCompressedThumbnail()) {
+ dataOutputStream.write(mExifData.getCompressedThumbnail());
+ } else if (mExifData.hasUncompressedStrip()) {
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ dataOutputStream.write(mExifData.getStrip(i));
+ }
+ }
+ }
+
+ private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+ IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interoperabilityIfd != null) {
+ writeIfd(interoperabilityIfd, dataOutputStream);
+ }
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ writeIfd(gpsIfd, dataOutputStream);
+ }
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+ }
+ }
+
+ private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ ExifTag[] tags = ifd.getAllTags();
+ dataOutputStream.writeShort((short) tags.length);
+ for (ExifTag tag : tags) {
+ dataOutputStream.writeShort(tag.getTagId());
+ dataOutputStream.writeShort(tag.getDataType());
+ dataOutputStream.writeInt(tag.getComponentCount());
+ if (DEBUG) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
+ if (tag.getDataSize() > 4) {
+ dataOutputStream.writeInt(tag.getOffset());
+ } else {
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
+ for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+ dataOutputStream.write(0);
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+ for (ExifTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
+ }
+ }
+ }
+
+ private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+ offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+ ExifTag[] tags = ifd.getAllTags();
+ for (ExifTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ tag.setOffset(offset);
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ private void createRequiredIfdAndTag() throws IOException {
+ // IFD0 is required for all file
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ if (ifd0 == null) {
+ ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+ mExifData.addIfdData(ifd0);
+ }
+ ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+ if (exifOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_EXIF_IFD);
+ }
+ ifd0.setTag(exifOffsetTag);
+
+ // Exif IFD is required for all files.
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ if (exifIfd == null) {
+ exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+ mExifData.addIfdData(exifIfd);
+ }
+
+ // GPS IFD
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+ if (gpsOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_GPS_IFD);
+ }
+ ifd0.setTag(gpsOffsetTag);
+ }
+
+ // Interoperability IFD
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ ExifTag interOffsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ if (interOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_INTEROPERABILITY_IFD);
+ }
+ exifIfd.setTag(interOffsetTag);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+
+ ExifTag offsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ }
+
+ ifd1.setTag(offsetTag);
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ }
+
+ lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+ ifd1.setTag(lengthTag);
+
+ // Get rid of tags for uncompressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ } else if (mExifData.hasUncompressedStrip()) {
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+ int stripCount = mExifData.getStripCount();
+ ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_OFFSETS);
+ }
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ }
+ long[] lengths = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ lengths[i] = mExifData.getStrip(i).length;
+ }
+ lengthTag.setValue(lengths);
+ ifd1.setTag(offsetTag);
+ ifd1.setTag(lengthTag);
+ // Get rid of tags for compressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ } else if (ifd1 != null) {
+ // Get rid of offset and length tags if there is no thumbnail.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ }
+ }
+
+ private int calculateAllOffset() {
+ int offset = TIFF_HEADER_SIZE;
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ offset = calculateOffsetOfIfd(ifd0, offset);
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
+
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ offset = calculateOffsetOfIfd(exifIfd, offset);
+
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+ .setValue(offset);
+ offset = calculateOffsetOfIfd(interIfd, offset);
+ }
+
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
+ offset = calculateOffsetOfIfd(gpsIfd, offset);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ ifd0.setOffsetToNextIfd(offset);
+ offset = calculateOffsetOfIfd(ifd1, offset);
+ }
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+ .setValue(offset);
+ offset += mExifData.getCompressedThumbnail().length;
+ } else if (mExifData.hasUncompressedStrip()) {
+ int stripCount = mExifData.getStripCount();
+ long[] offsets = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ offsets[i] = offset;
+ offset += mExifData.getStrip(i).length;
+ }
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+ offsets);
+ }
+ return offset;
+ }
+
+ static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ dataOutputStream.write(buf);
+ } else {
+ dataOutputStream.write(buf);
+ dataOutputStream.write(0);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/exif/ExifParser.java b/src/com/android/messaging/util/exif/ExifParser.java
new file mode 100644
index 0000000..4b6cf68
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifParser.java
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import android.util.Log;
+import com.android.messaging.util.LogUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * This class provides a low-level EXIF parsing API. Given a JPEG format
+ * InputStream, the caller can request which IFD's to read via
+ * {@link #parse(java.io.InputStream, int)} with given options.
+ * <p>
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
+ * parser.
+ *
+ * <pre>
+ * void parse() {
+ * ExifParser parser = ExifParser.parse(mImageInputStream,
+ * ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ * int event = parser.next();
+ * while (event != ExifParser.EVENT_END) {
+ * switch (event) {
+ * case ExifParser.EVENT_START_OF_IFD:
+ * break;
+ * case ExifParser.EVENT_NEW_TAG:
+ * ExifTag tag = parser.getTag();
+ * if (!tag.hasValue()) {
+ * parser.registerForTagValue(tag);
+ * } else {
+ * processTag(tag);
+ * }
+ * break;
+ * case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ * tag = parser.getTag();
+ * if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ * processTag(tag);
+ * }
+ * break;
+ * }
+ * event = parser.next();
+ * }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ * // process the tag as you like.
+ * }
+ * </pre>
+ */
+public class ExifParser {
+ private static final boolean LOGV = false;
+ private static final String TAG = LogUtil.BUGLE_TAG;
+ /**
+ * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
+ * know which IFD we are in.
+ */
+ public static final int EVENT_START_OF_IFD = 0;
+ /**
+ * When the parser reaches a new tag. Call {@link #getTag()}to get the
+ * corresponding tag.
+ */
+ public static final int EVENT_NEW_TAG = 1;
+ /**
+ * When the parser reaches the value area of tag that is registered by
+ * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+ * to get the corresponding tag.
+ */
+ public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
+
+ /**
+ * When the parser reaches the compressed image area.
+ */
+ public static final int EVENT_COMPRESSED_IMAGE = 3;
+ /**
+ * When the parser reaches the uncompressed image strip. Call
+ * {@link #getStripIndex()} to get the index of the strip.
+ *
+ * @see #getStripIndex()
+ * @see #getStripCount()
+ */
+ public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+ /**
+ * When there is nothing more to parse.
+ */
+ public static final int EVENT_END = 5;
+
+ /**
+ * Option bit to request to parse IFD0.
+ */
+ public static final int OPTION_IFD_0 = 1 << 0;
+ /**
+ * Option bit to request to parse IFD1.
+ */
+ public static final int OPTION_IFD_1 = 1 << 1;
+ /**
+ * Option bit to request to parse Exif-IFD.
+ */
+ public static final int OPTION_IFD_EXIF = 1 << 2;
+ /**
+ * Option bit to request to parse GPS-IFD.
+ */
+ public static final int OPTION_IFD_GPS = 1 << 3;
+ /**
+ * Option bit to request to parse Interoperability-IFD.
+ */
+ public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+ /**
+ * Option bit to request to parse thumbnail.
+ */
+ public static final int OPTION_THUMBNAIL = 1 << 5;
+
+ protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+ protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+
+ // TIFF header
+ protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+ protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+ protected static final short TIFF_HEADER_TAIL = 0x002A;
+
+ protected static final int TAG_SIZE = 12;
+ protected static final int OFFSET_SIZE = 2;
+
+ private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ protected static final int DEFAULT_IFD0_OFFSET = 8;
+
+ private final CountedDataInputStream mTiffStream;
+ private final int mOptions;
+ private int mIfdStartOffset = 0;
+ private int mNumOfTagInIfd = 0;
+ private int mIfdType;
+ private ExifTag mTag;
+ private ImageEvent mImageEvent;
+ private int mStripCount;
+ private ExifTag mStripSizeTag;
+ private ExifTag mJpegSizeTag;
+ private boolean mNeedToParseOffsetsInCurrentIfd;
+ private boolean mContainExifData = false;
+ private int mApp1End;
+ private int mOffsetToApp1EndFromSOF = 0;
+ private byte[] mDataAboveIfd0;
+ private int mIfd0Position;
+ private int mTiffStartPosition;
+ private final ExifInterface mInterface;
+
+ private static final short TAG_EXIF_IFD = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
+ private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
+ private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ private static final short TAG_STRIP_OFFSETS = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
+ private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+
+ private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
+
+ private boolean isIfdRequested(int ifdType) {
+ switch (ifdType) {
+ case IfdId.TYPE_IFD_0:
+ return (mOptions & OPTION_IFD_0) != 0;
+ case IfdId.TYPE_IFD_1:
+ return (mOptions & OPTION_IFD_1) != 0;
+ case IfdId.TYPE_IFD_EXIF:
+ return (mOptions & OPTION_IFD_EXIF) != 0;
+ case IfdId.TYPE_IFD_GPS:
+ return (mOptions & OPTION_IFD_GPS) != 0;
+ case IfdId.TYPE_IFD_INTEROPERABILITY:
+ return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
+ }
+ return false;
+ }
+
+ private boolean isThumbnailRequested() {
+ return (mOptions & OPTION_THUMBNAIL) != 0;
+ }
+
+ private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
+ throws IOException, ExifInvalidFormatException {
+ if (inputStream == null) {
+ throw new IOException("Null argument inputStream to ExifParser");
+ }
+ if (LOGV) {
+ Log.v(TAG, "Reading exif...");
+ }
+ mInterface = iRef;
+ mContainExifData = seekTiffData(inputStream);
+ mTiffStream = new CountedDataInputStream(inputStream);
+ mOptions = options;
+ if (!mContainExifData) {
+ return;
+ }
+
+ parseTiffHeader();
+ long offset = mTiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException("Invalid offset " + offset);
+ }
+ mIfd0Position = (int) offset;
+ mIfdType = IfdId.TYPE_IFD_0;
+ if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
+ registerIfd(IfdId.TYPE_IFD_0, offset);
+ if (offset != DEFAULT_IFD0_OFFSET) {
+ mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+ read(mDataAboveIfd0);
+ }
+ }
+ }
+
+ /**
+ * Parses the the given InputStream with the given options
+ *
+ * @exception java.io.IOException
+ * @exception ExifInvalidFormatException
+ */
+ protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
+ throws IOException, ExifInvalidFormatException {
+ return new ExifParser(inputStream, options, iRef);
+ }
+
+ /**
+ * Parses the the given InputStream with default options; that is, every IFD
+ * and thumbnaill will be parsed.
+ *
+ * @exception java.io.IOException
+ * @exception ExifInvalidFormatException
+ * @see #parse(java.io.InputStream, int)
+ */
+ protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
+ throws IOException, ExifInvalidFormatException {
+ return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
+ | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
+ | OPTION_THUMBNAIL, iRef);
+ }
+
+ /**
+ * Moves the parser forward and returns the next parsing event
+ *
+ * @exception java.io.IOException
+ * @exception ExifInvalidFormatException
+ * @see #EVENT_START_OF_IFD
+ * @see #EVENT_NEW_TAG
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ * @see #EVENT_COMPRESSED_IMAGE
+ * @see #EVENT_UNCOMPRESSED_STRIP
+ * @see #EVENT_END
+ */
+ protected int next() throws IOException, ExifInvalidFormatException {
+ if (!mContainExifData) {
+ return EVENT_END;
+ }
+ int offset = mTiffStream.getReadByteCount();
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ if (offset < endOfTags) {
+ mTag = readTag();
+ if (mTag == null) {
+ return next();
+ }
+ if (mNeedToParseOffsetsInCurrentIfd) {
+ checkOffsetOrImageTag(mTag);
+ }
+ return EVENT_NEW_TAG;
+ } else if (offset == endOfTags) {
+ // There is a link to ifd1 at the end of ifd0
+ if (mIfdType == IfdId.TYPE_IFD_0) {
+ long ifdOffset = readUnsignedLong();
+ if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
+ if (ifdOffset != 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+ }
+ }
+ } else {
+ int offsetSize = 4;
+ // Some camera models use invalid length of the offset
+ if (mCorrespondingEvent.size() > 0) {
+ offsetSize = mCorrespondingEvent.firstEntry().getKey() -
+ mTiffStream.getReadByteCount();
+ }
+ if (offsetSize < 4) {
+ Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
+ } else {
+ long ifdOffset = readUnsignedLong();
+ if (ifdOffset != 0) {
+ Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
+ }
+ }
+ }
+ }
+ while (mCorrespondingEvent.size() != 0) {
+ Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+ Object event = entry.getValue();
+ try {
+ skipTo(entry.getKey());
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
+ " for " + event.getClass().getName() + ", the file may be broken.");
+ continue;
+ }
+ if (event instanceof IfdEvent) {
+ mIfdType = ((IfdEvent) event).ifd;
+ mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+ mIfdStartOffset = entry.getKey();
+
+ if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
+ Log.w(TAG, "Invalid size of IFD " + mIfdType);
+ return EVENT_END;
+ }
+
+ mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+ if (((IfdEvent) event).isRequested) {
+ return EVENT_START_OF_IFD;
+ } else {
+ skipRemainingTagsInCurrentIfd();
+ }
+ } else if (event instanceof ImageEvent) {
+ mImageEvent = (ImageEvent) event;
+ return mImageEvent.type;
+ } else {
+ ExifTagEvent tagEvent = (ExifTagEvent) event;
+ mTag = tagEvent.tag;
+ if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ readFullTagValue(mTag);
+ checkOffsetOrImageTag(mTag);
+ }
+ if (tagEvent.isRequested) {
+ return EVENT_VALUE_OF_REGISTERED_TAG;
+ }
+ }
+ }
+ return EVENT_END;
+ }
+
+ /**
+ * Skips the tags area of current IFD, if the parser is not in the tag area,
+ * nothing will happen.
+ *
+ * @throws java.io.IOException
+ * @throws ExifInvalidFormatException
+ */
+ protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ int offset = mTiffStream.getReadByteCount();
+ if (offset > endOfTags) {
+ return;
+ }
+ if (mNeedToParseOffsetsInCurrentIfd) {
+ while (offset < endOfTags) {
+ mTag = readTag();
+ offset += TAG_SIZE;
+ if (mTag == null) {
+ continue;
+ }
+ checkOffsetOrImageTag(mTag);
+ }
+ } else {
+ skipTo(endOfTags);
+ }
+ long ifdOffset = readUnsignedLong();
+ // For ifd0, there is a link to ifd1 in the end of all tags
+ if (mIfdType == IfdId.TYPE_IFD_0
+ && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
+ if (ifdOffset > 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
+ }
+ }
+ }
+
+ private boolean needToParseOffsetsInCurrentIfd() {
+ switch (mIfdType) {
+ case IfdId.TYPE_IFD_0:
+ return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
+ || isIfdRequested(IfdId.TYPE_IFD_1);
+ case IfdId.TYPE_IFD_1:
+ return isThumbnailRequested();
+ case IfdId.TYPE_IFD_EXIF:
+ // The offset to interoperability IFD is located in Exif IFD
+ return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * If {@link #next()} return {@link #EVENT_NEW_TAG} or
+ * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
+ * corresponding tag.
+ * <p>
+ * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
+ * of the value is greater than 4 bytes. One should call
+ * {@link ExifTag#hasValue()} to check if the tag contains value. If there
+ * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
+ * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+ * pointed by the offset.
+ * <p>
+ * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
+ * tag will have already been read except for tags of undefined type. For
+ * tags of undefined type, call one of the read methods to get the value.
+ *
+ * @see #registerForTagValue(ExifTag)
+ * @see #read(byte[])
+ * @see #read(byte[], int, int)
+ * @see #readLong()
+ * @see #readRational()
+ * @see #readString(int)
+ * @see #readString(int, java.nio.charset.Charset)
+ */
+ protected ExifTag getTag() {
+ return mTag;
+ }
+
+ /**
+ * Gets number of tags in the current IFD area.
+ */
+ protected int getTagCountInCurrentIfd() {
+ return mNumOfTagInIfd;
+ }
+
+ /**
+ * Gets the ID of current IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ * @see IfdId#TYPE_IFD_EXIF
+ */
+ protected int getCurrentIfd() {
+ return mIfdType;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the index of this strip.
+ *
+ * @see #getStripCount()
+ */
+ protected int getStripIndex() {
+ return mImageEvent.stripIndex;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the number of strip data.
+ *
+ * @see #getStripIndex()
+ */
+ protected int getStripCount() {
+ return mStripCount;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the strip size.
+ */
+ protected int getStripSize() {
+ if (mStripSizeTag == null) {
+ return 0;
+ }
+ return (int) mStripSizeTag.getValueAt(0);
+ }
+
+ /**
+ * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+ * the image data size.
+ */
+ protected int getCompressedImageSize() {
+ if (mJpegSizeTag == null) {
+ return 0;
+ }
+ return (int) mJpegSizeTag.getValueAt(0);
+ }
+
+ private void skipTo(int offset) throws IOException {
+ mTiffStream.skipTo(offset);
+ while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+ mCorrespondingEvent.pollFirstEntry();
+ }
+ }
+
+ /**
+ * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
+ * not contain the value if the size of the value is greater than 4 bytes.
+ * When the value is not available here, call this method so that the parser
+ * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+ * where the value is located.
+ *
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ */
+ protected void registerForTagValue(ExifTag tag) {
+ if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+ }
+ }
+
+ private void registerIfd(int ifdType, long offset) {
+ // Cast unsigned int to int since the offset is always smaller
+ // than the size of APP1 (65536)
+ mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
+ }
+
+ private void registerCompressedImage(long offset) {
+ mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
+ }
+
+ private void registerUncompressedStrip(int stripIndex, long offset) {
+ mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
+ , stripIndex));
+ }
+
+ private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+ short tagId = mTiffStream.readShort();
+ short dataFormat = mTiffStream.readShort();
+ long numOfComp = mTiffStream.readUnsignedInt();
+ if (numOfComp > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "Number of component is larger then Integer.MAX_VALUE");
+ }
+ // Some invalid image file contains invalid data type. Ignore those tags
+ if (!ExifTag.isValidType(dataFormat)) {
+ Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
+ mTiffStream.skip(4);
+ return null;
+ }
+ // TODO: handle numOfComp overflow
+ ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
+ ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
+ int dataSize = tag.getDataSize();
+ if (dataSize > 4) {
+ long offset = mTiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "offset is larger then Integer.MAX_VALUE");
+ }
+ // Some invalid images put some undefined data before IFD0.
+ // Read the data here.
+ if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
+ byte[] buf = new byte[(int) numOfComp];
+ System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
+ buf, 0, (int) numOfComp);
+ tag.setValue(buf);
+ } else {
+ tag.setOffset((int) offset);
+ }
+ } else {
+ boolean defCount = tag.hasDefinedCount();
+ // Set defined count to 0 so we can add \0 to non-terminated strings
+ tag.setHasDefinedCount(false);
+ // Read value
+ readFullTagValue(tag);
+ tag.setHasDefinedCount(defCount);
+ mTiffStream.skip(4 - dataSize);
+ // Set the offset to the position of value.
+ tag.setOffset(mTiffStream.getReadByteCount() - 4);
+ }
+ return tag;
+ }
+
+ /**
+ * Check the tag, if the tag is one of the offset tag that points to the IFD
+ * or image the caller is interested in, register the IFD or image.
+ */
+ private void checkOffsetOrImageTag(ExifTag tag) {
+ // Some invalid formattd image contains tag with 0 size.
+ if (tag.getComponentCount() == 0) {
+ return;
+ }
+ short tid = tag.getTagId();
+ int ifd = tag.getIfd();
+ if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+ registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_INTEROPERABILITY_IFD
+ && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
+ && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
+ if (isThumbnailRequested()) {
+ registerCompressedImage(tag.getValueAt(0));
+ }
+ } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
+ && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
+ if (isThumbnailRequested()) {
+ mJpegSizeTag = tag;
+ }
+ } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
+ if (isThumbnailRequested()) {
+ if (tag.hasValue()) {
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+ registerUncompressedStrip(i, tag.getValueAt(i));
+ } else {
+ registerUncompressedStrip(i, tag.getValueAt(i));
+ }
+ }
+ } else {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
+ }
+ }
+ } else if (tid == TAG_STRIP_BYTE_COUNTS
+ && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
+ && isThumbnailRequested() && tag.hasValue()) {
+ mStripSizeTag = tag;
+ }
+ }
+
+ private boolean checkAllowed(int ifd, int tagId) {
+ int info = mInterface.getTagInfo().get(tagId);
+ if (info == ExifInterface.DEFINITION_NULL) {
+ return false;
+ }
+ return ExifInterface.isIfdAllowed(info, ifd);
+ }
+
+ protected void readFullTagValue(ExifTag tag) throws IOException {
+ // Some invalid images contains tags with wrong size, check it here
+ short type = tag.getDataType();
+ if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+ type == ExifTag.TYPE_UNSIGNED_BYTE) {
+ int size = tag.getComponentCount();
+ if (mCorrespondingEvent.size() > 0) {
+ if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
+ + size) {
+ Object event = mCorrespondingEvent.firstEntry().getValue();
+ if (event instanceof ImageEvent) {
+ // Tag value overlaps thumbnail, ignore thumbnail.
+ Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
+ Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+ Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
+ } else {
+ // Tag value overlaps another tag, shorten count
+ if (event instanceof IfdEvent) {
+ Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
+ + " overlaps value for tag: \n" + tag.toString());
+ } else if (event instanceof ExifTagEvent) {
+ Log.w(TAG, "Tag value for tag: \n"
+ + ((ExifTagEvent) event).tag.toString()
+ + " overlaps value for tag: \n" + tag.toString());
+ }
+ size = mCorrespondingEvent.firstEntry().getKey()
+ - mTiffStream.getReadByteCount();
+ Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
+ + " setting count to: " + size);
+ tag.forceSetComponentCount(size);
+ }
+ }
+ }
+ }
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ case ExifTag.TYPE_UNDEFINED: {
+ byte buf[] = new byte[tag.getComponentCount()];
+ read(buf);
+ tag.setValue(buf);
+ }
+ break;
+ case ExifTag.TYPE_ASCII:
+ tag.setValue(readString(tag.getComponentCount()));
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG: {
+ long value[] = new long[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedLong();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_RATIONAL: {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedRational();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT: {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedShort();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_LONG: {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readLong();
+ }
+ tag.setValue(value);
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL: {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readRational();
+ }
+ tag.setValue(value);
+ }
+ break;
+ }
+ if (LOGV) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
+ }
+
+ private void parseTiffHeader() throws IOException,
+ ExifInvalidFormatException {
+ short byteOrder = mTiffStream.readShort();
+ if (LITTLE_ENDIAN_TAG == byteOrder) {
+ mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ } else if (BIG_ENDIAN_TAG == byteOrder) {
+ mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ } else {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+
+ if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+ }
+
+ private boolean seekTiffData(InputStream inputStream) throws IOException,
+ ExifInvalidFormatException {
+ CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
+ if (dataStream.readShort() != JpegHeader.SOI) {
+ throw new ExifInvalidFormatException("Invalid JPEG format");
+ }
+
+ short marker = dataStream.readShort();
+ while (marker != JpegHeader.EOI
+ && !JpegHeader.isSofMarker(marker)) {
+ int length = dataStream.readUnsignedShort();
+ // Some invalid formatted image contains multiple APP1,
+ // try to find the one with Exif data.
+ if (marker == JpegHeader.APP1) {
+ int header = 0;
+ short headerTail = 0;
+ if (length >= 8) {
+ header = dataStream.readInt();
+ headerTail = dataStream.readShort();
+ length -= 6;
+ if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+ mTiffStartPosition = dataStream.getReadByteCount();
+ mApp1End = length;
+ mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
+ return true;
+ }
+ }
+ }
+ if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
+ Log.w(TAG, "Invalid JPEG format.");
+ return false;
+ }
+ marker = dataStream.readShort();
+ }
+ return false;
+ }
+
+ protected int getOffsetToExifEndFromSOF() {
+ return mOffsetToApp1EndFromSOF;
+ }
+
+ protected int getTiffStartPosition() {
+ return mTiffStartPosition;
+ }
+
+ /**
+ * Reads bytes from the InputStream.
+ */
+ protected int read(byte[] buffer, int offset, int length) throws IOException {
+ return mTiffStream.read(buffer, offset, length);
+ }
+
+ /**
+ * Equivalent to read(buffer, 0, buffer.length).
+ */
+ protected int read(byte[] buffer) throws IOException {
+ return mTiffStream.read(buffer);
+ }
+
+ /**
+ * Reads a String from the InputStream with US-ASCII charset. The parser
+ * will read n bytes and convert it to ascii string. This is used for
+ * reading values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ protected String readString(int n) throws IOException {
+ return readString(n, US_ASCII);
+ }
+
+ /**
+ * Reads a String from the InputStream with the given charset. The parser
+ * will read n bytes and convert it to string. This is used for reading
+ * values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ protected String readString(int n, Charset charset) throws IOException {
+ if (n > 0) {
+ return mTiffStream.readString(n, charset);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+ * InputStream.
+ */
+ protected int readUnsignedShort() throws IOException {
+ return mTiffStream.readShort() & 0xffff;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+ * InputStream.
+ */
+ protected long readUnsignedLong() throws IOException {
+ return readLong() & 0xffffffffL;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+ * InputStream.
+ */
+ protected Rational readUnsignedRational() throws IOException {
+ long nomi = readUnsignedLong();
+ long denomi = readUnsignedLong();
+ return new Rational(nomi, denomi);
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
+ */
+ protected int readLong() throws IOException {
+ return mTiffStream.readInt();
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
+ */
+ protected Rational readRational() throws IOException {
+ int nomi = readLong();
+ int denomi = readLong();
+ return new Rational(nomi, denomi);
+ }
+
+ private static class ImageEvent {
+ int stripIndex;
+ int type;
+
+ ImageEvent(int type) {
+ this.stripIndex = 0;
+ this.type = type;
+ }
+
+ ImageEvent(int type, int stripIndex) {
+ this.type = type;
+ this.stripIndex = stripIndex;
+ }
+ }
+
+ private static class IfdEvent {
+ int ifd;
+ boolean isRequested;
+
+ IfdEvent(int ifd, boolean isInterestedIfd) {
+ this.ifd = ifd;
+ this.isRequested = isInterestedIfd;
+ }
+ }
+
+ private static class ExifTagEvent {
+ ExifTag tag;
+ boolean isRequested;
+
+ ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
+ this.tag = tag;
+ this.isRequested = isRequireByUser;
+ }
+ }
+
+ /**
+ * Gets the byte order of the current InputStream.
+ */
+ protected ByteOrder getByteOrder() {
+ return mTiffStream.getByteOrder();
+ }
+}
diff --git a/src/com/android/messaging/util/exif/ExifReader.java b/src/com/android/messaging/util/exif/ExifReader.java
new file mode 100644
index 0000000..eece48a
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifReader.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import android.util.Log;
+import com.android.messaging.util.LogUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
+ */
+class ExifReader {
+ private static final String TAG = LogUtil.BUGLE_TAG;
+
+ private final ExifInterface mInterface;
+
+ ExifReader(ExifInterface iRef) {
+ mInterface = iRef;
+ }
+
+ /**
+ * Parses the inputStream and and returns the EXIF data in an
+ * {@link ExifData}.
+ *
+ * @throws ExifInvalidFormatException
+ * @throws java.io.IOException
+ */
+ protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+ IOException {
+ ExifParser parser = ExifParser.parse(inputStream, mInterface);
+ ExifData exifData = new ExifData(parser.getByteOrder());
+ ExifTag tag = null;
+
+ int event = parser.next();
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ tag = parser.getTag();
+ if (!tag.hasValue()) {
+ parser.registerForTagValue(tag);
+ } else {
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+ parser.readFullTagValue(tag);
+ }
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ break;
+ case ExifParser.EVENT_COMPRESSED_IMAGE:
+ byte buf[] = new byte[parser.getCompressedImageSize()];
+ if (buf.length == parser.read(buf)) {
+ exifData.setCompressedThumbnail(buf);
+ } else {
+ Log.w(TAG, "Failed to read the compressed thumbnail");
+ }
+ break;
+ case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+ buf = new byte[parser.getStripSize()];
+ if (buf.length == parser.read(buf)) {
+ exifData.setStripBytes(parser.getStripIndex(), buf);
+ } else {
+ Log.w(TAG, "Failed to read the strip bytes");
+ }
+ break;
+ }
+ event = parser.next();
+ }
+ return exifData;
+ }
+}
diff --git a/src/com/android/messaging/util/exif/ExifTag.java b/src/com/android/messaging/util/exif/ExifTag.java
new file mode 100644
index 0000000..da6f4ed
--- /dev/null
+++ b/src/com/android/messaging/util/exif/ExifTag.java
@@ -0,0 +1,1008 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
+ */
+public class ExifTag {
+ /**
+ * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+ */
+ public static final short TYPE_UNSIGNED_BYTE = 1;
+ /**
+ * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+ * ASCII code. The final byte is terminated with NULL.
+ */
+ public static final short TYPE_ASCII = 2;
+ /**
+ * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_SHORT = 3;
+ /**
+ * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_LONG = 4;
+ /**
+ * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+ * one is the numerator and the second one expresses the denominator.
+ */
+ public static final short TYPE_UNSIGNED_RATIONAL = 5;
+ /**
+ * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+ * value depending on the field definition.
+ */
+ public static final short TYPE_UNDEFINED = 7;
+ /**
+ * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+ * (2's complement notation).
+ */
+ public static final short TYPE_LONG = 9;
+ /**
+ * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+ * one is the numerator and the second one is the denominator.
+ */
+ public static final short TYPE_RATIONAL = 10;
+
+ private static Charset US_ASCII = Charset.forName("US-ASCII");
+ private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+ private static final int UNSIGNED_SHORT_MAX = 65535;
+ private static final long UNSIGNED_LONG_MAX = 4294967295L;
+ private static final long LONG_MAX = Integer.MAX_VALUE;
+ private static final long LONG_MIN = Integer.MIN_VALUE;
+
+ static {
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+ TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+ }
+
+ static final int SIZE_UNDEFINED = 0;
+
+ // Exif TagId
+ private final short mTagId;
+ // Exif Tag Type
+ private final short mDataType;
+ // If tag has defined count
+ private boolean mHasDefinedDefaultComponentCount;
+ // Actual data count in tag (should be number of elements in value array)
+ private int mComponentCountActual;
+ // The ifd that this tag should be put in
+ private int mIfd;
+ // The value (array of elements of type Tag Type)
+ private Object mValue;
+ // Value offset in exif header.
+ private int mOffset;
+
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+
+ /**
+ * Returns true if the given IFD is a valid IFD.
+ */
+ public static boolean isValidIfd(int ifdId) {
+ return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+ || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+ || ifdId == IfdId.TYPE_IFD_GPS;
+ }
+
+ /**
+ * Returns true if a given type is a valid tag type.
+ */
+ public static boolean isValidType(short type) {
+ return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
+ type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+ type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+ type == TYPE_LONG || type == TYPE_RATIONAL;
+ }
+
+ // Use builtTag in ExifInterface instead of constructor.
+ ExifTag(short tagId, short type, int componentCount, int ifd,
+ boolean hasDefinedComponentCount) {
+ mTagId = tagId;
+ mDataType = type;
+ mComponentCountActual = componentCount;
+ mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
+ mIfd = ifd;
+ mValue = null;
+ }
+
+ /**
+ * Gets the element size of the given data type in bytes.
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public static int getElementSize(short type) {
+ return TYPE_TO_SIZE_MAP[type];
+ }
+
+ /**
+ * Returns the ID of the IFD this tag belongs to.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ public int getIfd() {
+ return mIfd;
+ }
+
+ protected void setIfd(int ifdId) {
+ mIfd = ifdId;
+ }
+
+ /**
+ * Gets the TID of this tag.
+ */
+ public short getTagId() {
+ return mTagId;
+ }
+
+ /**
+ * Gets the data type of this tag
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public short getDataType() {
+ return mDataType;
+ }
+
+ /**
+ * Gets the total data size in bytes of the value of this tag.
+ */
+ public int getDataSize() {
+ return getComponentCount() * getElementSize(getDataType());
+ }
+
+ /**
+ * Gets the component count of this tag.
+ */
+
+ // TODO: fix integer overflows with this
+ public int getComponentCount() {
+ return mComponentCountActual;
+ }
+
+ /**
+ * Sets the component count of this tag. Call this function before
+ * setValue() if the length of value does not match the component count.
+ */
+ protected void forceSetComponentCount(int count) {
+ mComponentCountActual = count;
+ }
+
+ /**
+ * Returns true if this ExifTag contains value; otherwise, this tag will
+ * contain an offset value that is determined when the tag is written.
+ */
+ public boolean hasValue() {
+ return mValue != null;
+ }
+
+ /**
+ * Sets integer values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The value.length does NOT match the component count in the definition
+ * for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(int[] value) {
+ if (checkBadComponentCount(value.length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+ mDataType != TYPE_UNSIGNED_LONG) {
+ return false;
+ }
+ if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+ return false;
+ } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+ return false;
+ }
+
+ long[] data = new long[value.length];
+ for (int i = 0; i < value.length; i++) {
+ data[i] = value[i];
+ }
+ mValue = data;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets integer value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+ * will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The component count in the definition of this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(int value) {
+ return setValue(new int[] {
+ value
+ });
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The value.length does NOT match the component count in the definition
+ * for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(long[] value) {
+ if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+ return false;
+ }
+ if (checkOverflowForUnsignedLong(value)) {
+ return false;
+ }
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+ * <li>The value overflows.</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(long value) {
+ return setValue(new long[] {
+ value
+ });
+ }
+
+ /**
+ * Sets a string value into this tag. This method should be used for tags of
+ * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+ * Characters that cannot be converted are replaced with '?'. The length of
+ * the string must be equal to either (component count -1) or (component
+ * count). The final byte will be set to the string null terminator '\0',
+ * overwriting the last character in the string if the value.length is equal
+ * to the component count. This method will fail if:
+ * <ul>
+ * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+ * <li>The length of the string is not equal to (component count -1) or
+ * (component count) in the definition for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(String value) {
+ if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+ return false;
+ }
+
+ byte[] buf = value.getBytes(US_ASCII);
+ byte[] finalBuf = buf;
+ if (buf.length > 0) {
+ finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+ .copyOf(buf, buf.length + 1);
+ } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
+ finalBuf = new byte[] { 0 };
+ }
+ int count = finalBuf.length;
+ if (checkBadComponentCount(count)) {
+ return false;
+ }
+ mComponentCountActual = count;
+ mValue = finalBuf;
+ return true;
+ }
+
+ /**
+ * Sets Rational values into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.</li>
+ * <li>The value overflows.</li>
+ * <li>The value.length does NOT match the component count in the definition
+ * for this tag.</li>
+ * </ul>
+ *
+ * @see Rational
+ */
+ public boolean setValue(Rational[] value) {
+ if (checkBadComponentCount(value.length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+ return false;
+ }
+ if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+ return false;
+ } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+ return false;
+ }
+
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets a Rational value into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.</li>
+ * <li>The value overflows.</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
+ * </ul>
+ *
+ * @see Rational
+ */
+ public boolean setValue(Rational value) {
+ return setValue(new Rational[] {
+ value
+ });
+ }
+
+ /**
+ * Sets byte values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .</li>
+ * <li>The length does NOT match the component count in the definition for
+ * this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(byte[] value, int offset, int length) {
+ if (checkBadComponentCount(length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+ return false;
+ }
+ mValue = new byte[length];
+ System.arraycopy(value, offset, mValue, 0, length);
+ mComponentCountActual = length;
+ return true;
+ }
+
+ /**
+ * Equivalent to setValue(value, 0, value.length).
+ */
+ public boolean setValue(byte[] value) {
+ return setValue(value, 0, value.length);
+ }
+
+ /**
+ * Sets byte value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
+ * <ul>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(byte value) {
+ return setValue(new byte[] {
+ value
+ });
+ }
+
+ /**
+ * Sets the value for this tag using an appropriate setValue method for the
+ * given object. This method will fail if:
+ * <ul>
+ * <li>The corresponding setValue method for the class of the object passed
+ * in would fail.</li>
+ * <li>There is no obvious way to cast the object passed in into an EXIF tag
+ * type.</li>
+ * </ul>
+ */
+ public boolean setValue(Object obj) {
+ if (obj == null) {
+ return false;
+ } else if (obj instanceof Short) {
+ return setValue(((Short) obj).shortValue() & 0x0ffff);
+ } else if (obj instanceof String) {
+ return setValue((String) obj);
+ } else if (obj instanceof int[]) {
+ return setValue((int[]) obj);
+ } else if (obj instanceof long[]) {
+ return setValue((long[]) obj);
+ } else if (obj instanceof Rational) {
+ return setValue((Rational) obj);
+ } else if (obj instanceof Rational[]) {
+ return setValue((Rational[]) obj);
+ } else if (obj instanceof byte[]) {
+ return setValue((byte[]) obj);
+ } else if (obj instanceof Integer) {
+ return setValue(((Integer) obj).intValue());
+ } else if (obj instanceof Long) {
+ return setValue(((Long) obj).longValue());
+ } else if (obj instanceof Byte) {
+ return setValue(((Byte) obj).byteValue());
+ } else if (obj instanceof Short[]) {
+ // Nulls in this array are treated as zeroes.
+ Short[] arr = (Short[]) obj;
+ int[] fin = new int[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+ }
+ return setValue(fin);
+ } else if (obj instanceof Integer[]) {
+ // Nulls in this array are treated as zeroes.
+ Integer[] arr = (Integer[]) obj;
+ int[] fin = new int[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+ }
+ return setValue(fin);
+ } else if (obj instanceof Long[]) {
+ // Nulls in this array are treated as zeroes.
+ Long[] arr = (Long[]) obj;
+ long[] fin = new long[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+ }
+ return setValue(fin);
+ } else if (obj instanceof Byte[]) {
+ // Nulls in this array are treated as zeroes.
+ Byte[] arr = (Byte[]) obj;
+ byte[] fin = new byte[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+ }
+ return setValue(fin);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets a timestamp to this tag. The method converts the timestamp with the
+ * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+ * method will fail if the data type is not {@link #TYPE_ASCII} or the
+ * component count of this tag is not 20 or undefined.
+ *
+ * @param time the number of milliseconds since Jan. 1, 1970 GMT
+ * @return true on success
+ */
+ public boolean setTimeValue(long time) {
+ // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+ synchronized (TIME_FORMAT) {
+ return setValue(TIME_FORMAT.format(new Date(time)));
+ }
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @return the value as a String, or null if the tag's value does not exist
+ * or cannot be converted to a String.
+ */
+ public String getValueAsString() {
+ if (mValue == null) {
+ return null;
+ } else if (mValue instanceof String) {
+ return (String) mValue;
+ } else if (mValue instanceof byte[]) {
+ return new String((byte[]) mValue, US_ASCII);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @param defaultValue the String to return if the tag's value does not
+ * exist or cannot be converted to a String.
+ * @return the tag's value as a String, or the defaultValue.
+ */
+ public String getValueAsString(String defaultValue) {
+ String s = getValueAsString();
+ if (s == null) {
+ return defaultValue;
+ }
+ return s;
+ }
+
+ /**
+ * Gets the value as a byte array. This method should be used for tags of
+ * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @return the value as a byte array, or null if the tag's value does not
+ * exist or cannot be converted to a byte array.
+ */
+ public byte[] getValueAsBytes() {
+ if (mValue instanceof byte[]) {
+ return (byte[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a byte. If there are more than 1 bytes in this value,
+ * gets the first byte. This method should be used for tags of type
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @param defaultValue the byte to return if tag's value does not exist or
+ * cannot be converted to a byte.
+ * @return the tag's value as a byte, or the defaultValue.
+ */
+ public byte getValueAsByte(byte defaultValue) {
+ byte[] b = getValueAsBytes();
+ if (b == null || b.length < 1) {
+ return defaultValue;
+ }
+ return b[0];
+ }
+
+ /**
+ * Gets the value as an array of Rationals. This method should be used for
+ * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @return the value as as an array of Rationals, or null if the tag's value
+ * does not exist or cannot be converted to an array of Rationals.
+ */
+ public Rational[] getValueAsRationals() {
+ if (mValue instanceof Rational[]) {
+ return (Rational[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the Rational to return if tag's value does not exist
+ * or cannot be converted to a Rational.
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational(Rational defaultValue) {
+ Rational[] r = getValueAsRationals();
+ if (r == null || r.length < 1) {
+ return defaultValue;
+ }
+ return r[0];
+ }
+
+ /**
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the numerator of the Rational to return if tag's
+ * value does not exist or cannot be converted to a Rational (the
+ * denominator will be 1).
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational(long defaultValue) {
+ Rational defaultVal = new Rational(defaultValue, 1);
+ return getValueAsRational(defaultVal);
+ }
+
+ /**
+ * Gets the value as an array of ints. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of ints, or null if the tag's value does
+ * not exist or cannot be converted to an array of ints.
+ */
+ public int[] getValueAsInts() {
+ if (mValue == null) {
+ return null;
+ } else if (mValue instanceof long[]) {
+ long[] val = (long[]) mValue;
+ int[] arr = new int[val.length];
+ for (int i = 0; i < val.length; i++) {
+ arr[i] = (int) val[i]; // Truncates
+ }
+ return arr;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as an int. If there are more than 1 ints in this value,
+ * gets the first one. This method should be used for tags of type
+ * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the int to return if tag's value does not exist or
+ * cannot be converted to an int.
+ * @return the tag's value as a int, or the defaultValue.
+ */
+ public int getValueAsInt(int defaultValue) {
+ int[] i = getValueAsInts();
+ if (i == null || i.length < 1) {
+ return defaultValue;
+ }
+ return i[0];
+ }
+
+ /**
+ * Gets the value as an array of longs. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of longs, or null if the tag's value
+ * does not exist or cannot be converted to an array of longs.
+ */
+ public long[] getValueAsLongs() {
+ if (mValue instanceof long[]) {
+ return (long[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value or null if none exists. If there are more than 1 longs in
+ * this value, gets the first one. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the long to return if tag's value does not exist or
+ * cannot be converted to a long.
+ * @return the tag's value as a long, or the defaultValue.
+ */
+ public long getValueAsLong(long defaultValue) {
+ long[] l = getValueAsLongs();
+ if (l == null || l.length < 1) {
+ return defaultValue;
+ }
+ return l[0];
+ }
+
+ /**
+ * Gets the tag's value or null if none exists.
+ */
+ public Object getValue() {
+ return mValue;
+ }
+
+ /**
+ * Gets a long representation of the value.
+ *
+ * @param defaultValue value to return if there is no value or value is a
+ * rational with a denominator of 0.
+ * @return the tag's value as a long, or defaultValue if no representation
+ * exists.
+ */
+ public long forceGetValueAsLong(long defaultValue) {
+ long[] l = getValueAsLongs();
+ if (l != null && l.length >= 1) {
+ return l[0];
+ }
+ byte[] b = getValueAsBytes();
+ if (b != null && b.length >= 1) {
+ return b[0];
+ }
+ Rational[] r = getValueAsRationals();
+ if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+ return (long) r[0].toDouble();
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets a string representation of the value.
+ */
+ public String forceGetValueAsString() {
+ if (mValue == null) {
+ return "";
+ } else if (mValue instanceof byte[]) {
+ if (mDataType == TYPE_ASCII) {
+ return new String((byte[]) mValue, US_ASCII);
+ } else {
+ return Arrays.toString((byte[]) mValue);
+ }
+ } else if (mValue instanceof long[]) {
+ if (((long[]) mValue).length == 1) {
+ return String.valueOf(((long[]) mValue)[0]);
+ } else {
+ return Arrays.toString((long[]) mValue);
+ }
+ } else if (mValue instanceof Object[]) {
+ if (((Object[]) mValue).length == 1) {
+ Object val = ((Object[]) mValue)[0];
+ if (val == null) {
+ return "";
+ } else {
+ return val.toString();
+ }
+ } else {
+ return Arrays.toString((Object[]) mValue);
+ }
+ } else {
+ return mValue.toString();
+ }
+ }
+
+ /**
+ * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+ * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+ * {@link #getRational(int)} instead.
+ *
+ * @exception IllegalArgumentException if the data type is
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ protected long getValueAt(int index) {
+ if (mValue instanceof long[]) {
+ return ((long[]) mValue)[index];
+ } else if (mValue instanceof byte[]) {
+ return ((byte[]) mValue)[index];
+ }
+ throw new IllegalArgumentException("Cannot get integer value from "
+ + convertTypeToString(mDataType));
+ }
+
+ /**
+ * Gets the {@link #TYPE_ASCII} data.
+ *
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_ASCII}.
+ */
+ protected String getString() {
+ if (mDataType != TYPE_ASCII) {
+ throw new IllegalArgumentException("Cannot get ASCII value from "
+ + convertTypeToString(mDataType));
+ }
+ return new String((byte[]) mValue, US_ASCII);
+ }
+
+ /*
+ * Get the converted ascii byte. Used by ExifOutputStream.
+ */
+ protected byte[] getStringByte() {
+ return (byte[]) mValue;
+ }
+
+ /**
+ * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+ *
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ protected Rational getRational(int index) {
+ if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
+ throw new IllegalArgumentException("Cannot get RATIONAL value from "
+ + convertTypeToString(mDataType));
+ }
+ return ((Rational[]) mValue)[index];
+ }
+
+ /**
+ * Equivalent to getBytes(buffer, 0, buffer.length).
+ */
+ protected void getBytes(byte[] buf) {
+ getBytes(buf, 0, buf.length);
+ }
+
+ /**
+ * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+ *
+ * @param buf the byte array in which to store the bytes read.
+ * @param offset the initial position in buffer to store the bytes.
+ * @param length the maximum number of bytes to store in buffer. If length >
+ * component count, only the valid bytes will be stored.
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ */
+ protected void getBytes(byte[] buf, int offset, int length) {
+ if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
+ throw new IllegalArgumentException("Cannot get BYTE value from "
+ + convertTypeToString(mDataType));
+ }
+ System.arraycopy(mValue, 0, buf, offset,
+ (length > mComponentCountActual) ? mComponentCountActual : length);
+ }
+
+ /**
+ * Gets the offset of this tag. This is only valid if this data size > 4 and
+ * contains an offset to the location of the actual value.
+ */
+ protected int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Sets the offset of this tag.
+ */
+ protected void setOffset(int offset) {
+ mOffset = offset;
+ }
+
+ protected void setHasDefinedCount(boolean d) {
+ mHasDefinedDefaultComponentCount = d;
+ }
+
+ protected boolean hasDefinedCount() {
+ return mHasDefinedDefaultComponentCount;
+ }
+
+ private boolean checkBadComponentCount(int count) {
+ if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String convertTypeToString(short type) {
+ switch (type) {
+ case TYPE_UNSIGNED_BYTE:
+ return "UNSIGNED_BYTE";
+ case TYPE_ASCII:
+ return "ASCII";
+ case TYPE_UNSIGNED_SHORT:
+ return "UNSIGNED_SHORT";
+ case TYPE_UNSIGNED_LONG:
+ return "UNSIGNED_LONG";
+ case TYPE_UNSIGNED_RATIONAL:
+ return "UNSIGNED_RATIONAL";
+ case TYPE_UNDEFINED:
+ return "UNDEFINED";
+ case TYPE_LONG:
+ return "LONG";
+ case TYPE_RATIONAL:
+ return "RATIONAL";
+ default:
+ return "";
+ }
+ }
+
+ private boolean checkOverflowForUnsignedShort(int[] value) {
+ for (int v : value) {
+ if (v > UNSIGNED_SHORT_MAX || v < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong(long[] value) {
+ for (long v : value) {
+ if (v < 0 || v > UNSIGNED_LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong(int[] value) {
+ for (int v : value) {
+ if (v < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedRational(Rational[] value) {
+ for (Rational v : value) {
+ if (v.getNumerator() < 0 || v.getDenominator() < 0
+ || v.getNumerator() > UNSIGNED_LONG_MAX
+ || v.getDenominator() > UNSIGNED_LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForRational(Rational[] value) {
+ for (Rational v : value) {
+ if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+ || v.getNumerator() > LONG_MAX
+ || v.getDenominator() > LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof ExifTag) {
+ ExifTag tag = (ExifTag) obj;
+ if (tag.mTagId != this.mTagId
+ || tag.mComponentCountActual != this.mComponentCountActual
+ || tag.mDataType != this.mDataType) {
+ return false;
+ }
+ if (mValue != null) {
+ if (tag.mValue == null) {
+ return false;
+ } else if (mValue instanceof long[]) {
+ if (!(tag.mValue instanceof long[])) {
+ return false;
+ }
+ return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
+ } else if (mValue instanceof Rational[]) {
+ if (!(tag.mValue instanceof Rational[])) {
+ return false;
+ }
+ return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
+ } else if (mValue instanceof byte[]) {
+ if (!(tag.mValue instanceof byte[])) {
+ return false;
+ }
+ return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
+ } else {
+ return mValue.equals(tag.mValue);
+ }
+ } else {
+ return tag.mValue == null;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+ + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+ + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+ }
+
+}
diff --git a/src/com/android/messaging/util/exif/IfdData.java b/src/com/android/messaging/util/exif/IfdData.java
new file mode 100644
index 0000000..6b8c293
--- /dev/null
+++ b/src/com/android/messaging/util/exif/IfdData.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
+class IfdData {
+
+ private final int mIfdId;
+ private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
+ private int mOffsetToNextIfd = 0;
+ private static final int[] sIfds = {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
+ IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
+ };
+ /**
+ * Creates an IfdData with given IFD ID.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ IfdData(int ifdId) {
+ mIfdId = ifdId;
+ }
+
+ protected static int[] getIfds() {
+ return sIfds;
+ }
+
+ /**
+ * Get a array the contains all {@link ExifTag} in this IFD.
+ */
+ protected ExifTag[] getAllTags() {
+ return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
+ }
+
+ /**
+ * Gets the ID of this IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ protected int getId() {
+ return mIfdId;
+ }
+
+ /**
+ * Gets the {@link ExifTag} with given tag id. Return null if there is no
+ * such tag.
+ */
+ protected ExifTag getTag(short tagId) {
+ return mExifTags.get(tagId);
+ }
+
+ /**
+ * Adds or replaces a {@link ExifTag}.
+ */
+ protected ExifTag setTag(ExifTag tag) {
+ tag.setIfd(mIfdId);
+ return mExifTags.put(tag.getTagId(), tag);
+ }
+
+ protected boolean checkCollision(short tagId) {
+ return mExifTags.get(tagId) != null;
+ }
+
+ /**
+ * Removes the tag of the given ID
+ */
+ protected void removeTag(short tagId) {
+ mExifTags.remove(tagId);
+ }
+
+ /**
+ * Gets the tags count in the IFD.
+ */
+ protected int getTagCount() {
+ return mExifTags.size();
+ }
+
+ /**
+ * Sets the offset of next IFD.
+ */
+ protected void setOffsetToNextIfd(int offset) {
+ mOffsetToNextIfd = offset;
+ }
+
+ /**
+ * Gets the offset of next IFD.
+ */
+ protected int getOffsetToNextIfd() {
+ return mOffsetToNextIfd;
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFDs offset or thumbnail offset will be ignored.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof IfdData) {
+ IfdData data = (IfdData) obj;
+ if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
+ ExifTag[] tags = data.getAllTags();
+ for (ExifTag tag : tags) {
+ if (ExifInterface.isOffsetTag(tag.getTagId())) {
+ continue;
+ }
+ ExifTag tag2 = mExifTags.get(tag.getTagId());
+ if (!tag.equals(tag2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/messaging/util/exif/IfdId.java b/src/com/android/messaging/util/exif/IfdId.java
new file mode 100644
index 0000000..06a820d
--- /dev/null
+++ b/src/com/android/messaging/util/exif/IfdId.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
+public interface IfdId {
+ public static final int TYPE_IFD_0 = 0;
+ public static final int TYPE_IFD_1 = 1;
+ public static final int TYPE_IFD_EXIF = 2;
+ public static final int TYPE_IFD_INTEROPERABILITY = 3;
+ public static final int TYPE_IFD_GPS = 4;
+ /* This is used in ExifData to allocate enough IfdData */
+ static final int TYPE_IFD_COUNT = 5;
+
+}
diff --git a/src/com/android/messaging/util/exif/JpegHeader.java b/src/com/android/messaging/util/exif/JpegHeader.java
new file mode 100644
index 0000000..1dd12a5
--- /dev/null
+++ b/src/com/android/messaging/util/exif/JpegHeader.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+class JpegHeader {
+ public static final short SOI = (short) 0xFFD8;
+ public static final short APP1 = (short) 0xFFE1;
+ public static final short APP0 = (short) 0xFFE0;
+ public static final short EOI = (short) 0xFFD9;
+
+ /**
+ * SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
+ * and DAC marker.
+ */
+ public static final short SOF0 = (short) 0xFFC0;
+ public static final short SOF15 = (short) 0xFFCF;
+ public static final short DHT = (short) 0xFFC4;
+ public static final short JPG = (short) 0xFFC8;
+ public static final short DAC = (short) 0xFFCC;
+
+ public static final boolean isSofMarker(short marker) {
+ return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
+ && marker != DAC;
+ }
+}
diff --git a/src/com/android/messaging/util/exif/OrderedDataOutputStream.java b/src/com/android/messaging/util/exif/OrderedDataOutputStream.java
new file mode 100644
index 0000000..cf64805
--- /dev/null
+++ b/src/com/android/messaging/util/exif/OrderedDataOutputStream.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class OrderedDataOutputStream extends FilterOutputStream {
+ private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+ public OrderedDataOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ public OrderedDataOutputStream setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ return this;
+ }
+
+ public OrderedDataOutputStream writeShort(short value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putShort(value);
+ out.write(mByteBuffer.array(), 0, 2);
+ return this;
+ }
+
+ public OrderedDataOutputStream writeInt(int value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putInt(value);
+ out.write(mByteBuffer.array());
+ return this;
+ }
+
+ public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
+ writeInt((int) rational.getNumerator());
+ writeInt((int) rational.getDenominator());
+ return this;
+ }
+}
diff --git a/src/com/android/messaging/util/exif/Rational.java b/src/com/android/messaging/util/exif/Rational.java
new file mode 100644
index 0000000..b42ceb7
--- /dev/null
+++ b/src/com/android/messaging/util/exif/Rational.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 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.util.exif;
+
+/**
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
+ */
+public class Rational {
+
+ private final long mNumerator;
+ private final long mDenominator;
+
+ /**
+ * Create a Rational with a given numerator and denominator.
+ *
+ * @param nominator
+ * @param denominator
+ */
+ public Rational(long nominator, long denominator) {
+ mNumerator = nominator;
+ mDenominator = denominator;
+ }
+
+ /**
+ * Create a copy of a Rational.
+ */
+ public Rational(Rational r) {
+ mNumerator = r.mNumerator;
+ mDenominator = r.mDenominator;
+ }
+
+ /**
+ * Gets the numerator of the rational.
+ */
+ public long getNumerator() {
+ return mNumerator;
+ }
+
+ /**
+ * Gets the denominator of the rational
+ */
+ public long getDenominator() {
+ return mDenominator;
+ }
+
+ /**
+ * Gets the rational value as type double. Will cause a divide-by-zero error
+ * if the denominator is 0.
+ */
+ public double toDouble() {
+ return mNumerator / (double) mDenominator;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Rational) {
+ Rational data = (Rational) obj;
+ return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mNumerator + "/" + mDenominator;
+ }
+}
diff --git a/src/com/android/messaging/widget/BaseWidgetFactory.java b/src/com/android/messaging/widget/BaseWidgetFactory.java
new file mode 100644
index 0000000..30b80ae
--- /dev/null
+++ b/src/com/android/messaging/widget/BaseWidgetFactory.java
@@ -0,0 +1,232 @@
+/*
+ * 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.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Binder;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.style.StyleSpan;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
+import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageRequestDescriptor;
+import com.android.messaging.datamodel.media.ImageResource;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.LogUtil;
+
+/**
+ * Remote Views Factory for Bugle Widget.
+ */
+abstract class BaseWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
+ protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
+
+ protected static final int MAX_ITEMS_TO_SHOW = 25;
+
+ /**
+ * Lock to avoid race condition between widgets.
+ */
+ protected static final Object sWidgetLock = new Object();
+
+ protected final Context mContext;
+ protected final int mAppWidgetId;
+ protected boolean mShouldShowViewMore;
+ protected Cursor mCursor;
+ protected final AppWidgetManager mAppWidgetManager;
+ protected int mIconSize;
+ protected ImageResource mAvatarResource;
+
+ public BaseWidgetFactory(Context context, Intent intent) {
+ mContext = context;
+ mAppWidgetId = intent.getIntExtra(
+ AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BaseWidgetFactory intent: " + intent + "widget id: " + mAppWidgetId);
+ }
+ mIconSize = (int) context.getResources()
+ .getDimension(R.dimen.contact_icon_view_normal_size);
+
+ }
+
+ @Override
+ public void onCreate() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onCreate");
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onDestroy");
+ }
+ synchronized (sWidgetLock) {
+ if (mCursor != null && !mCursor.isClosed()) {
+ mCursor.close();
+ mCursor = null;
+ }
+ }
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onDataSetChanged");
+ }
+ synchronized (sWidgetLock) {
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCursor = doQuery();
+ onLoadComplete();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ protected abstract Cursor doQuery();
+
+ /**
+ * Returns the number of items that should be shown in the widget list. This method also
+ * updates the boolean that indicates whether the "show more" item should be shown.
+ * @return the number of items to be displayed in the list.
+ */
+ @Override
+ public int getCount() {
+ synchronized (sWidgetLock) {
+ if (mCursor == null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getCount: 0");
+ }
+ return 0;
+ }
+ final int count = getItemCount();
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getCount: " + count);
+ }
+ mShouldShowViewMore = count < mCursor.getCount();
+ return count + (mShouldShowViewMore ? 1 : 0);
+ }
+ }
+
+ /**
+ * Returns the number of messages that should be shown in the widget. This method
+ * doesn't update the boolean that indicates whether the "show more" item should be included
+ * in the list.
+ * @return
+ */
+ protected int getItemCount() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getItemCount: " + mCursor.getCount());
+ }
+ return Math.min(mCursor.getCount(), MAX_ITEMS_TO_SHOW);
+ }
+
+ /*
+ * Make the given text bold if the item is unread
+ */
+ protected CharSequence boldifyIfUnread(CharSequence text, final boolean unread) {
+ if (!unread) {
+ return text;
+ }
+ final SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ builder.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return builder;
+ }
+
+ protected Bitmap getAvatarBitmap(final Uri avatarUri) {
+ final String avatarType = avatarUri == null ?
+ null : AvatarUriUtil.getAvatarType(avatarUri);
+ ImageRequestDescriptor descriptor;
+ if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) {
+ descriptor = new AvatarGroupRequestDescriptor(avatarUri, mIconSize, mIconSize);
+ } else {
+ descriptor = new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize);
+ }
+
+ final MediaRequest<ImageResource> imageRequest =
+ descriptor.buildSyncMediaRequest(mContext);
+ final ImageResource imageResource =
+ MediaResourceManager.get().requestMediaResourceSync(imageRequest);
+ if (imageResource != null) {
+ setAvatarResource(imageResource);
+ return mAvatarResource.getBitmap();
+ } else {
+ releaseAvatarResource();
+ return null;
+ }
+ }
+
+ /**
+ * @return the "View more messages" view. When the user taps this item, they're
+ * taken to the conversation in Bugle.
+ */
+ abstract protected RemoteViews getViewMoreItemsView();
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ private void onLoadComplete() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onLoadComplete");
+ }
+ final RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(),
+ getMainLayoutId());
+ mAppWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews);
+ }
+
+ protected abstract int getMainLayoutId();
+
+ private void setAvatarResource(final ImageResource resource) {
+ if (mAvatarResource != resource) {
+ // Clear out any information for what is currently used
+ releaseAvatarResource();
+ mAvatarResource = resource;
+ }
+ }
+
+ private void releaseAvatarResource() {
+ if (mAvatarResource != null) {
+ mAvatarResource.release();
+ }
+ mAvatarResource = null;
+ }
+}
diff --git a/src/com/android/messaging/widget/BaseWidgetProvider.java b/src/com/android/messaging/widget/BaseWidgetProvider.java
new file mode 100644
index 0000000..431a6c7
--- /dev/null
+++ b/src/com/android/messaging/widget/BaseWidgetProvider.java
@@ -0,0 +1,184 @@
+/*
+ * 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.widget;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.messaging.util.LogUtil;
+
+public abstract class BaseWidgetProvider extends AppWidgetProvider {
+ protected static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
+
+ public static final int WIDGET_CONVERSATION_REQUEST_CODE = 987;
+
+ static final String WIDGET_SIZE_KEY = "widgetSizeKey";
+
+ public static final int SIZE_LARGE = 0; // undefined == 0, which is the default, large
+ public static final int SIZE_SMALL = 1;
+ public static final int SIZE_MEDIUM = 2;
+ public static final int SIZE_PRE_JB = 3;
+
+ /**
+ * Update all widgets in the list
+ */
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds);
+
+ for (int i = 0; i < appWidgetIds.length; ++i) {
+ updateWidget(context, appWidgetIds[i]);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onReceive intent: " + intent + " for " + this.getClass());
+ }
+ final String action = intent.getAction();
+
+ // The base class AppWidgetProvider's onReceive handles the normal widget intents. Here
+ // we're looking for an intent sent by our app when it knows a message has
+ // been sent or received (or a conversation has been read) and is telling the widget it
+ // needs to update.
+ if (getAction().equals(action)) {
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
+ this.getClass()));
+
+ if (appWidgetIds.length > 0) {
+ // We need to update all Bugle app widgets on the home screen.
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onReceive notifyAppWidgetViewDataChanged listId: " +
+ getListId() + " first widgetId: " + appWidgetIds[0]);
+ }
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, getListId());
+ }
+ } else {
+ super.onReceive(context, intent);
+ }
+ }
+
+ protected abstract String getAction();
+
+ protected abstract int getListId();
+
+ /**
+ * Update the widget appWidgetId
+ */
+ protected abstract void updateWidget(Context context, int appWidgetId);
+
+ private int getWidgetSize(AppWidgetManager appWidgetManager,
+ int appWidgetId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize");
+ }
+
+ // Get the dimensions
+ final Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
+
+ // Get min width and height.
+ final int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+ final int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+
+ // First find out rows and columns based on width provided.
+ final int rows = getCellsForSize(minHeight);
+ final int columns = getCellsForSize(minWidth);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize row: " + rows +
+ " columns: " + columns);
+ }
+
+ int size = SIZE_MEDIUM;
+ if (rows == 1) {
+ size = SIZE_SMALL; // Our widget doesn't let itself get this small. Perhaps in the
+ // future will add a super-mini widget.
+ } else if (columns > 3) {
+ size = SIZE_LARGE;
+ }
+
+ // put the size in the bundle so our service know what size it's dealing with.
+ final int savedSize = options.getInt(WIDGET_SIZE_KEY);
+ if (savedSize != size) {
+ options.putInt(WIDGET_SIZE_KEY, size);
+ appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
+
+ // The size changed. We have to force the widget to rebuild the list.
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, getListId());
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BaseWidgetProvider.getWidgetSize old size: " + savedSize +
+ " new size saved: " + size);
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * Returns number of cells needed for given size of the widget.
+ *
+ * @param size Widget size in dp.
+ * @return Size in number of cells.
+ */
+ private static int getCellsForSize(int size) {
+ // The hardwired sizes in this function come from the hardwired formula found in
+ // Android's UI guidelines for widget design:
+ // http://developer.android.com/guide/practices/ui_guidelines/widget_design.html
+ return (size + 30) / 70;
+ }
+
+ @Override
+ public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
+ int appWidgetId, Bundle newOptions) {
+
+ final int widgetSize = getWidgetSize(appWidgetManager, appWidgetId);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BaseWidgetProvider.onAppWidgetOptionsChanged new size: " +
+ widgetSize);
+ }
+
+ super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
+ }
+
+ protected void deletePreferences(final int widgetId) {
+ }
+
+ /**
+ * Remove preferences when deleting widget
+ */
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ super.onDeleted(context, appWidgetIds);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BaseWidgetProvider.onDeleted");
+ }
+
+ for (final int widgetId : appWidgetIds) {
+ deletePreferences(widgetId);
+ }
+ }
+
+}
diff --git a/src/com/android/messaging/widget/BugleWidgetProvider.java b/src/com/android/messaging/widget/BugleWidgetProvider.java
new file mode 100644
index 0000000..50c97b6
--- /dev/null
+++ b/src/com/android/messaging/widget/BugleWidgetProvider.java
@@ -0,0 +1,114 @@
+/*
+ * 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.widget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.widget.RemoteViews;
+
+import com.android.messaging.R;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.UiUtils;
+
+public class BugleWidgetProvider extends BaseWidgetProvider {
+ public static final String ACTION_NOTIFY_CONVERSATIONS_CHANGED =
+ "com.android.Bugle.intent.action.ACTION_NOTIFY_CONVERSATIONS_CHANGED";
+
+ public static final int WIDGET_NEW_CONVERSATION_REQUEST_CODE = 986;
+
+ /**
+ * Update the widget appWidgetId
+ */
+ @Override
+ protected void updateWidget(final Context context, final int appWidgetId) {
+ if (OsUtil.hasRequiredPermissions()) {
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ rebuildWidget(context, appWidgetId);
+ }
+ });
+ } else {
+ AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
+ UiUtils.getWidgetMissingPermissionView(context));
+ }
+ }
+
+ @Override
+ protected String getAction() {
+ return ACTION_NOTIFY_CONVERSATIONS_CHANGED;
+ }
+
+ @Override
+ protected int getListId() {
+ return R.id.conversation_list;
+ }
+
+ public static void rebuildWidget(final Context context, final int appWidgetId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BugleWidgetProvider.rebuildWidget appWidgetId: " + appWidgetId);
+ }
+ final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
+ R.layout.widget_conversation_list);
+ PendingIntent clickIntent;
+
+ // Launch an intent to avoid ANRs
+ final Intent intent = new Intent(context, WidgetConversationListService.class);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+ remoteViews.setRemoteAdapter(appWidgetId, R.id.conversation_list, intent);
+
+ remoteViews.setTextViewText(R.id.widget_label, context.getString(R.string.app_name));
+
+ // Open Bugle's app conversation list when click on header
+ clickIntent = UIIntents.get().getWidgetPendingIntentForConversationListActivity(context);
+ remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
+
+ // On click intent for Compose
+ clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context,
+ null /*conversationId*/, WIDGET_NEW_CONVERSATION_REQUEST_CODE);
+ remoteViews.setOnClickPendingIntent(R.id.widget_compose, clickIntent);
+
+ // On click intent for Conversation
+ // Note: the template intent has to be a "naked" intent without any extras. It turns out
+ // that if the template intent does have extras, those particular extras won't get
+ // replaced by the fill-in intent on each list item.
+ clickIntent = UIIntents.get().getWidgetPendingIntentForConversationActivity(context,
+ null /*conversationId*/, WIDGET_CONVERSATION_REQUEST_CODE);
+ remoteViews.setPendingIntentTemplate(R.id.conversation_list, clickIntent);
+
+ AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
+ }
+
+ /*
+ * notifyDatasetChanged call when the conversation list changes so the Bugle widget will
+ * update and reflect the changes
+ */
+ public static void notifyConversationListChanged(final Context context) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "notifyConversationListChanged");
+ }
+ final Intent intent = new Intent(ACTION_NOTIFY_CONVERSATIONS_CHANGED);
+ context.sendBroadcast(intent);
+ }
+}
diff --git a/src/com/android/messaging/widget/WidgetConversationListService.java b/src/com/android/messaging/widget/WidgetConversationListService.java
new file mode 100644
index 0000000..264b98c
--- /dev/null
+++ b/src/com/android/messaging/widget/WidgetConversationListService.java
@@ -0,0 +1,281 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.conversationlist.ConversationListItemView;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+public class WidgetConversationListService extends RemoteViewsService {
+ private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onGetViewFactory intent: " + intent);
+ }
+ return new WidgetConversationListFactory(getApplicationContext(), intent);
+ }
+
+ /**
+ * Remote Views Factory for Bugle Widget.
+ */
+ private static class WidgetConversationListFactory extends BaseWidgetFactory {
+
+ public WidgetConversationListFactory(Context context, Intent intent) {
+ super(context, intent);
+ }
+
+ @Override
+ protected Cursor doQuery() {
+ return mContext.getContentResolver().query(MessagingContentProvider.CONVERSATIONS_URI,
+ ConversationListItemData.PROJECTION,
+ ConversationListData.WHERE_NOT_ARCHIVED,
+ null, // selection args
+ ConversationListData.SORT_ORDER);
+ }
+
+ /**
+ * @return the {@link RemoteViews} for a specific position in the list.
+ */
+ @Override
+ public RemoteViews getViewAt(int position) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getViewAt position: " + position);
+ }
+ synchronized (sWidgetLock) {
+ // "View more conversations" view.
+ if (mCursor == null
+ || (mShouldShowViewMore && position >= getItemCount())) {
+ return getViewMoreItemsView();
+ }
+
+ if (!mCursor.moveToPosition(position)) {
+ // If we ever fail to move to a position, return the "View More conversations"
+ // view.
+ LogUtil.w(TAG, "Failed to move to position: " + position);
+ return getViewMoreItemsView();
+ }
+
+ final ConversationListItemData conv = new ConversationListItemData();
+ conv.bind(mCursor);
+
+ // Inflate and fill out the remote view
+ final RemoteViews remoteViews = new RemoteViews(
+ mContext.getPackageName(), R.layout.widget_conversation_list_item);
+
+ final boolean hasUnreadMessages = !conv.getIsRead();
+ final Resources resources = mContext.getResources();
+ final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
+
+ final String timeStamp = conv.getIsSendRequested() ?
+ resources.getString(R.string.message_status_sending) :
+ Dates.getWidgetTimeString(conv.getTimestamp(), true /*abbreviated*/)
+ .toString();
+ // Date/Timestamp or Sending or Error state -- all shown in the date item
+ remoteViews.setTextViewText(R.id.date,
+ boldifyIfUnread(timeStamp, hasUnreadMessages));
+
+ // From
+ remoteViews.setTextViewText(R.id.from,
+ boldifyIfUnread(conv.getName(), hasUnreadMessages));
+
+ // Notifications turned off mini-bell icon
+ remoteViews.setViewVisibility(R.id.conversation_notification_bell,
+ conv.getNotificationEnabled() ? View.GONE : View.VISIBLE);
+
+ // On click intent.
+ final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
+ conv.getConversationId(), null /* draft */);
+
+ remoteViews.setOnClickFillInIntent(R.id.widget_conversation_list_item, intent);
+
+ // Avatar
+ boolean includeAvatar;
+ if (OsUtil.isAtLeastJB()) {
+ final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " +
+ options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY));
+ }
+
+ includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY) ==
+ BugleWidgetProvider.SIZE_LARGE;
+ } else {
+ includeAvatar = true;;
+ }
+
+ // Show the avatar when grande size, otherwise hide it.
+ remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ?
+ View.VISIBLE : View.GONE);
+
+ Uri iconUri = null;
+ if (conv.getIcon() != null) {
+ iconUri = Uri.parse(conv.getIcon());
+ }
+ remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ?
+ getAvatarBitmap(iconUri) : null);
+
+ // Error
+ // Only show the fail icon if it is not a group conversation.
+ // And also require that we be the default sms app.
+ final boolean showError = conv.getIsFailedStatus() &&
+ isDefaultSmsApp;
+ final boolean showDraft = conv.getShowDraft() &&
+ isDefaultSmsApp;
+ remoteViews.setViewVisibility(R.id.conversation_failed_status_icon,
+ showError && includeAvatar ?
+ View.VISIBLE : View.GONE);
+
+ if (showError || showDraft) {
+ remoteViews.setViewVisibility(R.id.snippet, View.GONE);
+ remoteViews.setViewVisibility(R.id.errorBlock, View.VISIBLE);
+ remoteViews.setTextViewText(R.id.errorSnippet, getSnippetText(conv));
+
+ if (showDraft) {
+ // Show italicized "Draft" on third line
+ final String text = resources.getString(
+ R.string.conversation_list_item_view_draft_message);
+ SpannableStringBuilder builder = new SpannableStringBuilder(text);
+ builder.setSpan(new StyleSpan(Typeface.ITALIC), 0, text.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ builder.setSpan(new ForegroundColorSpan(
+ resources.getColor(R.color.widget_text_color)),
+ 0, text.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ remoteViews.setTextViewText(R.id.errorText, builder);
+ } else {
+ // Show error message on third line
+ int failureMessageId = R.string.message_status_download_failed;
+ if (conv.getIsMessageTypeOutgoing()) {
+ failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(
+ conv.getMessageStatus(),
+ conv.getMessageRawTelephonyStatus());
+ }
+ remoteViews.setTextViewText(R.id.errorText,
+ resources.getString(failureMessageId));
+ }
+ } else {
+ remoteViews.setViewVisibility(R.id.errorBlock, View.GONE);
+ remoteViews.setViewVisibility(R.id.snippet, View.VISIBLE);
+ remoteViews.setTextViewText(R.id.snippet,
+ boldifyIfUnread(getSnippetText(conv), hasUnreadMessages));
+ }
+
+ // Set the accessibility TalkBack text
+ remoteViews.setContentDescription(R.id.widget_conversation_list_item,
+ ConversationListItemView.buildContentDescription(mContext.getResources(),
+ conv, new TextPaint()));
+
+ return remoteViews;
+ }
+ }
+
+ private String getSnippetText(final ConversationListItemData conv) {
+ String snippetText = conv.getShowDraft() ?
+ conv.getDraftSnippetText() : conv.getSnippetText();
+ final String previewContentType = conv.getShowDraft() ?
+ conv.getDraftPreviewContentType() : conv.getPreviewContentType();
+ if (TextUtils.isEmpty(snippetText)) {
+ Resources resources = mContext.getResources();
+ // Use the attachment type as a snippet so the preview doesn't look odd
+ if (ContentType.isAudioType(previewContentType)) {
+ snippetText = resources.getString(
+ R.string.conversation_list_snippet_audio_clip);
+ } else if (ContentType.isImageType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_picture);
+ } else if (ContentType.isVideoType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_video);
+ } else if (ContentType.isVCardType(previewContentType)) {
+ snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
+ }
+ }
+ return snippetText;
+ }
+
+ /**
+ * @return the "View more conversations" view. When the user taps this item, they're
+ * taken to the Bugle's conversation list.
+ */
+ @Override
+ protected RemoteViews getViewMoreItemsView() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getViewMoreItemsView");
+ }
+ final RemoteViews view = new RemoteViews(mContext.getPackageName(),
+ R.layout.widget_loading);
+ view.setTextViewText(
+ R.id.loading_text, mContext.getText(R.string.view_more_conversations));
+
+ // Tapping this "More conversations" item should take us to the ConversationList.
+ // However, the list view is primed with an intent to go to the Conversation activity.
+ // Each normal conversation list item sets the fill-in intent with the
+ // ConversationId for that particular conversation. In other words, the only place
+ // we can go is the ConversationActivity. We add an extra here to tell the
+ // ConversationActivity to really take us to the ConversationListActivity.
+ final Intent intent = new Intent();
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST, true);
+ view.setOnClickFillInIntent(R.id.widget_loading, intent);
+ return view;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
+ view.setTextViewText(
+ R.id.loading_text, mContext.getText(R.string.loading_conversations));
+ return view;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ protected int getMainLayoutId() {
+ return R.layout.widget_conversation_list;
+ }
+ }
+
+}
diff --git a/src/com/android/messaging/widget/WidgetConversationProvider.java b/src/com/android/messaging/widget/WidgetConversationProvider.java
new file mode 100644
index 0000000..6ae5614
--- /dev/null
+++ b/src/com/android/messaging/widget/WidgetConversationProvider.java
@@ -0,0 +1,316 @@
+/*
+ * 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.widget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.WidgetPickConversationActivity;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.UiUtils;
+
+public class WidgetConversationProvider extends BaseWidgetProvider {
+ public static final String ACTION_NOTIFY_MESSAGES_CHANGED =
+ "com.android.Bugle.intent.action.ACTION_NOTIFY_MESSAGES_CHANGED";
+
+ public static final int WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE = 1985;
+ public static final int WIDGET_CONVERSATION_REPLY_CODE = 1987;
+
+ // Intent extras
+ public static final String UI_INTENT_EXTRA_RECIPIENT = "recipient";
+ public static final String UI_INTENT_EXTRA_ICON = "icon";
+
+ /**
+ * Update the widget appWidgetId
+ */
+ @Override
+ protected void updateWidget(final Context context, final int appWidgetId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "updateWidget appWidgetId: " + appWidgetId);
+ }
+ if (OsUtil.hasRequiredPermissions()) {
+ rebuildWidget(context, appWidgetId);
+ } else {
+ AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId,
+ UiUtils.getWidgetMissingPermissionView(context));
+ }
+ }
+
+ @Override
+ protected String getAction() {
+ return ACTION_NOTIFY_MESSAGES_CHANGED;
+ }
+
+ @Override
+ protected int getListId() {
+ return R.id.message_list;
+ }
+
+ public static void rebuildWidget(final Context context, final int appWidgetId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " + appWidgetId);
+ }
+ final RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
+ R.layout.widget_conversation);
+ PendingIntent clickIntent;
+ final UIIntents uiIntents = UIIntents.get();
+ if (!isWidgetConfigured(appWidgetId)) {
+ // Widget has not been configured yet. Hide the normal UI elements and show the
+ // configuration view instead.
+ remoteViews.setViewVisibility(R.id.widget_label, View.GONE);
+ remoteViews.setViewVisibility(R.id.message_list, View.GONE);
+ remoteViews.setViewVisibility(R.id.launcher_icon, View.VISIBLE);
+ remoteViews.setViewVisibility(R.id.widget_configuration, View.VISIBLE);
+
+ remoteViews.setOnClickPendingIntent(R.id.widget_configuration,
+ uiIntents.getWidgetPendingIntentForConfigurationActivity(context, appWidgetId));
+
+ // On click intent for Goto Conversation List
+ clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context);
+ remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
+
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "WidgetConversationProvider.rebuildWidget appWidgetId: " +
+ appWidgetId + " going into configure state");
+ }
+ } else {
+ remoteViews.setViewVisibility(R.id.widget_label, View.VISIBLE);
+ remoteViews.setViewVisibility(R.id.message_list, View.VISIBLE);
+ remoteViews.setViewVisibility(R.id.launcher_icon, View.GONE);
+ remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE);
+
+ final String conversationId =
+ WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
+ final boolean isMainThread = Looper.myLooper() == Looper.getMainLooper();
+ // If we're running on the UI thread, we can't do the DB access needed to get the
+ // conversation data. We'll do excute this again off of the UI thread.
+ final ConversationListItemData convData = isMainThread ?
+ null : getConversationData(context, conversationId);
+
+ // Launch an intent to avoid ANRs
+ final Intent intent = new Intent(context, WidgetConversationService.class);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+ remoteViews.setRemoteAdapter(appWidgetId, R.id.message_list, intent);
+
+ remoteViews.setTextViewText(R.id.widget_label, convData != null ?
+ convData.getName() : context.getString(R.string.app_name));
+
+ // On click intent for Goto Conversation List
+ clickIntent = uiIntents.getWidgetPendingIntentForConversationListActivity(context);
+ remoteViews.setOnClickPendingIntent(R.id.widget_goto_conversation_list, clickIntent);
+
+ // Open the conversation when click on header
+ clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context,
+ conversationId, WIDGET_CONVERSATION_REQUEST_CODE);
+ remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
+
+ // On click intent for Conversation
+ // Note: the template intent has to be a "naked" intent without any extras. It turns out
+ // that if the template intent does have extras, those particular extras won't get
+ // replaced by the fill-in intent on each list item.
+ clickIntent = uiIntents.getWidgetPendingIntentForConversationActivity(context,
+ conversationId, WIDGET_CONVERSATION_TEMPLATE_REQUEST_CODE);
+ remoteViews.setPendingIntentTemplate(R.id.message_list, clickIntent);
+
+ if (isMainThread) {
+ // We're running on the UI thread and we couldn't update all the parts of the
+ // widget dependent on ConversationListItemData. However, we have to update
+ // the widget regardless, even with those missing pieces. Here we update the
+ // widget again in the background.
+ SafeAsyncTask.executeOnThreadPool(new Runnable() {
+ @Override
+ public void run() {
+ rebuildWidget(context, appWidgetId);
+ }
+ });
+ }
+ }
+
+ AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews);
+
+ }
+
+ /*
+ * notifyMessagesChanged called when the conversation changes so the widget will
+ * update and reflect the changes
+ */
+ public static void notifyMessagesChanged(final Context context, final String conversationId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "notifyMessagesChanged");
+ }
+ final Intent intent = new Intent(ACTION_NOTIFY_MESSAGES_CHANGED);
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
+ context.sendBroadcast(intent);
+ }
+
+ /*
+ * notifyConversationDeleted is called when a conversation is deleted. Look through all the
+ * widgets and if they're displaying that conversation, force the widget into its
+ * configuration state.
+ */
+ public static void notifyConversationDeleted(final Context context,
+ final String conversationId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "notifyConversationDeleted convId: " + conversationId);
+ }
+
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context,
+ WidgetConversationProvider.class))) {
+ // Retrieve the persisted information for this widget from preferences.
+ final String widgetConvId =
+ WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
+
+ if (widgetConvId == null || widgetConvId.equals(conversationId)) {
+ if (widgetConvId != null) {
+ WidgetPickConversationActivity.deleteConversationIdPref(appWidgetId);
+ }
+ rebuildWidget(context, appWidgetId);
+ }
+ }
+ }
+
+ /*
+ * notifyConversationRenamed is called when a conversation is renamed. Look through all the
+ * widgets and if they're displaying that conversation, force the widget to rebuild itself
+ */
+ public static void notifyConversationRenamed(final Context context,
+ final String conversationId) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "notifyConversationRenamed convId: " + conversationId);
+ }
+
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ for (final int appWidgetId : appWidgetManager.getAppWidgetIds(new ComponentName(context,
+ WidgetConversationProvider.class))) {
+ // Retrieve the persisted information for this widget from preferences.
+ final String widgetConvId =
+ WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
+
+ if (widgetConvId != null && widgetConvId.equals(conversationId)) {
+ rebuildWidget(context, appWidgetId);
+ }
+ }
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "WidgetConversationProvider onReceive intent: " + intent);
+ }
+ final String action = intent.getAction();
+
+ // The base class AppWidgetProvider's onReceive handles the normal widget intents. Here
+ // we're looking for an intent sent by our app when it knows a message has
+ // been sent or received (or a conversation has been read) and is telling the widget it
+ // needs to update.
+ if (getAction().equals(action)) {
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
+ this.getClass()));
+
+ if (appWidgetIds.length == 0) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "WidgetConversationProvider onReceive no widget ids");
+ }
+ return;
+ }
+ // Normally the conversation id points to a specific conversation and we only update
+ // widgets looking at that conversation. When the conversation id is null, that means
+ // there's been a massive change (such as the initial import) and we need to update
+ // every conversation widget.
+ final String conversationId = intent.getExtras()
+ .getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+
+ // Only update the widgets that match the conversation id that changed.
+ for (final int widgetId : appWidgetIds) {
+ // Retrieve the persisted information for this widget from preferences.
+ final String widgetConvId =
+ WidgetPickConversationActivity.getConversationIdPref(widgetId);
+ if (conversationId == null || TextUtils.equals(conversationId, widgetConvId)) {
+ // Update the list portion (i.e. the message list) of the widget
+ appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, getListId());
+ }
+ }
+ } else {
+ super.onReceive(context, intent);
+ }
+ }
+
+ private static ConversationListItemData getConversationData(final Context context,
+ final String conversationId) {
+ if (TextUtils.isEmpty(conversationId)) {
+ return null;
+ }
+ final Uri uri = MessagingContentProvider.buildConversationMetadataUri(conversationId);
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(uri,
+ ConversationListItemData.PROJECTION,
+ null, // selection
+ null, // selection args
+ null); // sort order
+ if (cursor != null && cursor.getCount() > 0) {
+ final ConversationListItemData conv = new ConversationListItemData();
+ cursor.moveToFirst();
+ conv.bind(cursor);
+ return conv;
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void deletePreferences(final int widgetId) {
+ WidgetPickConversationActivity.deleteConversationIdPref(widgetId);
+ }
+
+ /**
+ * When this widget is created, it's created for a particular conversation and that
+ * ConversationId is stored in shared prefs. If the associated conversation is deleted,
+ * the widget doesn't get deleted. Instead, it goes into a "tap to configure" state. This
+ * function determines whether the widget has been configured and has an associated
+ * ConversationId.
+ */
+ public static boolean isWidgetConfigured(final int appWidgetId) {
+ final String conversationId =
+ WidgetPickConversationActivity.getConversationIdPref(appWidgetId);
+ return !TextUtils.isEmpty(conversationId);
+ }
+
+}
diff --git a/src/com/android/messaging/widget/WidgetConversationService.java b/src/com/android/messaging/widget/WidgetConversationService.java
new file mode 100644
index 0000000..4fd3934
--- /dev/null
+++ b/src/com/android/messaging/widget/WidgetConversationService.java
@@ -0,0 +1,521 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Formatter;
+import android.text.style.ForegroundColorSpan;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.media.ImageResource;
+import com.android.messaging.datamodel.media.MediaRequest;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor;
+import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
+import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.datamodel.media.VideoThumbnailRequest;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.AvatarUriUtil;
+import com.android.messaging.util.Dates;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import java.util.List;
+
+public class WidgetConversationService extends RemoteViewsService {
+ private static final String TAG = LogUtil.BUGLE_WIDGET_TAG;
+
+ private static final int IMAGE_ATTACHMENT_SIZE = 400;
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onGetViewFactory intent: " + intent);
+ }
+ return new WidgetConversationFactory(getApplicationContext(), intent);
+ }
+
+ /**
+ * Remote Views Factory for the conversation widget.
+ */
+ private static class WidgetConversationFactory extends BaseWidgetFactory {
+ private ImageResource mImageResource;
+ private String mConversationId;
+
+ public WidgetConversationFactory(Context context, Intent intent) {
+ super(context, intent);
+
+ mConversationId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "BugleFactory intent: " + intent + "widget id: " + mAppWidgetId);
+ }
+ mIconSize = (int) context.getResources()
+ .getDimension(R.dimen.contact_icon_view_normal_size);
+ }
+
+ @Override
+ public void onCreate() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "onCreate");
+ }
+ super.onCreate();
+
+ // If the conversation for this widget has been removed, we want to update the widget to
+ // "Tap to configure" mode.
+ if (!WidgetConversationProvider.isWidgetConfigured(mAppWidgetId)) {
+ WidgetConversationProvider.rebuildWidget(mContext, mAppWidgetId);
+ }
+ }
+
+ @Override
+ protected Cursor doQuery() {
+ if (TextUtils.isEmpty(mConversationId)) {
+ LogUtil.w(TAG, "doQuery no conversation id");
+ return null;
+ }
+ final Uri uri = MessagingContentProvider.buildConversationMessagesUri(mConversationId);
+ if (uri != null) {
+ LogUtil.w(TAG, "doQuery uri: " + uri.toString());
+ }
+ return mContext.getContentResolver().query(uri,
+ ConversationMessageData.getProjection(),
+ null, // where
+ null, // selection args
+ null // sort order
+ );
+ }
+
+ /**
+ * @return the {@link RemoteViews} for a specific position in the list.
+ */
+ @Override
+ public RemoteViews getViewAt(final int originalPosition) {
+ synchronized (sWidgetLock) {
+ // "View more messages" view.
+ if (mCursor == null
+ || (mShouldShowViewMore && originalPosition == 0)) {
+ return getViewMoreItemsView();
+ }
+ // The message cursor is in reverse order for performance reasons.
+ final int position = getCount() - originalPosition - 1;
+ if (!mCursor.moveToPosition(position)) {
+ // If we ever fail to move to a position, return the "View More messages"
+ // view.
+ LogUtil.w(TAG, "Failed to move to position: " + position);
+ return getViewMoreItemsView();
+ }
+
+ final ConversationMessageData message = new ConversationMessageData();
+ message.bind(mCursor);
+
+ // Inflate and fill out the remote view
+ final RemoteViews remoteViews = new RemoteViews(
+ mContext.getPackageName(), message.getIsIncoming() ?
+ R.layout.widget_message_item_incoming :
+ R.layout.widget_message_item_outgoing);
+
+ final boolean hasUnreadMessages = false; //!message.getIsRead();
+
+ // Date
+ remoteViews.setTextViewText(R.id.date, boldifyIfUnread(
+ Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
+ false /*abbreviated*/),
+ hasUnreadMessages));
+
+ // On click intent.
+ final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
+ mConversationId, null /* draft */);
+
+ // Attachments
+ int attachmentStringId = 0;
+ remoteViews.setViewVisibility(R.id.attachmentFrame, View.GONE);
+
+ int scrollToPosition = originalPosition;
+ final int cursorCount = mCursor.getCount();
+ if (cursorCount > MAX_ITEMS_TO_SHOW) {
+ scrollToPosition += cursorCount - MAX_ITEMS_TO_SHOW;
+ }
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getViewAt position: " + originalPosition +
+ " computed position: " + position +
+ " scrollToPosition: " + scrollToPosition +
+ " cursorCount: " + cursorCount +
+ " MAX_ITEMS_TO_SHOW: " + MAX_ITEMS_TO_SHOW);
+ }
+
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, scrollToPosition);
+ if (message.hasAttachments()) {
+ final List<MessagePartData> attachments = message.getAttachments();
+ for (MessagePartData part : attachments) {
+ final boolean videoWithThumbnail = part.isVideo()
+ && (VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()
+ || !message.getIsIncoming());
+ if (part.isImage() || videoWithThumbnail) {
+ final Uri uri = part.getContentUri();
+ remoteViews.setViewVisibility(R.id.attachmentFrame, View.VISIBLE);
+ remoteViews.setViewVisibility(R.id.playButton, part.isVideo() ?
+ View.VISIBLE : View.GONE);
+ remoteViews.setImageViewBitmap(R.id.attachment,
+ getAttachmentBitmap(part));
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_URI ,
+ uri.toString());
+ intent.putExtra(UIIntents.UI_INTENT_EXTRA_ATTACHMENT_TYPE ,
+ part.getContentType());
+ break;
+ } else if (part.isVideo()) {
+ attachmentStringId = R.string.conversation_list_snippet_video;
+ break;
+ }
+ if (part.isAudio()) {
+ attachmentStringId = R.string.conversation_list_snippet_audio_clip;
+ break;
+ }
+ if (part.isVCard()) {
+ attachmentStringId = R.string.conversation_list_snippet_vcard;
+ break;
+ }
+ }
+ }
+
+ remoteViews.setOnClickFillInIntent(message.getIsIncoming() ?
+ R.id.widget_message_item_incoming :
+ R.id.widget_message_item_outgoing,
+ intent);
+
+ // Avatar
+ boolean includeAvatar;
+ if (OsUtil.isAtLeastJB()) {
+ final Bundle options = mAppWidgetManager.getAppWidgetOptions(mAppWidgetId);
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getViewAt BugleWidgetProvider.WIDGET_SIZE_KEY: " +
+ options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY));
+ }
+
+ includeAvatar = options.getInt(BugleWidgetProvider.WIDGET_SIZE_KEY)
+ == BugleWidgetProvider.SIZE_LARGE;
+ } else {
+ includeAvatar = true;
+ }
+
+ // Show the avatar (and shadow) when grande size, otherwise hide it.
+ remoteViews.setViewVisibility(R.id.avatarView, includeAvatar ?
+ View.VISIBLE : View.GONE);
+ remoteViews.setViewVisibility(R.id.avatarShadow, includeAvatar ?
+ View.VISIBLE : View.GONE);
+
+ final Uri avatarUri = AvatarUriUtil.createAvatarUri(
+ message.getSenderProfilePhotoUri(),
+ message.getSenderFullName(),
+ message.getSenderNormalizedDestination(),
+ message.getSenderContactLookupKey());
+
+ remoteViews.setImageViewBitmap(R.id.avatarView, includeAvatar ?
+ getAvatarBitmap(avatarUri) : null);
+
+ String text = message.getText();
+ if (attachmentStringId != 0) {
+ final String attachment = mContext.getString(attachmentStringId);
+ if (!TextUtils.isEmpty(text)) {
+ text += '\n' + attachment;
+ } else {
+ text = attachment;
+ }
+ }
+
+ remoteViews.setViewVisibility(R.id.message, View.VISIBLE);
+ updateViewContent(text, message, remoteViews);
+
+ return remoteViews;
+ }
+ }
+
+ // updateViewContent figures out what to show in the message and date fields based on
+ // the message status. This code came from ConversationMessageView.updateViewContent, but
+ // had to be simplified to work with our simple widget list item.
+ // updateViewContent also builds the accessibility content description for the list item.
+ private void updateViewContent(final String messageText,
+ final ConversationMessageData message,
+ final RemoteViews remoteViews) {
+ int titleResId = -1;
+ int statusResId = -1;
+ boolean showInRed = false;
+ String statusText = null;
+ switch(message.getStatus()) {
+ case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
+ case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
+ case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
+ titleResId = R.string.message_title_downloading;
+ statusResId = R.string.message_status_downloading;
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
+ if (!OsUtil.isSecondaryUser()) {
+ titleResId = R.string.message_title_manual_download;
+ statusResId = R.string.message_status_download;
+ }
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
+ if (!OsUtil.isSecondaryUser()) {
+ titleResId = R.string.message_title_download_failed;
+ statusResId = R.string.message_status_download_error;
+ showInRed = true;
+ }
+ break;
+
+ case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
+ if (!OsUtil.isSecondaryUser()) {
+ titleResId = R.string.message_title_download_failed;
+ statusResId = R.string.message_status_download;
+ showInRed = true;
+ }
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
+ case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
+ statusResId = R.string.message_status_sending;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
+ case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
+ statusResId = R.string.message_status_send_retrying;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
+ statusResId = R.string.message_status_send_failed_emergency_number;
+ showInRed = true;
+ break;
+
+ case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
+ // don't show the error state unless we're the default sms app
+ if (PhoneUtils.getDefault().isDefaultSmsApp()) {
+ statusResId = MmsUtils.mapRawStatusToErrorResourceId(
+ message.getStatus(), message.getRawTelephonyStatus());
+ showInRed = true;
+ break;
+ }
+ // FALL THROUGH HERE
+
+ case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
+ case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
+ default:
+ if (!message.getCanClusterWithNextMessage()) {
+ statusText = Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
+ false /*abbreviated*/).toString();
+ }
+ break;
+ }
+
+ // Build the content description while we're populating the various fields.
+ final StringBuilder description = new StringBuilder();
+ final String separator = mContext.getString(R.string.enumeration_comma);
+ // Sender information
+ final boolean hasPlainTextMessage = !(TextUtils.isEmpty(message.getText()));
+ if (message.getIsIncoming()) {
+ int senderResId = hasPlainTextMessage
+ ? R.string.incoming_text_sender_content_description
+ : R.string.incoming_sender_content_description;
+ description.append(mContext.getString(senderResId, message.getSenderDisplayName()));
+ } else {
+ int senderResId = hasPlainTextMessage
+ ? R.string.outgoing_text_sender_content_description
+ : R.string.outgoing_sender_content_description;
+ description.append(mContext.getString(senderResId));
+ }
+
+ final boolean titleVisible = (titleResId >= 0);
+ if (titleVisible) {
+ final String titleText = mContext.getString(titleResId);
+ remoteViews.setTextViewText(R.id.message, titleText);
+
+ final String mmsInfoText = mContext.getString(
+ R.string.mms_info,
+ Formatter.formatFileSize(mContext, message.getSmsMessageSize()),
+ DateUtils.formatDateTime(
+ mContext,
+ message.getMmsExpiry(),
+ DateUtils.FORMAT_SHOW_DATE |
+ DateUtils.FORMAT_SHOW_TIME |
+ DateUtils.FORMAT_NUMERIC_DATE |
+ DateUtils.FORMAT_NO_YEAR));
+ remoteViews.setTextViewText(R.id.date, mmsInfoText);
+ description.append(separator);
+ description.append(mmsInfoText);
+ } else if (!TextUtils.isEmpty(messageText)) {
+ remoteViews.setTextViewText(R.id.message, messageText);
+ description.append(separator);
+ description.append(messageText);
+ } else {
+ remoteViews.setViewVisibility(R.id.message, View.GONE);
+ }
+
+ final String subjectText = MmsUtils.cleanseMmsSubject(mContext.getResources(),
+ message.getMmsSubject());
+ if (!TextUtils.isEmpty(subjectText)) {
+ description.append(separator);
+ description.append(subjectText);
+ }
+
+ if (statusResId >= 0) {
+ statusText = mContext.getString(statusResId);
+ final Spannable colorStr = new SpannableString(statusText);
+ if (showInRed) {
+ colorStr.setSpan(new ForegroundColorSpan(
+ mContext.getResources().getColor(R.color.timestamp_text_failed)),
+ 0, statusText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ remoteViews.setTextViewText(R.id.date, colorStr);
+ description.append(separator);
+ description.append(colorStr);
+ } else {
+ description.append(separator);
+ description.append(Dates.getWidgetTimeString(message.getReceivedTimeStamp(),
+ false /*abbreviated*/));
+ }
+
+ if (message.hasAttachments()) {
+ final List<MessagePartData> attachments = message.getAttachments();
+ int stringId;
+ for (MessagePartData part : attachments) {
+ if (part.isImage()) {
+ stringId = R.string.conversation_list_snippet_picture;
+ } else if (part.isVideo()) {
+ stringId = R.string.conversation_list_snippet_video;
+ } else if (part.isAudio()) {
+ stringId = R.string.conversation_list_snippet_audio_clip;
+ } else if (part.isVCard()) {
+ stringId = R.string.conversation_list_snippet_vcard;
+ } else {
+ stringId = 0;
+ }
+ if (stringId > 0) {
+ description.append(separator);
+ description.append(mContext.getString(stringId));
+ }
+ }
+ }
+ remoteViews.setContentDescription(message.getIsIncoming() ?
+ R.id.widget_message_item_incoming :
+ R.id.widget_message_item_outgoing, description);
+ }
+
+ private Bitmap getAttachmentBitmap(final MessagePartData part) {
+ UriImageRequestDescriptor descriptor;
+ if (part.isImage()) {
+ descriptor = new MessagePartImageRequestDescriptor(part,
+ IMAGE_ATTACHMENT_SIZE, // desiredWidth
+ IMAGE_ATTACHMENT_SIZE, // desiredHeight
+ true // isStatic
+ );
+ } else if (part.isVideo()) {
+ descriptor = new MessagePartVideoThumbnailRequestDescriptor(part);
+ } else {
+ return null;
+ }
+
+ final MediaRequest<ImageResource> imageRequest =
+ descriptor.buildSyncMediaRequest(mContext);
+ final ImageResource imageResource =
+ MediaResourceManager.get().requestMediaResourceSync(imageRequest);
+ if (imageResource != null && imageResource.getBitmap() != null) {
+ setImageResource(imageResource);
+ return Bitmap.createBitmap(imageResource.getBitmap());
+ } else {
+ releaseImageResource();
+ return null;
+ }
+ }
+
+ /**
+ * @return the "View more messages" view. When the user taps this item, they're
+ * taken to the conversation in Bugle.
+ */
+ @Override
+ protected RemoteViews getViewMoreItemsView() {
+ if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
+ LogUtil.v(TAG, "getViewMoreConversationsView");
+ }
+ final RemoteViews view = new RemoteViews(mContext.getPackageName(),
+ R.layout.widget_loading);
+ view.setTextViewText(
+ R.id.loading_text, mContext.getText(R.string.view_more_messages));
+
+ // Tapping this "More messages" item should take us to the conversation.
+ final Intent intent = UIIntents.get().getIntentForConversationActivity(mContext,
+ mConversationId, null /* draft */);
+ view.setOnClickFillInIntent(R.id.widget_loading, intent);
+ return view;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ final RemoteViews view = new RemoteViews(mContext.getPackageName(),
+ R.layout.widget_loading);
+ view.setTextViewText(
+ R.id.loading_text, mContext.getText(R.string.loading_messages));
+ return view;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 3; // Number of different list items that can be returned -
+ // 1- incoming list item
+ // 2- outgoing list item
+ // 3- more items list item
+ }
+
+ @Override
+ protected int getMainLayoutId() {
+ return R.layout.widget_conversation;
+ }
+
+ private void setImageResource(final ImageResource resource) {
+ if (mImageResource != resource) {
+ // Clear out any information for what is currently used
+ releaseImageResource();
+ mImageResource = resource;
+ }
+ }
+
+ private void releaseImageResource() {
+ if (mImageResource != null) {
+ mImageResource.release();
+ }
+ mImageResource = null;
+ }
+ }
+
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..f3f4752
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := messagingtests
+
+LOCAL_INSTRUMENTATION_FOR := messaging
+
+LOCAL_JACK_ENABLED := disabled
+
+# Matching ../Android.mk
+LOCAL_SDK_VERSION := current
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ mockito-target
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..867d8e1
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.messaging.test" >
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="19"/>
+
+ <application android:label="Messaging Tests" >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:label="Messaging Tests"
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.messaging"
+ android:windowSoftInputMode="stateHidden|adjustResize" />
+</manifest>
diff --git a/tests/src/com/android/messaging/BugleTestCase.java b/tests/src/com/android/messaging/BugleTestCase.java
new file mode 100644
index 0000000..64c6c86
--- /dev/null
+++ b/tests/src/com/android/messaging/BugleTestCase.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/*
+ * Base class for service tests that takes care of housekeeping that is common amongst basic test
+ * cases.
+ */
+public abstract class BugleTestCase extends AndroidTestCase {
+
+ static {
+ // Set flag during loading of test cases to prevent application initialization starting
+ setTestsRunning();
+ }
+
+ public static void setTestsRunning() {
+ BugleApplication.setTestsRunning();
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ TestUtil.testSetup(super.getContext(), this);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ TestUtil.testTeardown(this);
+ }
+
+ @Override
+ public Context getContext() {
+ // This doesn't really get the "application context" - just the fake context
+ // that the factory has been initialized with for each test case.
+ return Factory.get().getApplicationContext();
+ }
+
+ public Context getTestContext() {
+ return super.getContext();
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/FakeContentProvider.java b/tests/src/com/android/messaging/FakeContentProvider.java
new file mode 100644
index 0000000..53c29ba
--- /dev/null
+++ b/tests/src/com/android/messaging/FakeContentProvider.java
@@ -0,0 +1,177 @@
+/*
+ * 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;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.util.SimpleArrayMap;
+import android.text.TextUtils;
+
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.util.LogUtil;
+
+import java.util.ArrayList;
+
+public class FakeContentProvider extends ContentProvider {
+
+ private static class ContentOverride {
+ private final String uri;
+ private final String where;
+ private final String args;
+ private final String[] columns;
+ private final Object[][] data;
+
+ ContentOverride(final String uri, final String where, final String args,
+ final String[] columns, final Object[][] data) {
+ this.uri = uri;
+ this.where = where;
+ this.args = args;
+ this.columns = columns;
+ this.data = data;
+ }
+
+ boolean match(final String uri, final String where, final String[] args) {
+ if (!this.uri.equals(uri) || !TextUtils.equals(this.where, where)) {
+ return false;
+ }
+
+ if (this.args == null || args == null) {
+ return this.args == null && args == null;
+ }
+
+ return this.args.equals(TextUtils.join(";", args));
+ }
+ }
+
+ private final Context mGlobalContext;
+ private final ArrayList<ContentOverride> mOverrides = new ArrayList<ContentOverride>();
+ private final SimpleArrayMap<String, String> mTypes = new SimpleArrayMap<String, String>();
+ private final ContentProviderClient mProvider;
+ private final Uri mUri;
+
+ public FakeContentProvider(final Context context, final Uri uri, final boolean canDelegate) {
+ mGlobalContext = context;
+ mUri = uri;
+ if (canDelegate) {
+ mProvider = mGlobalContext.getContentResolver().acquireContentProviderClient(mUri);
+ } else {
+ mProvider = null;
+ }
+
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = uri.getAuthority();
+
+ this.attachInfo(mGlobalContext, providerInfo);
+ }
+
+ public void addOverrideData(final Uri uri, final String where, final String args,
+ final String[] columns, final Object[][] data) {
+ mOverrides.add(new ContentOverride(uri.toString(), where, args, columns, data));
+ }
+
+ public void addOverrideType(final Uri uri, final String type) {
+ mTypes.put(uri.toString(), type);
+ }
+
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Override
+ public void shutdown() {
+ if (mProvider != null) {
+ mProvider.release();
+ }
+ }
+
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: query " + uri.toString()
+ + " for " + (projection == null ? null : TextUtils.join(",", projection))
+ + " where " + selection
+ + " with " + (selectionArgs == null ? null : TextUtils.join(";", selectionArgs)));
+
+ for(final ContentOverride content : mOverrides) {
+ if (content.match(uri.toString(), selection, selectionArgs)) {
+ return new FakeCursor(projection, content.columns, content.data);
+ }
+ }
+ if (mProvider != null) {
+ try {
+ LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: delgating");
+
+ final Cursor cursor = mProvider.query(uri, projection, selection, selectionArgs,
+ sortOrder);
+
+ LogUtil.w(LogUtil.BUGLE_TAG, "FakeContentProvider: response size "
+ + cursor.getCount() + " contains " + TextUtils.join(",",
+ cursor.getColumnNames()) + " type(0) " + cursor.getType(0));
+
+ return cursor;
+ } catch (final RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getType(final Uri uri) {
+ String type = mTypes.get(uri.toString());
+ if (type == null) {
+ try {
+ type = mProvider.getType(uri);
+ } catch (final RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ return type;
+ }
+
+ @Override
+ public Uri insert(final Uri uri, final ContentValues values) {
+ // TODO: Add code to track insert operations and return correct status
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
+ // TODO: Add code to track delete operations and return correct status
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(final Uri uri, final ContentValues values, final String selection,
+ final String[] selectionArgs) {
+ // TODO: Add code to track update operations and return correct status
+ throw new UnsupportedOperationException();
+ }
+
+ public Bundle call(final String callingPkg, final String method, final String arg,
+ final Bundle extras) {
+ return null;
+ }
+}
diff --git a/tests/src/com/android/messaging/FakeContext.java b/tests/src/com/android/messaging/FakeContext.java
new file mode 100644
index 0000000..671e0f3
--- /dev/null
+++ b/tests/src/com/android/messaging/FakeContext.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class FakeContext extends RenamingDelegatingContext {
+ private static final String TAG = "FakeContext";
+
+ public interface FakeContextHost {
+ public String getServiceClassName();
+ public void startServiceForStub(Intent intent);
+ public void onStartCommandForStub(Intent intent, int flags, int startid);
+ }
+
+ ArrayList<Intent> mStartedIntents;
+ boolean mServiceStarted = false;
+ private final FakeContextHost mService;
+ private final MockContentResolver mContentResolver;
+
+
+ public FakeContext(final Context context, final FakeContextHost service) {
+ super(context, "test_");
+ mService = service;
+ mStartedIntents = new ArrayList<Intent>();
+ mContentResolver = new MockContentResolver();
+ }
+
+ public FakeContext(final Context context) {
+ this(context, null);
+ }
+
+ public ArrayList<Intent> extractIntents() {
+ final ArrayList<Intent> intents = mStartedIntents;
+ mStartedIntents = new ArrayList<Intent>();
+ return intents;
+ }
+
+ @Override
+ public ComponentName startService(final Intent intent) {
+ // Record that a startService occurred with the intent that was passed.
+ Log.d(TAG, "MockContext receiving startService. intent=" + intent.toString());
+ mStartedIntents.add(intent);
+ if (mService == null) {
+ return super.startService(intent);
+ } else if (intent.getComponent() != null &&
+ intent.getComponent().getClassName().equals(mService.getServiceClassName())) {
+ if (!mServiceStarted) {
+ Log.d(TAG, "MockContext first start service.");
+ mService.startServiceForStub(intent);
+ } else {
+ Log.d(TAG, "MockContext not first start service. Calling onStartCommand.");
+ mService.onStartCommandForStub(intent, 0, 0);
+ }
+ mServiceStarted = true;
+ return new ComponentName(this, intent.getComponent().getClassName());
+ }
+ return null;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ // If you want to use a content provider in your test, then you need to add it
+ // explicitly.
+ return mContentResolver;
+ }
+
+ public void addContentProvider(final String name, final ContentProvider provider) {
+ mContentResolver.addProvider(name, provider);
+ }
+
+ public void addDefaultProvider(final Context context, final Uri uri) {
+ final FakeContentProvider provider = new FakeContentProvider(context, uri, true);
+ mContentResolver.addProvider(uri.getAuthority(), provider);
+ }
+
+}
diff --git a/tests/src/com/android/messaging/FakeFactory.java b/tests/src/com/android/messaging/FakeFactory.java
new file mode 100644
index 0000000..41ede77
--- /dev/null
+++ b/tests/src/com/android/messaging/FakeFactory.java
@@ -0,0 +1,288 @@
+/*
+ * 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;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Settings;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.datamodel.ParticipantRefresh.ContactContentObserver;
+import com.android.messaging.datamodel.media.MediaCacheManager;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.sms.ApnDatabase;
+import com.android.messaging.sms.BugleCarrierConfigValuesLoader;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.BuglePrefs;
+import com.android.messaging.util.FakeBugleGservices;
+import com.android.messaging.util.FakeBuglePrefs;
+import com.android.messaging.util.MediaUtil;
+import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.PhoneUtils;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+public class FakeFactory extends Factory {
+ private Context mContext;
+ private FakeContext mFakeContext;
+ private BugleGservices mBugleGservices;
+ private BuglePrefs mBuglePrefs;
+ private DataModel mDataModel;
+ private UIIntents mUIIntents;
+ private MemoryCacheManager mMemoryCacheManager;
+ private MediaResourceManager mMediaResourceManager;
+ private MediaCacheManager mMediaCacheManager;
+ @Mock protected PhoneUtils mPhoneUtils;
+ private MediaUtil mMediaUtil;
+ private BugleCarrierConfigValuesLoader mCarrierConfigValuesLoader;
+
+ private FakeFactory() {
+ }
+
+ public static FakeFactory registerWithFakeContext(final Context context,
+ final FakeContext fake) {
+ // In tests we currently NEVER run the application/factory initialization
+ Assert.isTrue(!sRegistered);
+ Assert.isTrue(!sInitialized);
+
+ final FakeFactory factory = new FakeFactory();
+ Factory.setInstance(factory);
+
+ // At this point Factory is published. Services can now get initialized and depend on
+ // Factory.get().
+ factory.mContext = context;
+ factory.mFakeContext = fake;
+ factory.mMediaResourceManager = Mockito.mock(MediaResourceManager.class);
+ factory.mBugleGservices = new FakeBugleGservices();
+ factory.mBuglePrefs = new FakeBuglePrefs();
+ factory.mPhoneUtils = Mockito.mock(PhoneUtils.class);
+
+ ApnDatabase.initializeAppContext(context);
+
+ Mockito.when(factory.mPhoneUtils.getCanonicalBySystemLocale(Matchers.anyString()))
+ .thenAnswer(new Answer<String>() {
+ @Override
+ public String answer(final InvocationOnMock invocation) throws Throwable {
+ final Object[] args = invocation.getArguments();
+ return (String) args[0];
+ }
+ }
+ );
+ Mockito.when(factory.mPhoneUtils.getCanonicalBySimLocale(Matchers.anyString())).thenAnswer(
+ new Answer<String>() {
+ @Override
+ public String answer(final InvocationOnMock invocation) throws Throwable {
+ final Object[] args = invocation.getArguments();
+ return (String) args[0];
+ }
+ }
+ );
+ Mockito.when(factory.mPhoneUtils.formatForDisplay(Matchers.anyString())).thenAnswer(
+ new Answer<String>() {
+ @Override
+ public String answer(final InvocationOnMock invocation) throws Throwable {
+ return (String) invocation.getArguments()[0];
+ }
+ }
+ );
+ if (OsUtil.isAtLeastL_MR1()) {
+ Mockito.when(factory.mPhoneUtils.toLMr1()).thenReturn(
+ new PhoneUtils.LMr1() {
+ @Override
+ public SubscriptionInfo getActiveSubscriptionInfo() {
+ return null;
+ }
+
+ @Override
+ public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
+ return null;
+ }
+
+ @Override
+ public void registerOnSubscriptionsChangedListener(
+ final SubscriptionManager.OnSubscriptionsChangedListener listener) {
+ }
+ }
+ );
+ }
+ // By default only allow reading of system settings (that we provide) - can delegate
+ // to real provider if required.
+ final FakeContentProvider settings = new FakeContentProvider(context,
+ Settings.System.CONTENT_URI, false);
+ settings.addOverrideData(Settings.System.CONTENT_URI, "name=?", "time_12_24",
+ new String[] { "value" }, new Object[][] { { "12" } });
+ settings.addOverrideData(Settings.System.CONTENT_URI, "name=?", "sound_effects_enabled",
+ new String[] { "value" }, new Object[][] { { 1 } });
+
+ factory.withProvider(Settings.System.CONTENT_URI, settings);
+
+ return factory;
+ }
+
+ public static FakeFactory register(final Context applicationContext) {
+ final FakeContext context = new FakeContext(applicationContext);
+ return registerWithFakeContext(applicationContext, context);
+ }
+
+ public static FakeFactory registerWithoutFakeContext(final Context applicationContext) {
+ return registerWithFakeContext(applicationContext, null);
+ }
+
+ @Override
+ public void onRequiredPermissionsAcquired() {
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return ((mFakeContext != null) ? mFakeContext : mContext );
+ }
+
+ @Override
+ public DataModel getDataModel() {
+ return mDataModel;
+ }
+
+ @Override
+ public BugleGservices getBugleGservices() {
+ return mBugleGservices;
+ }
+
+ @Override
+ public BuglePrefs getApplicationPrefs() {
+ return mBuglePrefs;
+ }
+
+ @Override
+ public BuglePrefs getWidgetPrefs() {
+ return mBuglePrefs;
+ }
+
+ @Override
+ public BuglePrefs getSubscriptionPrefs(final int subId) {
+ return mBuglePrefs;
+ }
+
+ @Override
+ public UIIntents getUIIntents() {
+ return mUIIntents;
+ }
+
+ @Override
+ public MemoryCacheManager getMemoryCacheManager() {
+ return mMemoryCacheManager;
+ }
+
+ @Override
+ public MediaResourceManager getMediaResourceManager() {
+ return mMediaResourceManager;
+ }
+
+ @Override
+ public MediaCacheManager getMediaCacheManager() {
+ return mMediaCacheManager;
+ }
+
+ @Override
+ public PhoneUtils getPhoneUtils(final int subId) {
+ return mPhoneUtils;
+ }
+
+ @Override
+ public MediaUtil getMediaUtil() {
+ return mMediaUtil;
+ }
+
+ @Override
+ public BugleCarrierConfigValuesLoader getCarrierConfigValuesLoader() {
+ return mCarrierConfigValuesLoader;
+ }
+
+ @Override
+ public ContactContentObserver getContactContentObserver() {
+ return null;
+ }
+
+ @Override
+ public void reclaimMemory() {
+ }
+
+ @Override
+ public void onActivityResume() {
+ }
+
+ public FakeFactory withDataModel(final DataModel dataModel) {
+ this.mDataModel = dataModel;
+ return this;
+ }
+
+ public FakeFactory withUIIntents(final UIIntents uiIntents) {
+ this.mUIIntents = uiIntents;
+ return this;
+ }
+
+ public FakeFactory withMemoryCacheManager(final MemoryCacheManager memoryCacheManager) {
+ this.mMemoryCacheManager = memoryCacheManager;
+ return this;
+ }
+
+ public FakeFactory withBugleGservices(final BugleGservices bugleGservices) {
+ this.mBugleGservices = bugleGservices;
+ return this;
+ }
+
+ public FakeFactory withMediaCacheManager(final MediaCacheManager mediaCacheManager) {
+ this.mMediaCacheManager = mediaCacheManager;
+ return this;
+ }
+
+ public FakeFactory withProvider(final Uri uri, final ContentProvider provider) {
+ if (mFakeContext != null) {
+ mFakeContext.addContentProvider(uri.getAuthority(), provider);
+ }
+ return this;
+ }
+
+ public FakeFactory withDefaultProvider(final Uri uri) {
+ if (mFakeContext != null) {
+ mFakeContext.addDefaultProvider(this.mContext, uri);
+ }
+ return this;
+ }
+
+ public FakeFactory withMediaUtil(final MediaUtil mediaUtil) {
+ this.mMediaUtil = mediaUtil;
+ return this;
+ }
+
+ public FakeFactory withCarrierConfigValuesLoader(
+ final BugleCarrierConfigValuesLoader carrierConfigValuesLoader) {
+ this.mCarrierConfigValuesLoader = carrierConfigValuesLoader;
+ return this;
+ }
+}
diff --git a/tests/src/com/android/messaging/TestUtil.java b/tests/src/com/android/messaging/TestUtil.java
new file mode 100644
index 0000000..77a8450
--- /dev/null
+++ b/tests/src/com/android/messaging/TestUtil.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.PowerManager;
+import android.test.InstrumentationTestCase;
+
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.widget.BugleWidgetProvider;
+import com.android.messaging.widget.WidgetConversationProvider;
+
+import junit.framework.TestCase;
+
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Helpers that can be called from all test base classes to 'reset' state and prevent as much as
+ * possible having side effects leak from one test to another.
+ */
+public class TestUtil {
+ public static void testSetup(final Context context, final TestCase testCase) {
+ haltIfTestsAreNotAbleToRun(context);
+
+ // Workaround to get mockito to work.
+ // See https://code.google.com/p/dexmaker/issues/detail?id=2. TODO: Apparently
+ // solvable by using a different runner.
+ System.setProperty("dexmaker.dexcache",
+ context.getCacheDir().getPath());
+
+ // Initialize @Mock objects.
+ MockitoAnnotations.initMocks(testCase);
+
+ // Tests have to explicitly override this
+ Factory.setInstance(null);
+ }
+
+ public static void testTeardown(final TestCase testCase) {
+ if (testCase instanceof InstrumentationTestCase) {
+ // Make sure the test case is finished running or we'll get NPEs when accessing
+ // Fragment.get()
+ ((InstrumentationTestCase) testCase).getInstrumentation().waitForIdleSync();
+ }
+ Factory.setInstance(null);
+ }
+
+ private static void haltIfTestsAreNotAbleToRun(final Context context) {
+ final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ if (!pm.isScreenOn()) {
+ // Ideally we could turn it on for you using the WindowManager, but we currently run
+ // the tests independently of the activity life cycle.
+ LogUtil.wtf(LogUtil.BUGLE_TAG, "You need to turn on your screen to run tests!");
+ }
+
+ final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int [] conversationWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,
+ WidgetConversationProvider.class));
+ int [] conversationListWidgetIds = appWidgetManager.getAppWidgetIds(new
+ ComponentName(context, BugleWidgetProvider.class));
+
+ if ((conversationWidgetIds.length > 0) || (conversationListWidgetIds.length > 0)) {
+ // Currently widgets asynchronously access our content providers and singletons which
+ // interacts badly with our test setup and tear down.
+ LogUtil.wtf(LogUtil.BUGLE_TAG, "You currently can't reliably run unit tests" +
+ " with a Messaging widget on your desktop!");
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/BindingTest.java b/tests/src/com/android/messaging/datamodel/BindingTest.java
new file mode 100644
index 0000000..9205657
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/BindingTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.datamodel;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.datamodel.binding.BindableData;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.binding.ImmutableBindingRef;
+
+/**
+ * Test binding
+ */
+@SmallTest
+public class BindingTest extends BugleTestCase {
+ private static final Object TEST_DATA_ID = "myDataId";
+ private static final Object YOUR_DATA_ID = "yourDataId";
+
+ public void testBindingStartsUnbound() {
+ final Binding<TestBindableData> binding = BindingBase.createBinding(this);
+ assertNull(binding.getBindingId());
+ }
+
+ public void testDataStartsUnbound() {
+ final TestBindableData data = new TestBindableData(TEST_DATA_ID);
+ assertFalse(data.isBound());
+ }
+
+ public void testBindingUpdatesDataAndBindee() {
+ final Binding<TestBindableData> binding = BindingBase.createBinding(this);
+ final TestBindableData data = new TestBindableData(TEST_DATA_ID);
+ binding.bind(data);
+ assertTrue(binding.isBound());
+ assertEquals(binding.getData(), data);
+ assertTrue(data.isBound(binding.getBindingId()));
+ assertFalse(data.isBound("SomeRandomString"));
+ assertNotNull(binding.getBindingId());
+ assertFalse(data.mListenersUnregistered);
+ }
+
+ public void testRebindingFails() {
+ final Binding<TestBindableData> binding = BindingBase.createBinding(this);
+ final TestBindableData yours = new TestBindableData(YOUR_DATA_ID);
+ binding.bind(yours);
+ assertEquals(binding.getData(), yours);
+ assertTrue(yours.isBound(binding.getBindingId()));
+ final TestBindableData data = new TestBindableData(TEST_DATA_ID);
+ try {
+ binding.bind(data);
+ fail();
+ } catch (final IllegalStateException e) {
+ }
+ assertTrue(binding.isBound());
+ assertEquals(binding.getData(), yours);
+ assertTrue(yours.isBound(binding.getBindingId()));
+ }
+
+ public void testUnbindingClearsDataAndBindee() {
+ final Binding<TestBindableData> binding = BindingBase.createBinding(this);
+ final TestBindableData data = new TestBindableData(TEST_DATA_ID);
+ binding.bind(data);
+ assertTrue(data.isBound(binding.getBindingId()));
+ assertTrue(binding.isBound());
+ binding.unbind();
+ try {
+ final TestBindableData other = binding.getData();
+ fail();
+ } catch (final IllegalStateException e) {
+ }
+ assertFalse(data.isBound());
+ assertNull(binding.getBindingId());
+ assertTrue(data.mListenersUnregistered);
+ }
+
+ public void testUnbindingAndRebinding() {
+ final Binding<TestBindableData> binding = BindingBase.createBinding(this);
+ final TestBindableData yours = new TestBindableData(YOUR_DATA_ID);
+ binding.bind(yours);
+ assertEquals(binding.getData(), yours);
+ assertTrue(yours.isBound(binding.getBindingId()));
+ binding.unbind();
+ assertFalse(yours.isBound());
+ assertNull(binding.getBindingId());
+
+ final TestBindableData data = new TestBindableData(TEST_DATA_ID);
+ binding.bind(data);
+ assertEquals(binding.getData(), data);
+ assertTrue(data.isBound(binding.getBindingId()));
+ assertFalse(data.isBound("SomeRandomString"));
+ assertTrue(binding.isBound());
+ assertNotNull(binding.getBindingId());
+ }
+
+ public void testBindingReference() {
+ final Binding<TestBindableData> binding = BindingBase.createBinding(this);
+ final TestBindableData data = new TestBindableData(TEST_DATA_ID);
+ binding.bind(data);
+ assertEquals(binding.getData(), data);
+ assertTrue(data.isBound(binding.getBindingId()));
+
+ final ImmutableBindingRef<TestBindableData> bindingRef =
+ BindingBase.createBindingReference(binding);
+ assertEquals(bindingRef.getData(), data);
+ assertTrue(data.isBound(bindingRef.getBindingId()));
+
+ binding.unbind();
+ assertFalse(binding.isBound());
+ assertNull(binding.getBindingId());
+ assertFalse(bindingRef.isBound());
+ assertNull(bindingRef.getBindingId());
+ }
+
+ static class TestBindableData extends BindableData {
+ private final Object mDataId;
+ public boolean mListenersUnregistered;
+
+ public TestBindableData(final Object dataId) {
+ mDataId = dataId;
+ mListenersUnregistered = false;
+ }
+
+ @Override
+ public void unregisterListeners() {
+ mListenersUnregistered = true;
+ }
+
+ @Override
+ public boolean isBound() {
+ return super.isBound();
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java
new file mode 100644
index 0000000..6d0aaa3
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/BitmapPoolTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.datamodel;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+public class BitmapPoolTest extends BugleTestCase {
+ private static final int POOL_SIZE = 5;
+ private static final int IMAGE_DIM = 1;
+ private static final String NAME = "BitmapPoolTest";
+
+ @Mock private MemoryCacheManager mockMemoryCacheManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getTestContext())
+ .withMemoryCacheManager(mockMemoryCacheManager);
+ }
+
+ private Set<Bitmap> fillPoolAndGetPoolContents(final BitmapPool pool, final int width,
+ final int height) {
+ final Set<Bitmap> returnedBitmaps = new HashSet<Bitmap>();
+ for (int i = 0; i < POOL_SIZE; i++) {
+ final Bitmap temp = pool.createOrReuseBitmap(width, height);
+ assertFalse(returnedBitmaps.contains(temp));
+ returnedBitmaps.add(temp);
+ }
+ for (final Bitmap b : returnedBitmaps) {
+ pool.reclaimBitmap(b);
+ }
+ assertTrue(pool.isFull(width, height));
+ return returnedBitmaps;
+ }
+
+ public void testCreateAndPutBackInPoolTest() {
+ final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME);
+ final Bitmap bitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM);
+ assertFalse(bitmap.isRecycled());
+ assertFalse(pool.isFull(IMAGE_DIM, IMAGE_DIM));
+ pool.reclaimBitmap(bitmap);
+
+ // Don't recycle because the pool isn't full yet.
+ assertFalse(bitmap.isRecycled());
+ }
+
+ public void testCreateBeyondFullAndCheckReuseTest() {
+ final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME);
+ final Set<Bitmap> returnedBitmaps =
+ fillPoolAndGetPoolContents(pool, IMAGE_DIM, IMAGE_DIM);
+ final Bitmap overflowBitmap = pool.createOrReuseBitmap(IMAGE_DIM, IMAGE_DIM);
+ assertFalse(overflowBitmap.isRecycled());
+ assertTrue(returnedBitmaps.contains(overflowBitmap));
+ }
+
+ /**
+ * Make sure that we have the correct options to create mutable for bitmap pool reuse.
+ */
+ public void testAssertBitmapOptionsAreMutable() {
+ final BitmapFactory.Options options =
+ BitmapPool.getBitmapOptionsForPool(false, IMAGE_DIM, IMAGE_DIM);
+ assertTrue(options.inMutable);
+ }
+
+ public void testDecodeFromResourceBitmap() {
+ final BitmapPool pool = new BitmapPool(POOL_SIZE, NAME);
+ final BitmapFactory.Options options =
+ BitmapPool.getBitmapOptionsForPool(true, IMAGE_DIM, IMAGE_DIM);
+ final Resources resources = getContext().getResources();
+ final Bitmap resourceBitmap = pool.decodeSampledBitmapFromResource(
+ R.drawable.msg_bubble_incoming, resources, options, IMAGE_DIM, IMAGE_DIM);
+ assertNotNull(resourceBitmap);
+ assertTrue(resourceBitmap.getByteCount() > 0);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java
new file mode 100644
index 0000000..42eb647
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/BugleServiceTestCase.java
@@ -0,0 +1,52 @@
+/*
+ * 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.datamodel;
+
+import android.app.Service;
+import android.test.ServiceTestCase;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.TestUtil;
+
+
+/*
+ * Base class for service tests that takes care of housekeeping that is commong amongst our service
+ * test case.
+ */
+public abstract class BugleServiceTestCase<T extends Service> extends ServiceTestCase<T> {
+
+ static {
+ // Set flag during loading of test cases to prevent application initialization starting
+ BugleTestCase.setTestsRunning();
+ }
+
+ public BugleServiceTestCase(final Class<T> serviceClass) {
+ super(serviceClass);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ TestUtil.testSetup(getContext(), this);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ TestUtil.testTeardown(this);
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/datamodel/ConversationListTest.java b/tests/src/com/android/messaging/datamodel/ConversationListTest.java
new file mode 100644
index 0000000..f45696b
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/ConversationListTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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.datamodel;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+
+@SmallTest
+public class ConversationListTest extends BugleTestCase {
+ public void testTesting() {
+ assertTrue(true);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/DataModelTest.java b/tests/src/com/android/messaging/datamodel/DataModelTest.java
new file mode 100644
index 0000000..71723a4
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/DataModelTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.datamodel;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
+
+import org.mockito.Mock;
+
+public class DataModelTest extends BugleTestCase {
+
+ DataModel dataModel;
+ @Mock protected ConversationDataListener mockConversationDataListener;
+ @Mock protected ConversationListDataListener mockConversationListDataListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ dataModel = new DataModelImpl(getTestContext());
+ FakeFactory.register(mContext)
+ .withDataModel(dataModel);
+ }
+
+ @SmallTest
+ public void testCreateConversationList() {
+ final ConversationListData list = dataModel.createConversationListData(getContext(),
+ mockConversationListDataListener, true);
+ assertTrue(list instanceof ConversationListData);
+ final ConversationData conv = dataModel.createConversationData(getContext(),
+ mockConversationDataListener, "testConversation");
+ assertTrue(conv instanceof ConversationData);
+ }
+
+ private static final String FOCUSED_CONV_ID = "focused_conv_id";
+
+ @SmallTest
+ public void testFocusedConversationIsObservable() {
+ dataModel.setFocusedConversation(FOCUSED_CONV_ID);
+ assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID));
+ dataModel.setFocusedConversation(null);
+ assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID));
+ }
+
+ @SmallTest
+ public void testConversationIsObservableInList() {
+ dataModel.setConversationListScrolledToNewestConversation(true);
+ assertTrue(dataModel.isNewMessageObservable(FOCUSED_CONV_ID));
+ dataModel.setConversationListScrolledToNewestConversation(false);
+ assertFalse(dataModel.isNewMessageObservable(FOCUSED_CONV_ID));
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/FakeCursor.java b/tests/src/com/android/messaging/datamodel/FakeCursor.java
new file mode 100644
index 0000000..59d1b89
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/FakeCursor.java
@@ -0,0 +1,161 @@
+/*
+ * 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.datamodel;
+
+import android.os.Bundle;
+import android.test.mock.MockCursor;
+
+import java.util.ArrayList;
+
+/**
+ * A simple in memory fake cursor that can be used for UI tests.
+ */
+public class FakeCursor extends MockCursor {
+ private final ArrayList<Integer> mProjection;
+ private final String[] mColumnNamesOfData;
+ private final Object[][] mData;
+ private int mIndex;
+
+ public FakeCursor(final String[] projection, final String[] columnNames,
+ final Object[][] data) {
+ mColumnNamesOfData = columnNames;
+ mData = data;
+ mIndex = -1;
+ mProjection = new ArrayList<Integer>(projection.length);
+ for (final String column : projection) {
+ mProjection.add(getColumnIndex(column));
+ }
+ }
+
+ public Object getAt(final String columnName, final int row) {
+ final int dataIdx = getColumnIndex(columnName);
+ return (dataIdx < 0 || row < 0 || row >= mData.length) ? 0 : mData[row][dataIdx];
+ }
+
+ @Override
+ public int getCount() {
+ return mData.length;
+ }
+
+ @Override
+ public boolean isFirst() {
+ return mIndex == 0;
+ }
+
+ @Override
+ public boolean isLast() {
+ return mIndex == mData.length - 1;
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ if (mData.length == 0) {
+ return false;
+ }
+ mIndex = 0;
+ return true;
+ }
+
+ @Override
+ public boolean moveToPosition(final int position) {
+ if (position < 0 || position >= mData.length) {
+ return false;
+ }
+ mIndex = position;
+ return true;
+ }
+
+ @Override
+ public int getPosition() {
+ return mIndex;
+ }
+
+ @Override
+ public boolean moveToPrevious() {
+ if (mIndex <= 0) {
+ return false;
+ }
+ mIndex--;
+ return true;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ if (mIndex == mData.length - 1) {
+ return false;
+ }
+
+ mIndex++;
+ return true;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return mColumnNamesOfData.length;
+ }
+
+ @Override
+ public int getColumnIndex(final String columnName) {
+ for (int i = 0 ; i < mColumnNamesOfData.length ; i++) {
+ if (mColumnNamesOfData[i].equals(columnName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(final String columnName) {
+ final int result = getColumnIndex(columnName);
+ if (result == -1) {
+ throw new IllegalArgumentException();
+ }
+
+ return result;
+ }
+
+ @Override
+ public String getString(final int columnIndex) {
+ final int dataIdx = mProjection.get(columnIndex);
+ final Object obj = (dataIdx < 0 ? null : mData[mIndex][dataIdx]);
+ return (obj == null ? null : obj.toString());
+ }
+
+ @Override
+ public int getInt(final int columnIndex) {
+ final int dataIdx = mProjection.get(columnIndex);
+ return (dataIdx < 0 ? 0 : (Integer) mData[mIndex][dataIdx]);
+ }
+
+ @Override
+ public long getLong(final int columnIndex) {
+ final int dataIdx = mProjection.get(columnIndex);
+ return (dataIdx < 0 ? 0 : (Long) mData[mIndex][dataIdx]);
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public Bundle getExtras() { return null; }
+}
diff --git a/tests/src/com/android/messaging/datamodel/FakeDataModel.java b/tests/src/com/android/messaging/datamodel/FakeDataModel.java
new file mode 100644
index 0000000..5e80eab
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/FakeDataModel.java
@@ -0,0 +1,257 @@
+/*
+ * 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.datamodel;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.test.RenamingDelegatingContext;
+
+import com.android.messaging.datamodel.action.ActionService;
+import com.android.messaging.datamodel.action.BackgroundWorker;
+import com.android.messaging.datamodel.data.BlockedParticipantsData;
+import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.ContactPickerData;
+import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.datamodel.data.LaunchConversationData;
+import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.ParticipantListItemData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData;
+import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
+import com.android.messaging.datamodel.data.PeopleOptionsItemData;
+import com.android.messaging.datamodel.data.SettingsData;
+import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
+import com.android.messaging.datamodel.data.SubscriptionListData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.datamodel.data.VCardContactItemData;
+import com.android.messaging.util.ConnectivityUtil;
+
+public class FakeDataModel extends DataModel {
+ private BackgroundWorker mWorker;
+ private ActionService mActionService;
+ private final DatabaseHelper mDatabaseHelper;
+ private ConversationListData mConversationListData;
+ private ContactPickerData mContactPickerData;
+ private MediaPickerData mMediaPickerData;
+ private PeopleAndOptionsData mPeopleAndOptionsData;
+ private ConnectivityUtil mConnectivityUtil;
+ private SyncManager mSyncManager;
+ private SettingsData mSettingsData;
+ private DraftMessageData mDraftMessageData;
+
+ public FakeDataModel(final Context context) {
+ super();
+ if (context instanceof RenamingDelegatingContext) {
+ mDatabaseHelper = DatabaseHelper.getNewInstanceForTest(context);
+ } else {
+ mDatabaseHelper = null;
+ }
+ }
+
+ @Override
+ public BackgroundWorker getBackgroundWorkerForActionService() {
+ return mWorker;
+ }
+
+ public FakeDataModel withBackgroundWorkerForActionService(final BackgroundWorker worker) {
+ mWorker = worker;
+ return this;
+ }
+
+ public FakeDataModel withActionService(final ActionService ActionService) {
+ mActionService = ActionService;
+ return this;
+ }
+
+ public FakeDataModel withConversationListData(final ConversationListData conversationListData) {
+ mConversationListData = conversationListData;
+ return this;
+ }
+
+ public FakeDataModel withContactPickerData(final ContactPickerData contactPickerData) {
+ mContactPickerData = contactPickerData;
+ return this;
+ }
+
+ public FakeDataModel withMediaPickerData(final MediaPickerData mediaPickerData) {
+ mMediaPickerData = mediaPickerData;
+ return this;
+ }
+
+ public FakeDataModel withConnectivityUtil(final ConnectivityUtil connectivityUtil) {
+ mConnectivityUtil = connectivityUtil;
+ return this;
+ }
+
+ public FakeDataModel withSyncManager(final SyncManager syncManager) {
+ mSyncManager = syncManager;
+ return this;
+ }
+
+ public FakeDataModel withPeopleAndOptionsData(final PeopleAndOptionsData peopleAndOptionsData) {
+ mPeopleAndOptionsData = peopleAndOptionsData;
+ return this;
+ }
+
+ public FakeDataModel withSettingsData(final SettingsData settingsData) {
+ mSettingsData = settingsData;
+ return this;
+ }
+
+ public FakeDataModel withDraftMessageData(final DraftMessageData draftMessageData) {
+ mDraftMessageData = draftMessageData;
+ return this;
+ }
+
+ @Override
+ public ConversationListData createConversationListData(final Context context,
+ final ConversationListDataListener listener, final boolean archivedMode) {
+ return mConversationListData;
+ }
+
+ @Override
+ public ConversationData createConversationData(final Context context,
+ final ConversationDataListener listener, final String conversationId) {
+ throw new IllegalStateException("Add withXXX or mock this method");
+ }
+
+ @Override
+ public ContactListItemData createContactListItemData() {
+ // This is a lightweight data holder object for each individual list item for which
+ // we don't perform any data request, so we can directly return a new instance.
+ return new ContactListItemData();
+ }
+
+ @Override
+ public ContactPickerData createContactPickerData(final Context context,
+ final ContactPickerDataListener listener) {
+ return mContactPickerData;
+ }
+
+ @Override
+ public MediaPickerData createMediaPickerData(final Context context) {
+ return mMediaPickerData;
+ }
+
+ @Override
+ public GalleryGridItemData createGalleryGridItemData() {
+ // This is a lightweight data holder object for each individual grid item for which
+ // we don't perform any data request, so we can directly return a new instance.
+ return new GalleryGridItemData();
+ }
+
+ @Override
+ public LaunchConversationData createLaunchConversationData(
+ final LaunchConversationDataListener listener) {
+ return new LaunchConversationData(listener);
+ }
+
+ @Override
+ public PeopleOptionsItemData createPeopleOptionsItemData(final Context context) {
+ return new PeopleOptionsItemData(context);
+ }
+
+ @Override
+ public PeopleAndOptionsData createPeopleAndOptionsData(final String conversationId,
+ final Context context, final PeopleAndOptionsDataListener listener) {
+ return mPeopleAndOptionsData;
+ }
+
+ @Override
+ public VCardContactItemData createVCardContactItemData(final Context context,
+ final MessagePartData data) {
+ return new VCardContactItemData(context, data);
+ }
+
+ @Override
+ public VCardContactItemData createVCardContactItemData(final Context context,
+ final Uri vCardUri) {
+ return new VCardContactItemData(context, vCardUri);
+ }
+
+ @Override
+ public ParticipantListItemData createParticipantListItemData(
+ final ParticipantData participant) {
+ return new ParticipantListItemData(participant);
+ }
+
+ @Override
+ public SubscriptionListData createSubscriptonListData(Context context) {
+ return new SubscriptionListData(context);
+ }
+
+ @Override
+ public SettingsData createSettingsData(Context context, SettingsDataListener listener) {
+ return mSettingsData;
+ }
+
+ @Override
+ public DraftMessageData createDraftMessageData(String conversationId) {
+ return mDraftMessageData;
+ }
+
+ @Override
+ public ActionService getActionService() {
+ return mActionService;
+ }
+
+ @Override
+ public ConnectivityUtil getConnectivityUtil() {
+ return mConnectivityUtil;
+ }
+
+ @Override
+ public SyncManager getSyncManager() {
+ return mSyncManager;
+ }
+
+ @Override
+ public DatabaseWrapper getDatabase() {
+ // Note this will crash unless the application context is redirected...
+ // This is by design so that tests do not inadvertently use the real database
+ return mDatabaseHelper.getDatabase();
+ }
+
+ @Override
+ void onCreateTables(final SQLiteDatabase db) {
+ TestDataFactory.createTestData(db);
+ }
+
+ @Override
+ public void onActivityResume() {
+ }
+
+ @Override
+ public void onApplicationCreated() {
+ }
+
+ @Override
+ public BlockedParticipantsData createBlockedParticipantsData(Context context,
+ BlockedParticipantsDataListener listener) {
+ return new BlockedParticipantsData(context, listener);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java
new file mode 100644
index 0000000..6b78a07
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/FrequentContactsCursorBuilderTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.datamodel;
+
+import android.database.Cursor;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.util.ContactUtil;
+
+@SmallTest
+public class FrequentContactsCursorBuilderTest extends BugleTestCase {
+
+ private void verifyBuiltCursor(final Cursor expected, final Cursor actual) {
+ final int rowCount = expected.getCount();
+ final int columnCount = expected.getColumnCount();
+ assertEquals(rowCount, actual.getCount());
+ assertEquals(columnCount, actual.getColumnCount());
+ for (int i = 0; i < rowCount; i++) {
+ expected.moveToPosition(i);
+ actual.moveToPosition(i);
+ assertEquals(expected.getLong(ContactUtil.INDEX_DATA_ID),
+ actual.getLong(ContactUtil.INDEX_DATA_ID));
+ assertEquals(expected.getLong(ContactUtil.INDEX_CONTACT_ID),
+ actual.getLong(ContactUtil.INDEX_CONTACT_ID));
+ assertEquals(expected.getString(ContactUtil.INDEX_LOOKUP_KEY),
+ actual.getString(ContactUtil.INDEX_LOOKUP_KEY));
+ assertEquals(expected.getString(ContactUtil.INDEX_DISPLAY_NAME),
+ actual.getString(ContactUtil.INDEX_DISPLAY_NAME));
+ assertEquals(expected.getString(ContactUtil.INDEX_PHOTO_URI),
+ actual.getString(ContactUtil.INDEX_PHOTO_URI));
+ assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL),
+ actual.getString(ContactUtil.INDEX_PHONE_EMAIL));
+ assertEquals(expected.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE),
+ actual.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE));
+ assertEquals(expected.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL),
+ actual.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL));
+ }
+ }
+
+ public void testIncompleteBuild() {
+ final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder();
+ assertNull(builder.build());
+ assertNull(builder.setFrequents(TestDataFactory.getStrequentContactsCursor()).build());
+ builder.resetBuilder();
+ assertNull(builder.build());
+ assertNull(builder.setAllContacts(TestDataFactory.getAllContactListCursor()).build());
+ }
+
+ public void testBuildOnce() {
+ final Cursor cursor = new FrequentContactsCursorBuilder()
+ .setAllContacts(TestDataFactory.getAllContactListCursor())
+ .setFrequents(TestDataFactory.getStrequentContactsCursor())
+ .build();
+ assertNotNull(cursor);
+ verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), cursor);
+ }
+
+ public void testBuildTwice() {
+ final FrequentContactsCursorBuilder builder = new FrequentContactsCursorBuilder();
+ final Cursor firstCursor = builder
+ .setAllContacts(TestDataFactory.getAllContactListCursor())
+ .setFrequents(TestDataFactory.getStrequentContactsCursor())
+ .build();
+ assertNotNull(firstCursor);
+ builder.resetBuilder();
+ assertNull(builder.build());
+
+ final Cursor secondCursor = builder
+ .setAllContacts(TestDataFactory.getAllContactListCursor())
+ .setFrequents(TestDataFactory.getStrequentContactsCursor())
+ .build();
+ assertNotNull(firstCursor);
+ verifyBuiltCursor(TestDataFactory.getFrequentContactListCursor(), secondCursor);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java
new file mode 100644
index 0000000..5da9e27
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/MemoryCacheManagerTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.datamodel;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.datamodel.MemoryCacheManager.MemoryCache;
+
+import org.mockito.Mockito;
+
+@SmallTest
+public class MemoryCacheManagerTest extends AndroidTestCase {
+
+ public void testRegisterCachesGetReclaimed() {
+ final MemoryCache mockMemoryCache = Mockito.mock(MemoryCache.class);
+ final MemoryCache otherMockMemoryCache = Mockito.mock(MemoryCache.class);
+ final MemoryCacheManager memoryCacheManager = new MemoryCacheManager();
+
+ memoryCacheManager.registerMemoryCache(mockMemoryCache);
+ memoryCacheManager.registerMemoryCache(otherMockMemoryCache);
+ memoryCacheManager.reclaimMemory();
+ memoryCacheManager.unregisterMemoryCache(otherMockMemoryCache);
+ memoryCacheManager.reclaimMemory();
+
+ Mockito.verify(mockMemoryCache, Mockito.times(2)).reclaim();
+ Mockito.verify(otherMockMemoryCache, Mockito.times(1)).reclaim();
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java
new file mode 100644
index 0000000..cd1d6c7
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/ParticipantRefreshTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.datamodel;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeContentProvider;
+import com.android.messaging.FakeContext;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery;
+import com.android.messaging.util.ContactUtil;
+
+import org.junit.Assert;
+
+/**
+ * Utility class for testing ParticipantRefresh class for different scenarios.
+ */
+@SmallTest
+public class ParticipantRefreshTest extends BugleTestCase {
+ private FakeContext mContext;
+ FakeFactory mFakeFactory;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mContext = new FakeContext(getTestContext());
+
+ final ContentProvider provider = new MessagingContentProvider();
+ provider.attachInfo(mContext, null);
+ mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider);
+
+ final FakeDataModel fakeDataModel = new FakeDataModel(mContext);
+ mFakeFactory = FakeFactory.registerWithFakeContext(getTestContext(), mContext)
+ .withDataModel(fakeDataModel);
+ }
+
+ /**
+ * Add some phonelookup result into take PhoneLookup content provider. This will be
+ * used for doing phone lookup during participant refresh.
+ */
+ private void addPhoneLookup(final String phone, final Object[][] lookupResult) {
+ final Uri uri = ContactUtil.lookupPhone(mContext, phone).getUri();
+ final FakeContentProvider phoneLookup = new FakeContentProvider(mContext,
+ uri, false);
+ phoneLookup.addOverrideData(uri, null, null, ContactUtil.PhoneLookupQuery.PROJECTION,
+ lookupResult);
+ mFakeFactory.withProvider(uri, phoneLookup);
+ }
+
+ /**
+ * Add some participant to test database.
+ */
+ private void addParticipant(final String normalizedDestination, final long contactId,
+ final String name, final String photoUrl) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final ContentValues values = new ContentValues();
+
+ values.put(ParticipantColumns.NORMALIZED_DESTINATION, normalizedDestination);
+ values.put(ParticipantColumns.CONTACT_ID, contactId);
+ values.put(ParticipantColumns.FULL_NAME, name);
+ values.put(ParticipantColumns.PROFILE_PHOTO_URI, photoUrl);
+
+ db.beginTransaction();
+ try {
+ db.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, values);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Verify that participant in the database has expected contacdtId, name and photoUrl fields.
+ */
+ private void verifyParticipant(final String normalizedDestination, final long contactId,
+ final String name, final String photoUrl) {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ db.beginTransaction();
+ try {
+ final String selection = ParticipantColumns.NORMALIZED_DESTINATION + "=?";
+ final String[] selectionArgs = new String[] { normalizedDestination };
+
+ final Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null);
+
+ if (cursor == null || cursor.getCount() != 1) {
+ Assert.fail("Should have participants for:" + normalizedDestination);
+ return;
+ }
+
+ cursor.moveToFirst();
+ final int currentContactId = cursor.getInt(ParticipantsQuery.INDEX_CONTACT_ID);
+ final String currentName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
+ final String currentPhotoUrl =
+ cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
+ if (currentContactId != contactId) {
+ Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination +
+ " expected=" + contactId + " actual=" + currentContactId);
+ return;
+ }
+
+ if (!TextUtils.equals(currentName, name)) {
+ Assert.fail("Name doesn't match. normalizedNumber=" + normalizedDestination +
+ " expected=" + name + " actual=" + currentName);
+ return;
+ }
+
+ if (!TextUtils.equals(currentPhotoUrl, photoUrl)) {
+ Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination +
+ " expected=" + photoUrl + " actual=" + currentPhotoUrl);
+ return;
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Verify that incremental refresh will resolve previously not resolved participants.
+ */
+ public void testIncrementalRefreshNotResolvedSingleMatch() {
+ addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED,
+ null, null);
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
+ verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
+ }
+
+ /**
+ * Verify that incremental refresh will resolve previously not resolved participants.
+ */
+ public void testIncrementalRefreshNotResolvedMultiMatch() {
+ addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED,
+ null, null);
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
+ { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
+ verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
+ }
+
+ /**
+ * Verify that incremental refresh will not touch already-resolved participants.
+ */
+ public void testIncrementalRefreshResolvedSingleMatch() {
+ addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
+ verifyParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
+ }
+
+ /**
+ * Verify that full refresh will correct already-resolved participants if needed
+ */
+ public void testFullRefreshResolvedSingleMatch() {
+ addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
+ verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
+ }
+
+ /**
+ * Verify that incremental refresh will not touch participant that is marked as not found.
+ */
+ public void testIncrementalRefreshNotFound() {
+ addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
+ null, null);
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
+ verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
+ null, null);
+ }
+
+ /**
+ * Verify that full refresh will resolve participant that is marked as not found.
+ */
+ public void testFullRefreshNotFound() {
+ addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
+ null, null);
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
+ verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
+ }
+
+ /**
+ * Verify that refresh take consideration of current contact_id when having multiple matches.
+ */
+ public void testFullRefreshResolvedMultiMatch1() {
+ addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
+ { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
+ verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
+ }
+
+ /**
+ * Verify that refresh take consideration of current contact_id when having multiple matches.
+ */
+ public void testFullRefreshResolvedMultiMatch2() {
+ addParticipant("650-123-1233", 2, "Joh", "content://photo/joh");
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
+ { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
+ verifyParticipant("650-123-1233", 2, "Joe", "content://photo/joe");
+ }
+
+ /**
+ * Verify that refresh take first contact in case current contact_id no longer matches.
+ */
+ public void testFullRefreshResolvedMultiMatch3() {
+ addParticipant("650-123-1233", 3, "Joh", "content://photo/joh");
+ addPhoneLookup("650-123-1233", new Object[][] {
+ { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
+ { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
+ });
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
+ verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
+ }
+
+ /**
+ * Verify that refresh take first contact in case current contact_id no longer matches.
+ */
+ public void testFullRefreshResolvedBeforeButNotFoundNow() {
+ addParticipant("650-123-1233", 3, "Joh", "content://photo/joh");
+ addPhoneLookup("650-123-1233", new Object[][] {});
+
+ ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
+ verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
+ null, null);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java
new file mode 100644
index 0000000..039bec9
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/action/ActionServiceSystemTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.Factory;
+import com.android.messaging.FakeContext;
+import com.android.messaging.FakeContext.FakeContextHost;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.BugleServiceTestCase;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionExecutedListener;
+import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader;
+
+import java.util.ArrayList;
+
+@MediumTest
+public class ActionServiceSystemTest extends BugleServiceTestCase<ActionServiceImpl>
+ implements ActionCompletedListener, ActionExecutedListener, FakeContextHost {
+ private static final String TAG = "ActionServiceSystemTest";
+
+ static {
+ // Set flag during loading of test cases to prevent application initialization starting
+ BugleTestCase.setTestsRunning();
+ }
+
+ @Override
+ public void onActionSucceeded(final ActionMonitor monitor,
+ final Action action, final Object data, final Object result) {
+ final TestChatAction test = (TestChatAction) action;
+ assertEquals("Expect correct action parameter", parameter, test.parameter);
+ final ResultTracker tracker = (ResultTracker) data;
+ tracker.completionResult = result;
+ synchronized(tracker) {
+ tracker.notifyAll();
+ }
+ }
+
+ @Override
+ public void onActionFailed(final ActionMonitor monitor, final Action action,
+ final Object data, final Object result) {
+ final TestChatAction test = (TestChatAction) action;
+ assertEquals("Expect correct action parameter", parameter, test.parameter);
+ final ResultTracker tracker = (ResultTracker) data;
+ tracker.completionResult = result;
+ synchronized(tracker) {
+ tracker.notifyAll();
+ }
+ }
+
+ @Override
+ public void onActionExecuted(final ActionMonitor monitor, final Action action,
+ final Object data, final Object result) {
+ final TestChatAction test = (TestChatAction) action;
+ assertEquals("Expect correct action parameter", parameter, test.parameter);
+ final ResultTracker tracker = (ResultTracker) data;
+ tracker.executionResult = result;
+ }
+
+ public ActionServiceSystemTest() {
+ super(ActionServiceImpl.class);
+ }
+
+ public void testChatActionSucceeds() {
+ final ResultTracker tracker = new ResultTracker();
+
+ final ActionService service = DataModel.get().getActionService();
+ final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this);
+ final TestChatAction initial = new TestChatAction(monitor.getActionKey(), parameter);
+
+ assertNull("Expect completion result to start null", tracker.completionResult);
+ assertNull("Expect execution result to start null", tracker.executionResult);
+
+ final Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(initial, 0);
+ parcel.setDataPosition(0);
+ final TestChatAction action = parcel.readParcelable(mContext.getClassLoader());
+
+ synchronized(mWorker) {
+ try {
+ action.start(monitor);
+ // Wait for callback across threads
+ mWorker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for execution", false);
+ }
+ }
+
+ assertEquals("Expect to see 1 server request queued", 1,
+ mWorker.getRequestsMade().size());
+ final Action request = mWorker.getRequestsMade().get(0);
+ assertTrue("Expect Test type", request instanceof TestChatAction);
+
+ final Bundle response = new Bundle();
+ response.putString(TestChatAction.RESPONSE_TEST, processResponseResult);
+ synchronized(tracker) {
+ try {
+ request.markBackgroundWorkStarting();
+ request.markBackgroundWorkQueued();
+
+ request.markBackgroundWorkStarting();
+ request.markBackgroundCompletionQueued();
+ service.handleResponseFromBackgroundWorker(request, response);
+ // Wait for callback across threads
+ tracker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for response processing", false);
+ }
+ }
+
+ // TODO
+ //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult);
+ assertEquals("Expect completion result set", processResponseResult,
+ tracker.completionResult);
+ }
+
+ public void testChatActionFails() {
+ final ResultTracker tracker = new ResultTracker();
+
+ final ActionService service = DataModel.get().getActionService();
+ final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this);
+ final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter);
+
+ assertNull("Expect completion result to start null", tracker.completionResult);
+ assertNull("Expect execution result to start null", tracker.executionResult);
+
+ synchronized(mWorker) {
+ try {
+ action.start(monitor);
+ // Wait for callback across threads
+ mWorker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for requests", false);
+ }
+ }
+
+ final ArrayList<Intent> intents = mContext.extractIntents();
+ assertNotNull(intents);
+ assertEquals("Expect to see one intent", intents.size(), 1);
+
+ assertEquals("Expect to see 1 server request queued", 1,
+ mWorker.getRequestsMade().size());
+ final Action request = mWorker.getRequestsMade().get(0);
+ assertTrue("Expect Test type", request instanceof TestChatAction);
+
+ synchronized(tracker) {
+ try {
+ request.markBackgroundWorkStarting();
+ request.markBackgroundWorkQueued();
+
+ request.markBackgroundWorkStarting();
+ request.markBackgroundCompletionQueued();
+ service.handleFailureFromBackgroundWorker(request, new Exception("It went wrong"));
+ // Wait for callback across threads
+ tracker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for response processing", false);
+ }
+ }
+
+ // TODO
+ //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult);
+ assertEquals("Expect completion result set", processFailureResult,
+ tracker.completionResult);
+ }
+
+ public void testChatActionNoMonitor() {
+ final ActionService service = DataModel.get().getActionService();
+ final TestChatAction action =
+ new TestChatAction(Action.generateUniqueActionKey(null), parameter);
+
+ synchronized(mWorker) {
+ try {
+ action.start();
+ // Wait for callback across threads
+ mWorker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for execution", false);
+ }
+ }
+
+ assertEquals("Expect to see 1 server request queued", 1,
+ mWorker.getRequestsMade().size());
+ Action request = mWorker.getRequestsMade().get(0);
+ assertTrue("Expect Test type", request instanceof TestChatAction);
+
+ final Bundle response = new Bundle();
+ response.putString(TestChatAction.RESPONSE_TEST, processResponseResult);
+ synchronized(mWorker) {
+ try {
+ service.handleResponseFromBackgroundWorker(request, response);
+ // Wait for callback across threads
+ mWorker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for response processing", false);
+ }
+ }
+
+ assertEquals("Expect to see second server request queued",
+ 2, mWorker.getRequestsMade().size());
+ request = mWorker.getRequestsMade().get(1);
+ assertTrue("Expect other type",
+ request instanceof TestChatActionOther);
+ }
+
+ public void testChatActionUnregisterListener() {
+ final ResultTracker tracker = new ResultTracker();
+
+ final ActionService service = DataModel.get().getActionService();
+ final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this);
+ final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter);
+
+ assertNull("Expect completion result to start null", tracker.completionResult);
+ assertNull("Expect execution result to start null", tracker.executionResult);
+
+ synchronized(mWorker) {
+ try {
+ action.start(monitor);
+ // Wait for callback across threads
+ mWorker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for execution", false);
+ }
+ }
+
+ assertEquals("Expect to see 1 server request queued", 1,
+ mWorker.getRequestsMade().size());
+ final Action request = mWorker.getRequestsMade().get(0);
+ assertTrue("Expect Test type", request instanceof TestChatAction);
+
+ monitor.unregister();
+
+ final Bundle response = new Bundle();
+ synchronized(mWorker) {
+ try {
+ request.markBackgroundWorkStarting();
+ request.markBackgroundWorkQueued();
+
+ request.markBackgroundWorkStarting();
+ request.markBackgroundCompletionQueued();
+ service.handleResponseFromBackgroundWorker(request, response);
+ // Wait for callback across threads
+ mWorker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for response processing", false);
+ }
+ }
+
+ //assertEquals("Expect execution result set", executeActionResult, tracker.executionResult);
+ assertEquals("Expect completion never called", null, tracker.completionResult);
+ }
+
+ StubBackgroundWorker mWorker;
+ FakeContext mContext;
+ StubLoader mLoader;
+
+ private static final String parameter = "parameter";
+ private static final Object executeActionResult = "executeActionResult";
+ private static final String processResponseResult = "processResponseResult";
+ private static final Object processFailureResult = "processFailureResult";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ Log.d(TAG, "ChatActionTest setUp");
+
+ mContext = new FakeContext(getContext(), this);
+ mWorker = new StubBackgroundWorker();
+ FakeFactory.registerWithFakeContext(getContext(), mContext)
+ .withDataModel(new FakeDataModel(mContext)
+ .withBackgroundWorkerForActionService(mWorker)
+ .withActionService(new ActionService())
+ .withConnectivityUtil(new StubConnectivityUtil(mContext)));
+
+ mLoader = new StubLoader();
+ setContext(Factory.get().getApplicationContext());
+ }
+
+ @Override
+ public String getServiceClassName() {
+ return ActionServiceImpl.class.getName();
+ }
+
+ @Override
+ public void startServiceForStub(final Intent intent) {
+ this.startService(intent);
+ }
+
+ @Override
+ public void onStartCommandForStub(final Intent intent, final int flags, final int startId) {
+ this.getService().onStartCommand(intent, flags, startId);
+ }
+
+ public static class TestChatAction extends Action implements Parcelable {
+ public static String RESPONSE_TEST = "response_test";
+ public static String KEY_PARAMETER = "parameter";
+
+ protected TestChatAction(final String key, final String parameter) {
+ super(key);
+ this.actionParameters.putString(KEY_PARAMETER, parameter);
+ // Cache parameter as a member variable
+ this.parameter = parameter;
+ }
+
+ // An example parameter
+ public final String parameter;
+
+ /**
+ * Process the action locally - runs on datamodel service thread
+ */
+ @Override
+ protected Object executeAction() {
+ requestBackgroundWork();
+ return executeActionResult;
+ }
+
+ /**
+ * Process the response from the server - runs on datamodel service thread
+ */
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ requestBackgroundWork(new TestChatActionOther(null, parameter));
+ return response.get(RESPONSE_TEST);
+ }
+
+ /**
+ * Called in case of failures when sending requests - runs on datamodel service thread
+ */
+ @Override
+ protected Object processBackgroundFailure() {
+ return processFailureResult;
+ }
+
+ private TestChatAction(final Parcel in) {
+ super(in);
+ // Cache parameter as a member variable
+ parameter = actionParameters.getString(KEY_PARAMETER);
+ }
+
+ public static final Parcelable.Creator<TestChatAction> CREATOR
+ = new Parcelable.Creator<TestChatAction>() {
+ @Override
+ public TestChatAction createFromParcel(final Parcel in) {
+ return new TestChatAction(in);
+ }
+
+ @Override
+ public TestChatAction[] newArray(final int size) {
+ return new TestChatAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+ }
+
+ public static class TestChatActionOther extends Action implements Parcelable {
+ protected TestChatActionOther(final String key, final String parameter) {
+ super(generateUniqueActionKey(key));
+ this.parameter = parameter;
+ }
+
+ public final String parameter;
+
+ private TestChatActionOther(final Parcel in) {
+ super(in);
+ parameter = in.readString();
+ }
+
+ public static final Parcelable.Creator<TestChatActionOther> CREATOR
+ = new Parcelable.Creator<TestChatActionOther>() {
+ @Override
+ public TestChatActionOther createFromParcel(final Parcel in) {
+ return new TestChatActionOther(in);
+ }
+
+ @Override
+ public TestChatActionOther[] newArray(final int size) {
+ return new TestChatActionOther[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ parcel.writeString(parameter);
+ }
+ }
+
+ /**
+ * An operation that notifies a listener upon completion
+ */
+ public static class TestChatActionMonitor extends ActionMonitor {
+ /**
+ * Create action state wrapping an BlockUserAction instance
+ * @param account - account in which to block the user
+ * @param baseKey - suggested action key from BlockUserAction
+ * @param data - optional action specific data that is handed back to listener
+ * @param listener - action completed listener
+ */
+ public TestChatActionMonitor(final String baseKey, final Object data,
+ final ActionCompletedListener completed, final ActionExecutedListener executed) {
+ super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data);
+ setCompletedListener(completed);
+ setExecutedListener(executed);
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java
new file mode 100644
index 0000000..02cddae
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/action/ActionServiceTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import com.android.messaging.Factory;
+import com.android.messaging.FakeContext;
+import com.android.messaging.FakeContext.FakeContextHost;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.BugleServiceTestCase;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
+import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener;
+import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader;
+import com.android.messaging.util.WakeLockHelper;
+
+import java.util.ArrayList;
+
+@MediumTest
+public class ActionServiceTest extends BugleServiceTestCase<ActionServiceImpl>
+ implements FakeContextHost, ActionStateChangedListener, ActionCompletedListener {
+ private static final String TAG = "ActionServiceTest";
+
+ @Override
+ public void onActionStateChanged(final Action action, final int state) {
+ mStates.add(state);
+ }
+
+ @Override
+ public void onActionSucceeded(final ActionMonitor monitor,
+ final Action action, final Object data, final Object result) {
+ final TestChatAction test = (TestChatAction) action;
+ assertNotSame(test.dontRelyOnMe, dontRelyOnMe);
+ // This will be true - but only briefly
+ assertEquals(test.dontRelyOnMe, becauseIChange);
+
+ final ResultTracker tracker = (ResultTracker) data;
+ tracker.completionResult = result;
+ synchronized(tracker) {
+ tracker.notifyAll();
+ }
+ }
+
+ @Override
+ public void onActionFailed(final ActionMonitor monitor, final Action action,
+ final Object data, final Object result) {
+ final TestChatAction test = (TestChatAction) action;
+ assertNotSame(test.dontRelyOnMe, dontRelyOnMe);
+ // This will be true - but only briefly
+ assertEquals(test.dontRelyOnMe, becauseIChange);
+
+ final ResultTracker tracker = (ResultTracker) data;
+ tracker.completionResult = result;
+ synchronized(tracker) {
+ tracker.notifyAll();
+ }
+ }
+
+ /**
+ * For a dummy action verify that the service intent is constructed and queued correctly and
+ * that when that intent is processed it actually executes the action.
+ */
+ public void testChatServiceCreatesIntentAndExecutesAction() {
+ final ResultTracker tracker = new ResultTracker();
+
+ final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this);
+ final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter);
+
+ action.dontRelyOnMe = dontRelyOnMe;
+ assertFalse("Expect service initially stopped", mServiceStarted);
+
+ action.start(monitor);
+
+ assertTrue("Expect service started", mServiceStarted);
+
+ final ArrayList<Intent> intents = mContext.extractIntents();
+ assertNotNull(intents);
+ assertEquals("Expect to see 1 server request queued", 1, intents.size());
+ final Intent intent = intents.get(0);
+ assertEquals("Check pid", intent.getIntExtra(WakeLockHelper.EXTRA_CALLING_PID, 0),
+ Process.myPid());
+ assertEquals("Check opcode", intent.getIntExtra(ActionServiceImpl.EXTRA_OP_CODE, 0),
+ ActionServiceImpl.OP_START_ACTION);
+ assertTrue("Check wakelock held", ActionServiceImpl.sWakeLock.isHeld(intent));
+
+ synchronized(tracker) {
+ try {
+ this.startService(intent);
+ // Wait for callback across threads
+ tracker.wait(2000);
+ } catch (final InterruptedException e) {
+ assertTrue("Interrupted waiting for response processing", false);
+ }
+ }
+
+ assertEquals("Expect three states ", mStates.size(), 3);
+ assertEquals("State-0 should be STATE_QUEUED", (int)mStates.get(0),
+ ActionMonitor.STATE_QUEUED);
+ assertEquals("State-1 should be STATE_EXECUTING", (int)mStates.get(1),
+ ActionMonitor.STATE_EXECUTING);
+ assertEquals("State-2 should be STATE_COMPLETE", (int)mStates.get(2),
+ ActionMonitor.STATE_COMPLETE);
+ // TODO: Should find a way to reliably wait, this is a bit of a hack
+ if (ActionServiceImpl.sWakeLock.isHeld(intent)) {
+ Log.d(TAG, "ActionServiceTest: waiting for wakelock release");
+ try {
+ Thread.sleep(100);
+ } catch (final InterruptedException e) {
+ }
+ }
+ assertFalse("Check wakelock released", ActionServiceImpl.sWakeLock.isHeld(intent));
+ }
+
+ StubBackgroundWorker mWorker;
+ FakeContext mContext;
+ StubLoader mLoader;
+ ActionService mService;
+
+ ArrayList<Integer> mStates;
+
+ private static final String parameter = "parameter";
+ private static final Object dontRelyOnMe = "dontRelyOnMe";
+ private static final Object becauseIChange = "becauseIChange";
+ private static final Object executeActionResult = "executeActionResult";
+ private static final Object processResponseResult = "processResponseResult";
+ private static final Object processFailureResult = "processFailureResult";
+
+ public ActionServiceTest() {
+ super(ActionServiceImpl.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ Log.d(TAG, "ChatActionTest setUp");
+
+ sLooper = Looper.myLooper();
+
+ mWorker = new StubBackgroundWorker();
+ mContext = new FakeContext(getContext(), this);
+ FakeFactory.registerWithFakeContext(getContext(),mContext)
+ .withDataModel(new FakeDataModel(mContext)
+ .withBackgroundWorkerForActionService(mWorker)
+ .withActionService(new ActionService())
+ .withConnectivityUtil(new StubConnectivityUtil(mContext)));
+
+ mStates = new ArrayList<Integer>();
+ setContext(Factory.get().getApplicationContext());
+ }
+
+ @Override
+ public String getServiceClassName() {
+ return ActionServiceImpl.class.getName();
+ }
+
+ boolean mServiceStarted = false;
+
+ @Override
+ public void startServiceForStub(final Intent intent) {
+ // Do nothing until later
+ assertFalse(mServiceStarted);
+ mServiceStarted = true;
+ }
+
+ @Override
+ public void onStartCommandForStub(final Intent intent, final int flags, final int startId) {
+ assertTrue(mServiceStarted);
+ }
+
+ private static Looper sLooper;
+ public static void assertRunsOnOtherThread() {
+ assertTrue (Looper.myLooper() != Looper.getMainLooper());
+ assertTrue (Looper.myLooper() != sLooper);
+ }
+
+ public static class TestChatAction extends Action implements Parcelable {
+ public static String RESPONSE_TEST = "response_test";
+ public static String KEY_PARAMETER = "parameter";
+
+ protected TestChatAction(final String key, final String parameter) {
+ super(key);
+ this.actionParameters.putString(KEY_PARAMETER, parameter);
+ }
+
+ transient Object dontRelyOnMe;
+
+ /**
+ * Process the action locally - runs on service thread
+ */
+ @Override
+ protected Object executeAction() {
+ this.dontRelyOnMe = becauseIChange;
+ assertRunsOnOtherThread();
+ return executeActionResult;
+ }
+
+ /**
+ * Process the response from the server - runs on service thread
+ */
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ assertRunsOnOtherThread();
+ return processResponseResult;
+ }
+
+ /**
+ * Called in case of failures when sending requests - runs on service thread
+ */
+ @Override
+ protected Object processBackgroundFailure() {
+ assertRunsOnOtherThread();
+ return processFailureResult;
+ }
+
+ private TestChatAction(final Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<TestChatAction> CREATOR
+ = new Parcelable.Creator<TestChatAction>() {
+ @Override
+ public TestChatAction createFromParcel(final Parcel in) {
+ return new TestChatAction(in);
+ }
+
+ @Override
+ public TestChatAction[] newArray(final int size) {
+ return new TestChatAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ }
+ }
+
+ /**
+ * An operation that notifies a listener upon state changes, execution and completion
+ */
+ public static class TestChatActionMonitor extends ActionMonitor {
+ public TestChatActionMonitor(final String baseKey, final Object data,
+ final ActionStateChangedListener listener, final ActionCompletedListener executed) {
+ super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data);
+ setStateChangedListener(listener);
+ setCompletedListener(executed);
+ assertEquals("Initial state should be STATE_CREATED", mState, STATE_CREATED);
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTest.java b/tests/src/com/android/messaging/datamodel/action/ActionTest.java
new file mode 100644
index 0000000..aefa25e
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/action/ActionTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.datamodel.action;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.DataModelImpl;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubChatActionMonitor;
+
+import java.util.ArrayList;
+
+@MediumTest
+public class ActionTest extends BugleTestCase {
+ private static final String parameter = "parameter";
+ private static final Object executeActionResult = "executeActionResult";
+ private static final Object processResponseResult = "processResponseResult";
+ private static final Object processFailureResult = "processFailureResult";
+
+ private static final String mActionKey = "TheActionKey";
+ private static final Object mData = "PrivateData";
+ private StubChatActionMonitor mMonitor;
+ private TestChatAction mAction;
+
+ private ArrayList<StubChatActionMonitor.StateTransition> mTransitions;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getTestContext())
+ .withDataModel(new DataModelImpl(getContext()));
+
+ mMonitor = new StubChatActionMonitor(ActionMonitor.STATE_CREATED, mActionKey,
+ mData);
+ mAction = new TestChatAction(mActionKey, parameter);
+ mTransitions = mMonitor.getTransitions();
+ }
+
+ private void verifyState(final int count, final int from, final int to) {
+ assertEquals(to, mMonitor.getState());
+ assertEquals(mTransitions.size(), count);
+ verifyTransition(count-1, from , to);
+ }
+
+ private void verifyTransition(final int index, final int from, final int to) {
+ assertTrue(mTransitions.size() > index);
+ assertEquals(mAction, mTransitions.get(index).action);
+ assertEquals(from, mTransitions.get(index).from);
+ assertEquals(to, mTransitions.get(index).to);
+ }
+
+ @SmallTest
+ public void testActionStartTransitionsCorrectly() {
+ mMonitor.setState(ActionMonitor.STATE_CREATED);
+
+ ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor);
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.markStart();
+ assertEquals("After start state: STATE_QUEUED", ActionMonitor.STATE_QUEUED,
+ mMonitor.getState());
+ verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED);
+
+ ActionMonitor.unregisterActionMonitor(mAction.actionKey, mMonitor);
+
+ assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ }
+
+ @SmallTest
+ public void testActionStartAssertsFromIncorrectState() {
+ mMonitor.setState(ActionMonitor.STATE_UNDEFINED);
+
+ ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor);
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ try {
+ mAction.markStart();
+ fail("Expect assertion when starting from STATE_UNDEFINED");
+ } catch (final IllegalStateException ex){
+ }
+ ActionMonitor.unregisterActionMonitor(mAction.actionKey, mMonitor);
+
+ assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ }
+
+ public void testActionTransitionsEndToEndWithRequests() {
+ assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED,
+ mMonitor.getState());
+
+ ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor);
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.markStart();
+
+ verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED);
+
+ mAction.markBeginExecute();
+
+ verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING);
+
+ final Object result = mAction.executeAction();
+ mAction.requestBackgroundWork();
+
+ assertEquals("Check executeAction result", result, executeActionResult);
+
+ mAction.markEndExecute(result);
+
+ verifyState(3, ActionMonitor.STATE_EXECUTING,
+ ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
+
+ mAction.markBackgroundWorkStarting();
+
+ verifyState(4, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
+
+ mAction.markBackgroundWorkQueued();
+
+ verifyState(5, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
+ ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
+
+ mAction.markBackgroundWorkStarting();
+
+ verifyState(6, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
+
+ final Bundle response = null;
+
+ mAction.markBackgroundCompletionQueued();
+
+ verifyState(7, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
+ ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED);
+
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.processBackgroundWorkResponse(response);
+
+ verifyTransition(7, ActionMonitor.STATE_BACKGROUND_COMPLETION_QUEUED,
+ ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE);
+
+ verifyState(9, ActionMonitor.STATE_PROCESSING_BACKGROUND_RESPONSE,
+ ActionMonitor.STATE_COMPLETE);
+
+ assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ }
+
+ public void testActionTransitionsEndToEndFailsRequests() {
+ assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED,
+ mMonitor.getState());
+
+ ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor);
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.markStart();
+
+ verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED);
+
+ mAction.markBeginExecute();
+
+ verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING);
+
+ final Object result = mAction.executeAction();
+ mAction.requestBackgroundWork();
+
+ assertEquals("Check executeAction result", result, executeActionResult);
+
+ mAction.markEndExecute(result);
+
+ verifyState(3, ActionMonitor.STATE_EXECUTING,
+ ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
+
+ mAction.markBackgroundWorkStarting();
+
+ verifyState(4, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
+
+ mAction.markBackgroundWorkQueued();
+
+ verifyState(5, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
+ ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED);
+
+ mAction.markBackgroundWorkStarting();
+
+ verifyState(6, ActionMonitor.STATE_BACKGROUND_ACTIONS_QUEUED,
+ ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION);
+
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.processBackgroundWorkFailure();
+
+ verifyState(7, ActionMonitor.STATE_EXECUTING_BACKGROUND_ACTION,
+ ActionMonitor.STATE_COMPLETE);
+
+ assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ }
+
+ public void testActionTransitionsEndToEndNoRequests() {
+ assertEquals("Start state: STATE_CREATED", ActionMonitor.STATE_CREATED,
+ mMonitor.getState());
+
+ ActionMonitor.registerActionMonitor(mAction.actionKey, mMonitor);
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.markStart();
+
+ verifyState(1, ActionMonitor.STATE_CREATED, ActionMonitor.STATE_QUEUED);
+
+ mAction.markBeginExecute();
+
+ verifyState(2, ActionMonitor.STATE_QUEUED, ActionMonitor.STATE_EXECUTING);
+
+ final Object result = mAction.executeAction();
+
+ assertEquals("Check executeAction result", result, executeActionResult);
+
+ assertTrue(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertTrue(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ assertEquals(ActionMonitor.sActionMonitors.get(mAction.actionKey), mMonitor);
+
+ mAction.markEndExecute(result);
+
+ verifyState(3, ActionMonitor.STATE_EXECUTING,
+ ActionMonitor.STATE_COMPLETE);
+
+ assertFalse(ActionMonitor.sActionMonitors.containsKey(mAction.actionKey));
+ assertFalse(ActionMonitor.sActionMonitors.containsValue(mMonitor));
+ }
+
+ public static class TestChatAction extends Action implements Parcelable {
+ protected TestChatAction(final String key, final String parameter) {
+ super(key);
+ this.parameter = parameter;
+ }
+
+ public final String parameter;
+
+ /**
+ * Process the action locally - runs on service thread
+ */
+ @Override
+ protected Object executeAction() {
+ assertEquals("Check parameter", parameter, ActionTest.parameter);
+ return executeActionResult;
+ }
+
+ /**
+ * Process the response from the server - runs on service thread
+ */
+ @Override
+ protected Object processBackgroundResponse(final Bundle response) {
+ assertEquals("Check parameter", parameter, ActionTest.parameter);
+ return processResponseResult;
+ }
+
+ /**
+ * Called in case of failures when sending requests - runs on service thread
+ */
+ @Override
+ protected Object processBackgroundFailure() {
+ assertEquals("Check parameter", parameter, ActionTest.parameter);
+ return processFailureResult;
+ }
+
+ private TestChatAction(final Parcel in) {
+ super(in);
+ parameter = in.readString();
+ }
+
+ public static final Parcelable.Creator<TestChatAction> CREATOR
+ = new Parcelable.Creator<TestChatAction>() {
+ @Override
+ public TestChatAction createFromParcel(final Parcel in) {
+ return new TestChatAction(in);
+ }
+
+ @Override
+ public TestChatAction[] newArray(final int size) {
+ return new TestChatAction[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ writeActionToParcel(parcel, flags);
+ parcel.writeString(parameter);
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java
new file mode 100644
index 0000000..d72a0f9
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/action/ActionTestHelpers.java
@@ -0,0 +1,191 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.messaging.util.ConnectivityUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActionTestHelpers {
+ private static final String TAG = "DataModelTestHelpers";
+
+ static class StubLoader extends ContentObserver {
+ ArrayList<Uri> mUriList = new ArrayList<Uri>();
+
+ StubLoader() {
+ super(null);
+ }
+
+ public void clear() {
+ mUriList.clear();
+ }
+
+ @Override
+ public void onChange(final boolean selfChange) {
+ // Handle change.
+ mUriList.add(null);
+ }
+
+ // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
+ // Only supported on platform 16 and above...
+ @Override
+ public void onChange(final boolean selfChange, final Uri uri) {
+ // Handle change.
+ mUriList.add(uri);
+ }
+ }
+
+ static class StubBackgroundWorker extends BackgroundWorker {
+ public StubBackgroundWorker() {
+ super();
+ mActions = new ArrayList<Action>();
+ }
+
+ ArrayList<Action> mActions;
+ public ArrayList<Action> getRequestsMade() {
+ return mActions;
+ }
+
+ @Override
+ public void queueBackgroundWork(final List<Action> actions) {
+ mActions.addAll(actions);
+
+ synchronized(this) {
+ this.notifyAll();
+ }
+ }
+ }
+
+ static class ResultTracker {
+ public Object executionResult;
+ public Object completionResult;
+ }
+
+ static class StubChatActionMonitor extends ActionMonitor {
+ static public class StateTransition {
+ Action action;
+ int from;
+ int to;
+ public StateTransition(final Action action, final int from, final int to) {
+ this.action = action;
+ this.from = from;
+ this.to = to;
+ }
+ }
+
+ private final ArrayList<StateTransition> mTransitions;
+ public ArrayList<StateTransition> getTransitions() {
+ return mTransitions;
+ }
+
+ protected StubChatActionMonitor(final int initialState, final String actionKey,
+ final Object data) {
+ super(initialState, actionKey, data);
+ mTransitions = new ArrayList<StateTransition>();
+ }
+
+ @Override
+ protected void updateState(final Action action, final int expectedState,
+ final int state) {
+ mTransitions.add(new StateTransition(action, mState, state));
+ super.updateState(action, expectedState, state);
+ }
+
+ public void setState(final int state) {
+ mState = state;
+ }
+
+ public int getState() {
+ return mState;
+ }
+ }
+
+ public static class StubActionService extends ActionService {
+ public static class StubActionServiceCallLog {
+ public final Action action;
+ public final Action request;
+ public final Bundle response;
+ public final Exception exception;
+ public final Action update;
+
+ public StubActionServiceCallLog(final Action action,
+ final Action request,
+ final Bundle response,
+ final Exception exception,
+ final Action update) {
+ this.action = action;
+ this.request = request;
+ this.response = response;
+ this.exception = exception;
+ this.update = update;
+ }
+ }
+
+ private final ArrayList<StubActionServiceCallLog> mServiceCalls =
+ new ArrayList<StubActionServiceCallLog>();
+
+ public ArrayList<StubActionServiceCallLog> getCalls() {
+ return mServiceCalls;
+ }
+
+ @Override
+ public void startAction(final Action action) {
+ mServiceCalls.add(new StubActionServiceCallLog(action, null, null, null, null));
+ synchronized(this) {
+ this.notifyAll();
+ }
+ }
+
+ @Override
+ public void handleResponseFromBackgroundWorker(final Action request,
+ final Bundle response) {
+ mServiceCalls.add(new StubActionServiceCallLog(null, request, response, null, null));
+ synchronized(this) {
+ this.notifyAll();
+ }
+ }
+
+ @Override
+ protected void handleFailureFromBackgroundWorker(final Action request,
+ final Exception exception) {
+ mServiceCalls.add(new StubActionServiceCallLog(null, request, null, exception, null));
+ synchronized(this) {
+ this.notifyAll();
+ }
+ }
+ }
+
+ public static class StubConnectivityUtil extends ConnectivityUtil {
+ public StubConnectivityUtil(final Context context) {
+ super(context);
+ }
+
+ @Override
+ public void registerForSignalStrength() {
+ }
+
+ @Override
+ public void unregisterForSignalStrength() {
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java
new file mode 100644
index 0000000..b05b022
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/action/GetOrCreateConversationActionTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.test.mock.MockContentProvider;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeContext;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.sms.MmsUtils;
+
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+
+@SmallTest
+public class GetOrCreateConversationActionTest extends BugleTestCase {
+
+ @Mock GetOrCreateConversationActionListener mockListener;
+
+ public void testGetOrCreateConversation() {
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final ArrayList<String> recipients = new ArrayList<String>();
+ recipients.add("5551234567");
+ recipients.add("5551234568");
+
+ // Generate a list of partially formed participants
+ final ArrayList<ParticipantData> participants = new
+ ArrayList<ParticipantData>();
+
+
+ for (final String recipient : recipients) {
+ participants.add(ParticipantData.getFromRawPhoneBySystemLocale(recipient));
+ }
+
+ // Test that we properly stubbed the SMS provider to return a thread id
+ final long threadId = MmsUtils.getOrCreateThreadId(mContext, recipients);
+ assertEquals(TestDataFactory.SMS_MMS_THREAD_ID_CURSOR_VALUE, threadId);
+
+ final String blankId = BugleDatabaseOperations.getExistingConversation(db, threadId, false);
+ assertNull("Conversation already exists", blankId);
+
+ ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
+
+ GetOrCreateConversationActionMonitor monitor =
+ GetOrCreateConversationAction.getOrCreateConversation(participants, null,
+ mockListener);
+
+ assertEquals("Failed to start service once for action", calls.size(), 1);
+ assertTrue("Action not GetOrCreateConversationAction", calls.get(0).action instanceof
+ GetOrCreateConversationAction);
+
+ GetOrCreateConversationAction action = (GetOrCreateConversationAction)
+ calls.get(0).action;
+
+ Object result = action.executeAction();
+
+ assertTrue(result instanceof String);
+
+ // Make sure that we created a new conversation
+ assertEquals(TestDataFactory.NUM_TEST_CONVERSATIONS+1, Integer.parseInt((String)result));
+
+ // Now get the conversation that we just created again
+ monitor = GetOrCreateConversationAction.getOrCreateConversation(participants, null,
+ mockListener);
+
+ calls = mService.getCalls();
+ assertEquals("Failed to start service for second action", calls.size(), 2);
+ assertTrue("Action not GetOrCreateConversationAction", calls.get(1).action instanceof
+ GetOrCreateConversationAction);
+ action = (GetOrCreateConversationAction)calls.get(1).action;
+ result = action.executeAction();
+
+ assertTrue(result instanceof String);
+
+ final String conversationId = (String) result;
+
+ // Make sure that we found the same conversation id
+ assertEquals(TestDataFactory.NUM_TEST_CONVERSATIONS+1, Integer.parseInt((String)result));
+
+ final ArrayList<ParticipantData> conversationParticipants =
+ BugleDatabaseOperations.getParticipantsForConversation(db, conversationId);
+
+ assertEquals("Participant count mismatch", recipients.size(),
+ conversationParticipants.size());
+ for(final ParticipantData participant : conversationParticipants) {
+ assertTrue(recipients.contains(participant.getSendDestination()));
+ }
+
+ final Uri conversationParticipantsUri =
+ MessagingContentProvider.buildConversationParticipantsUri(conversationId);
+ final Cursor cursor = mContext.getContentResolver().query(conversationParticipantsUri,
+ ParticipantData.ParticipantsQuery.PROJECTION, null, null, null);
+
+ int countSelf = 0;
+ while(cursor.moveToNext()) {
+ final ParticipantData participant = ParticipantData.getFromCursor(cursor);
+ if (participant.isSelf()) {
+ countSelf++;
+ } else {
+ assertTrue(recipients.contains(participant.getSendDestination()));
+ }
+ }
+ cursor.close();
+ assertEquals("Expect one self participant in conversations", 1, countSelf);
+ assertEquals("Cursor count mismatch", recipients.size(), cursor.getCount() - countSelf);
+
+ final String realId = BugleDatabaseOperations.getExistingConversation(db, threadId, false);
+ assertEquals("Conversation already exists", realId, conversationId);
+ }
+
+ private FakeContext mContext;
+ private StubActionService mService;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mContext = new FakeContext(getTestContext());
+
+ final MockContentProvider mockProvider = new MockContentProvider() {
+ @Override
+ public Cursor query(final Uri uri, final String[] projection, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ return TestDataFactory.getSmsMmsThreadIdCursor();
+ }
+ };
+
+ mContext.addContentProvider("mms-sms", mockProvider);
+ final MessagingContentProvider provider = new MessagingContentProvider();
+ final ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = MessagingContentProvider.AUTHORITY;
+ provider.attachInfo(mContext, providerInfo);
+ mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider);
+
+ mService = new StubActionService();
+ final FakeDataModel fakeDataModel = new FakeDataModel(mContext)
+ .withActionService(mService);
+ FakeFactory.registerWithFakeContext(getTestContext(), mContext)
+ .withDataModel(fakeDataModel);
+ provider.setDatabaseForTest(fakeDataModel.getDatabase());
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java
new file mode 100644
index 0000000..0405c90
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/action/ReadWriteDraftMessageActionTest.java
@@ -0,0 +1,482 @@
+/*
+ * 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.datamodel.action;
+
+import android.content.ContentProvider;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeContext;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.MediaScratchFileProvider;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubConnectivityUtil;
+import com.android.messaging.datamodel.action.ReadDraftDataAction.ReadDraftDataActionListener;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.util.ContentType;
+
+import org.mockito.Mock;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+@SmallTest
+public class ReadWriteDraftMessageActionTest extends BugleTestCase {
+
+ @Mock ReadDraftDataActionListener mockListener;
+
+ // TODO: Add test cases
+ // 1. Make sure drafts can include attachments and multiple parts
+ // 2. Make sure attachments get cleaned up appropriately
+ // 3. Make sure messageId and partIds not reused (currently each draft is a new message).
+ public void testWriteDraft() {
+ final String draftMessage = "draftMessage";
+ final long threadId = 1234567;
+ final boolean senderBlocked = false;
+ final String participantNumber = "5551234567";
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
+ senderBlocked);
+ final String selfId = getOrCreateSelfId(db);
+
+ // Should clear/stub DB
+ final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
+
+ final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
+ draftMessage);
+
+ WriteDraftMessageAction.writeDraftMessage(conversationId, message);
+
+ assertEquals("Failed to start service once for action", calls.size(), 1);
+ assertTrue("Action not SaveDraftMessageAction",
+ calls.get(0).action instanceof WriteDraftMessageAction);
+
+ final Action save = calls.get(0).action;
+
+ final Object result = save.executeAction();
+
+ assertTrue("Expect row number string as result", result instanceof String);
+ final String messageId = (String) result;
+
+ // Should check DB
+ final MessageData actual = BugleDatabaseOperations.readMessage(db, messageId);
+ assertNotNull("Database missing draft", actual);
+ assertEquals("Draft text changed", draftMessage, actual.getMessageText());
+ }
+
+ private static String getOrCreateSelfId(final DatabaseWrapper db) {
+ db.beginTransaction();
+ final String selfId = BugleDatabaseOperations.getOrCreateParticipantInTransaction(db,
+ ParticipantData.getSelfParticipant(ParticipantData.DEFAULT_SELF_SUB_ID));
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ return selfId;
+ }
+
+ private static String getOrCreateConversation(final DatabaseWrapper db,
+ final String participantNumber, final long threadId, final boolean senderBlocked) {
+ final ArrayList<ParticipantData> participants =
+ new ArrayList<ParticipantData>();
+ participants.add(ParticipantData.getFromRawPhoneBySystemLocale(participantNumber));
+
+ final String conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
+ senderBlocked, participants, false, false, null);
+ assertNotNull("No conversation", conversationId);
+ return conversationId;
+ }
+
+ public void testReadDraft() {
+ final Object data = "data";
+ final String draftMessage = "draftMessage";
+ final long threadId = 1234567;
+ final boolean senderBlocked = false;
+ final String participantNumber = "5552345678";
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
+ senderBlocked);
+ final String selfId = getOrCreateSelfId(db);
+
+ // Should clear/stub DB
+ final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
+
+ final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
+ draftMessage);
+
+ BugleDatabaseOperations.updateDraftMessageData(db, conversationId, message,
+ BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
+
+ final ActionMonitor monitor =
+ ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener);
+
+ assertEquals("Unexpected number of calls to service", 1, calls.size());
+ assertTrue("Action not of type ReadDraftMessageAction",
+ calls.get(0).action instanceof ReadDraftDataAction);
+
+ final Action read = calls.get(0).action;
+
+ final Object result = read.executeAction();
+
+ assertTrue(result instanceof ReadDraftDataAction.DraftData);
+ final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result;
+
+ assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText());
+ assertEquals("Draft self differs", selfId, draft.message.getSelfId());
+ assertEquals("Draft conversation differs", conversationId,
+ draft.conversation.getConversationId());
+ }
+
+ public void testReadDraftForNewConversation() {
+ final Object data = "data";
+ long threadId = 1234567;
+ final boolean senderBlocked = false;
+ long phoneNumber = 5557654567L;
+ final String notConversationId = "ThisIsNotValidConversationId";
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final String selfId = getOrCreateSelfId(db);
+
+ // Unless set a new conversation should have a null draft message
+ final MessageData blank = BugleDatabaseOperations.readDraftMessageData(db,
+ notConversationId, selfId);
+ assertNull(blank);
+
+ String conversationId = null;
+ do {
+ conversationId = BugleDatabaseOperations.getExistingConversation(db,
+ threadId, senderBlocked);
+ threadId++;
+ phoneNumber++;
+ }
+ while(!TextUtils.isEmpty(conversationId));
+
+ final ArrayList<ParticipantData> participants =
+ new ArrayList<ParticipantData>();
+ participants.add(ParticipantData.getFromRawPhoneBySystemLocale(Long.toString(phoneNumber)));
+
+ conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
+ senderBlocked, participants, false, false, null);
+ assertNotNull("No conversation", conversationId);
+
+ final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db, conversationId,
+ selfId);
+ assertNull(actual);
+
+ // Should clear/stub DB
+ final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
+
+ final ActionMonitor monitor =
+ ReadDraftDataAction.readDraftData(conversationId, null, data, mockListener);
+
+ assertEquals("Unexpected number of calls to service", 1, calls.size());
+ assertTrue("Action not of type ReadDraftMessageAction",
+ calls.get(0).action instanceof ReadDraftDataAction);
+
+ final Action read = calls.get(0).action;
+
+ final Object result = read.executeAction();
+
+ assertTrue(result instanceof ReadDraftDataAction.DraftData);
+ final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result;
+
+ assertEquals("Draft message text differs", "", draft.message.getMessageText());
+ assertEquals("Draft self differs", selfId, draft.message.getSelfId());
+ assertEquals("Draft conversation differs", conversationId,
+ draft.conversation.getConversationId());
+ }
+
+ public void testWriteAndReadDraft() {
+ final Object data = "data";
+ final String draftMessage = "draftMessage";
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ final Cursor conversations = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns._ID, ConversationColumns.CURRENT_SELF_ID }, null,
+ null, null /* groupBy */, null /* having */, null /* orderBy */);
+
+ if (conversations.moveToFirst()) {
+ final String conversationId = conversations.getString(0);
+ final String selfId = getOrCreateSelfId(db);
+
+ // Should clear/stub DB
+ final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
+
+ final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
+ draftMessage);
+
+ WriteDraftMessageAction.writeDraftMessage(conversationId, message);
+
+ assertEquals("Failed to start service once for action", calls.size(), 1);
+ assertTrue("Action not SaveDraftMessageAction",
+ calls.get(0).action instanceof WriteDraftMessageAction);
+
+ final Action save = calls.get(0).action;
+
+ Object result = save.executeAction();
+
+ assertTrue("Expect row number string as result", result instanceof String);
+
+ // Should check DB
+
+ final ActionMonitor monitor =
+ ReadDraftDataAction.readDraftData(conversationId, null, data,
+ mockListener);
+
+ assertEquals("Expect two calls queued", 2, calls.size());
+ assertTrue("Expect action", calls.get(1).action instanceof ReadDraftDataAction);
+
+ final Action read = calls.get(1).action;
+
+ result = read.executeAction();
+
+ assertTrue(result instanceof ReadDraftDataAction.DraftData);
+ final ReadDraftDataAction.DraftData draft = (ReadDraftDataAction.DraftData) result;
+
+ assertEquals("Draft message text differs", draftMessage, draft.message.getMessageText());
+ // The conversation's self id is used as the draft's self id.
+ assertEquals("Draft self differs", conversations.getString(1),
+ draft.message.getSelfId());
+ assertEquals("Draft conversation differs", conversationId,
+ draft.conversation.getConversationId());
+ } else {
+ fail("No conversations in database");
+ }
+ }
+
+ public void testUpdateDraft() {
+ final String initialMessage = "initialMessage";
+ final String draftMessage = "draftMessage";
+ final long threadId = 1234567;
+ final boolean senderBlocked = false;
+ final String participantNumber = "5553456789";
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
+ senderBlocked);
+ final String selfId = getOrCreateSelfId(db);
+
+ final ArrayList<StubActionServiceCallLog> calls = mService.getCalls();
+
+ // Insert initial message
+ MessageData initial = MessageData.createDraftSmsMessage(conversationId, selfId,
+ initialMessage);
+
+ BugleDatabaseOperations.updateDraftMessageData(db, conversationId, initial,
+ BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
+
+ initial = BugleDatabaseOperations.readDraftMessageData(db,
+ conversationId, selfId);
+ assertEquals("Initial text mismatch", initialMessage, initial.getMessageText());
+
+ // Now update the draft
+ final MessageData message = MessageData.createDraftSmsMessage(conversationId, selfId,
+ draftMessage);
+ WriteDraftMessageAction.writeDraftMessage(conversationId, message);
+
+ assertEquals("Failed to start service once for action", calls.size(), 1);
+ assertTrue("Action not SaveDraftMessageAction",
+ calls.get(0).action instanceof WriteDraftMessageAction);
+
+ final Action save = calls.get(0).action;
+
+ final Object result = save.executeAction();
+
+ assertTrue("Expect row number string as result", result instanceof String);
+
+ // Check DB
+ final MessageData actual = BugleDatabaseOperations.readDraftMessageData(db,
+ conversationId, selfId);
+ assertNotNull("Database missing draft", actual);
+ assertEquals("Draft text mismatch", draftMessage, actual.getMessageText());
+ assertNull("Draft messageId should be null", actual.getMessageId());
+ }
+
+ public void testBugleDatabaseDraftOperations() {
+ final String initialMessage = "initialMessage";
+ final String draftMessage = "draftMessage";
+ final long threadId = 1234599;
+ final boolean senderBlocked = false;
+ final String participantNumber = "5553456798";
+ final String subject = "subject here";
+
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final String conversationId = getOrCreateConversation(db, participantNumber, threadId,
+ senderBlocked);
+ final String selfId = getOrCreateSelfId(db);
+
+ final String text = "This is some text";
+ final Uri mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt");
+ OutputStream outputStream = null;
+ try {
+ outputStream = mContext.getContentResolver().openOutputStream(mOutputUri);
+ outputStream.write(text.getBytes());
+ } catch (final FileNotFoundException e) {
+ fail("Cannot open output file");
+ } catch (final IOException e) {
+ fail("Cannot write output file");
+ }
+
+ final MessageData initial =
+ MessageData.createDraftMmsMessage(conversationId, selfId, initialMessage, subject);
+ initial.addPart(MessagePartData.createMediaMessagePart(ContentType.MULTIPART_MIXED,
+ mOutputUri, 0, 0));
+
+ final String initialMessageId = BugleDatabaseOperations.updateDraftMessageData(db,
+ conversationId, initial, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
+ assertNotNull(initialMessageId);
+
+ final MessageData initialDraft = BugleDatabaseOperations.readMessage(db, initialMessageId);
+ assertNotNull(initialDraft);
+ int cnt = 0;
+ for(final MessagePartData part : initialDraft.getParts()) {
+ if (part.isAttachment()) {
+ assertEquals(part.getContentUri(), mOutputUri);
+ } else {
+ assertEquals(part.getText(), initialMessage);
+ }
+ cnt++;
+ }
+ assertEquals("Wrong number of parts", 2, cnt);
+
+ InputStream inputStream = null;
+ try {
+ inputStream = mContext.getContentResolver().openInputStream(mOutputUri);
+ final byte[] buffer = new byte[256];
+ final int read = inputStream.read(buffer);
+ assertEquals(read, text.getBytes().length);
+ } catch (final FileNotFoundException e) {
+ fail("Cannot open input file");
+ } catch (final IOException e) {
+ fail("Cannot read input file");
+ }
+
+ final String moreText = "This is some more text";
+ final Uri mAnotherUri = MediaScratchFileProvider.buildMediaScratchSpaceUri("txt");
+ outputStream = null;
+ try {
+ outputStream = mContext.getContentResolver().openOutputStream(mAnotherUri);
+ outputStream.write(moreText.getBytes());
+ } catch (final FileNotFoundException e) {
+ fail("Cannot open output file");
+ } catch (final IOException e) {
+ fail("Cannot write output file");
+ }
+
+ final MessageData another =
+ MessageData.createDraftMmsMessage(conversationId, selfId, draftMessage, subject);
+ another.addPart(MessagePartData.createMediaMessagePart(ContentType.MMS_MULTIPART_MIXED,
+ mAnotherUri, 0, 0));
+
+ final String anotherMessageId = BugleDatabaseOperations.updateDraftMessageData(db,
+ conversationId, another, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
+ assertNotNull(anotherMessageId);
+
+ final MessageData anotherDraft = BugleDatabaseOperations.readMessage(db, anotherMessageId);
+ assertNotNull(anotherDraft);
+ cnt = 0;
+ for(final MessagePartData part : anotherDraft.getParts()) {
+ if (part.isAttachment()) {
+ assertEquals(part.getContentUri(), mAnotherUri);
+ } else {
+ assertEquals(part.getText(), draftMessage);
+ }
+ cnt++;
+ }
+ assertEquals("Wrong number of parts", 2, cnt);
+
+ inputStream = null;
+ try {
+ inputStream = mContext.getContentResolver().openInputStream(mOutputUri);
+ assertNull("Original draft content should have been deleted", inputStream);
+ } catch (final FileNotFoundException e) {
+ }
+ inputStream = null;
+ try {
+ inputStream = mContext.getContentResolver().openInputStream(mAnotherUri);
+ final byte[] buffer = new byte[256];
+ final int read = inputStream.read(buffer);
+ assertEquals(read, moreText.getBytes().length);
+ } catch (final FileNotFoundException e) {
+ fail("Cannot open input file");
+ } catch (final IOException e) {
+ fail("Cannot read input file");
+ }
+
+ final MessageData last = null;
+ final String lastPartId = BugleDatabaseOperations.updateDraftMessageData(db,
+ conversationId, last, BugleDatabaseOperations.UPDATE_MODE_ADD_DRAFT);
+ assertNull(lastPartId);
+
+ inputStream = null;
+ try {
+ inputStream = mContext.getContentResolver().openInputStream(mAnotherUri);
+ assertNull("Original draft content should have been deleted", inputStream);
+ } catch (final FileNotFoundException e) {
+ }
+
+ }
+
+ private StubActionService mService;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final FakeContext context = new FakeContext(getTestContext());
+
+ final ContentProvider bugleProvider = new MessagingContentProvider();
+ final ProviderInfo bugleProviderInfo = new ProviderInfo();
+ bugleProviderInfo.authority = MessagingContentProvider.AUTHORITY;
+ bugleProvider.attachInfo(mContext, bugleProviderInfo);
+ context.addContentProvider(MessagingContentProvider.AUTHORITY, bugleProvider);
+ final ContentProvider mediaProvider = new MediaScratchFileProvider();
+ final ProviderInfo mediaProviderInfo = new ProviderInfo();
+ mediaProviderInfo.authority = MediaScratchFileProvider.AUTHORITY;
+ mediaProvider.attachInfo(mContext, mediaProviderInfo);
+ context.addContentProvider(MediaScratchFileProvider.AUTHORITY, mediaProvider);
+
+ mService = new StubActionService();
+ final FakeDataModel fakeDataModel = new FakeDataModel(context)
+ .withActionService(mService)
+ .withConnectivityUtil(new StubConnectivityUtil(context));
+ FakeFactory.registerWithFakeContext(getTestContext(), context)
+ .withDataModel(fakeDataModel);
+
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java
new file mode 100644
index 0000000..c6b9b20
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/data/ConversationMessageDataTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.datamodel.data;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.data.ConversationMessageData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns;
+
+@SmallTest
+public class ConversationMessageDataTest extends BugleTestCase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getTestContext());
+ }
+
+ public void testBindFirstMessage() {
+ final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor();
+ final ConversationMessageData data = new ConversationMessageData();
+ testCursor.moveToFirst();
+ data.bind(testCursor);
+ // TODO: Add before checking in all bound fields...
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 0).equals(
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
+ 0), data.getSenderProfilePhotoUri());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 0),
+ data.getSenderFullName());
+ }
+
+ public void testBindTwice() {
+ final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor();
+ final ConversationMessageData data = new ConversationMessageData();
+ testCursor.moveToPosition(1);
+ data.bind(testCursor);
+ assertEquals(TestDataFactory.getMessageText(testCursor, 1), data.getText());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 1),
+ data.getReceivedTimeStamp());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 1).equals(
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
+ 1), data.getSenderProfilePhotoUri());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 1),
+ data.getSenderFullName());
+ testCursor.moveToPosition(2);
+ data.bind(testCursor);
+ assertEquals(TestDataFactory.getMessageText(testCursor, 2), data.getText());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.RECEIVED_TIMESTAMP, 2),
+ data.getReceivedTimeStamp());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.STATUS, 2).equals(
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE), data.getIsIncoming());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
+ 2), data.getSenderProfilePhotoUri());
+ assertEquals(testCursor.getAt(ConversationMessageViewColumns.SENDER_FULL_NAME, 2),
+ data.getSenderFullName());
+ }
+
+ public void testMessageClustering() {
+ final FakeCursor testCursor = TestDataFactory.getConversationMessageCursor();
+ final ConversationMessageData data = new ConversationMessageData();
+ testCursor.moveToPosition(0);
+ data.bind(testCursor);
+ assertFalse(data.getCanClusterWithPreviousMessage());
+ assertFalse(data.getCanClusterWithNextMessage());
+
+ testCursor.moveToPosition(1);
+ data.bind(testCursor);
+ assertFalse(data.getCanClusterWithPreviousMessage());
+ assertFalse(data.getCanClusterWithNextMessage());
+
+ testCursor.moveToPosition(2);
+ data.bind(testCursor);
+ assertFalse(data.getCanClusterWithPreviousMessage());
+ assertTrue(data.getCanClusterWithNextMessage()); // 2 and 3 can be clustered
+ testCursor.moveToPosition(3);
+
+ data.bind(testCursor);
+ assertTrue(data.getCanClusterWithPreviousMessage()); // 2 and 3 can be clustered
+ assertFalse(data.getCanClusterWithNextMessage());
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java
new file mode 100644
index 0000000..527a600
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/data/ConversationParticipantsDataTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.datamodel.data;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.data.ConversationParticipantsData;
+import com.android.messaging.datamodel.data.ParticipantData;
+
+@SmallTest
+public class ConversationParticipantsDataTest extends BugleTestCase {
+ public void testBindParticipants() {
+ final FakeCursor testCursor = TestDataFactory.getConversationParticipantsCursor();
+ final ConversationParticipantsData data = new ConversationParticipantsData();
+ data.bind(testCursor);
+
+ assertEquals(data.getParticipantListExcludingSelf().size(), testCursor.getCount());
+ final ParticipantData participant2 = data.getParticipantById("2");
+ assertNotNull(participant2);
+ assertEquals(participant2.getFirstName(), testCursor.getAt(
+ ParticipantColumns.FIRST_NAME, 1) );
+ assertEquals(participant2.getSendDestination(), testCursor.getAt(
+ ParticipantColumns.SEND_DESTINATION, 1));
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java
new file mode 100644
index 0000000..8527e2b
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/data/TestDataFactory.java
@@ -0,0 +1,347 @@
+/*
+ * 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.datamodel.data;
+
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.MediaStore.Images.Media;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.DatabaseHelper;
+import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseHelper.PartColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
+import com.android.messaging.datamodel.data.ConversationMessageData.ConversationMessageViewColumns;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContactUtil;
+import com.android.messaging.util.ContentType;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A factory for fake objects that can be useful for multiple tests.
+ */
+public class TestDataFactory {
+ private final static String[] sConversationListCursorColumns = new String[] {
+ ConversationListViewColumns._ID,
+ ConversationListViewColumns.NAME,
+ ConversationListViewColumns.ICON,
+ ConversationListViewColumns.SNIPPET_TEXT,
+ ConversationListViewColumns.PREVIEW_URI,
+ ConversationListViewColumns.SORT_TIMESTAMP,
+ ConversationListViewColumns.READ,
+ ConversationListViewColumns.PREVIEW_CONTENT_TYPE,
+ ConversationListViewColumns.MESSAGE_STATUS,
+ };
+
+ private final static String[] sContactCursorColumns = new String[] {
+ Phone.CONTACT_ID,
+ Phone.DISPLAY_NAME_PRIMARY,
+ Phone.PHOTO_THUMBNAIL_URI,
+ Phone.NUMBER,
+ Phone.TYPE,
+ Phone.LABEL,
+ Phone.LOOKUP_KEY,
+ Phone._ID,
+ Phone.SORT_KEY_PRIMARY,
+ };
+
+ private final static String[] sFrequentContactCursorColumns = new String[] {
+ Contacts._ID,
+ Contacts.DISPLAY_NAME,
+ Contacts.PHOTO_URI,
+ Phone.LOOKUP_KEY,
+ };
+
+ private final static String[] sConversationMessageCursorColumns = new String[] {
+ ConversationMessageViewColumns._ID,
+ ConversationMessageViewColumns.CONVERSATION_ID,
+ ConversationMessageViewColumns.PARTICIPANT_ID,
+ ConversationMessageViewColumns.SENT_TIMESTAMP,
+ ConversationMessageViewColumns.RECEIVED_TIMESTAMP,
+ ConversationMessageViewColumns.STATUS,
+ ConversationMessageViewColumns.SENDER_FULL_NAME,
+ ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
+ ConversationMessageViewColumns.PARTS_IDS,
+ ConversationMessageViewColumns.PARTS_CONTENT_TYPES,
+ ConversationMessageViewColumns.PARTS_CONTENT_URIS,
+ ConversationMessageViewColumns.PARTS_WIDTHS,
+ ConversationMessageViewColumns.PARTS_HEIGHTS,
+ ConversationMessageViewColumns.PARTS_TEXTS,
+ ConversationMessageViewColumns.PARTS_COUNT
+ };
+
+ private final static String[] sGalleryCursorColumns = new String[] {
+ Media._ID,
+ Media.DATA,
+ Media.WIDTH,
+ Media.HEIGHT,
+ Media.MIME_TYPE
+ };
+
+ public static FakeCursor getConversationListCursor() {
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(1), "name1", "content://icon1",
+ "snippetText1", "content://snippetUri1", Long.valueOf(10), 1,
+ ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE},
+ new Object[] { Long.valueOf(2), "name2", "content://icon2",
+ "snippetText2", "content://snippetUri2", Long.valueOf(20) + 24*60*60*1000,
+ 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_INCOMING_COMPLETE},
+ new Object[] { Long.valueOf(3), "name3", "content://icon3",
+ "snippetText3", "content://snippetUri3", Long.valueOf(30) + 2*24*60*60*1000,
+ 0, ContentType.IMAGE_JPEG, MessageData.BUGLE_STATUS_OUTGOING_COMPLETE}
+ };
+ return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns,
+ cursorData);
+ }
+ public static final int CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX = 0;
+ public static final int CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX = 1;
+
+ public static FakeCursor getEmptyConversationListCursor() {
+ return new FakeCursor(ConversationListItemData.PROJECTION, sConversationListCursorColumns,
+ new Object[][] {});
+ }
+
+ public static FakeCursor getConversationMessageCursor() {
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(0), Long.valueOf(1), Long.valueOf(1),
+ Long.valueOf(10), Long.valueOf(10),
+ MessageData.BUGLE_STATUS_INCOMING_COMPLETE, "Alice", null,
+ "0", "text/plain", "''", -1, -1, "msg0", 1},
+ new Object[] { Long.valueOf(1), Long.valueOf(1), Long.valueOf(2),
+ Long.valueOf(20), Long.valueOf(20),
+ MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Bob", null,
+ "1", "text/plain", "''", -1, -1, "msg1", 1},
+ new Object[] { Long.valueOf(2), Long.valueOf(1), Long.valueOf(1),
+ Long.valueOf(30), Long.valueOf(30),
+ MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null,
+ "2", "contentType3", "'content://fakeUri3'", "0", "0", "msg1", 1},
+ new Object[] { Long.valueOf(3), Long.valueOf(1), Long.valueOf(1),
+ Long.valueOf(40), Long.valueOf(40),
+ MessageData.BUGLE_STATUS_OUTGOING_COMPLETE, "Alice", null,
+ "3|4", "'contentType4'|'text/plain'", "'content://fakeUri4'|''", "0|-1", "0|-1", "''|'msg3'", 2},
+ };
+ return new FakeCursor(
+ ConversationMessageData.getProjection(),
+ sConversationMessageCursorColumns,
+ cursorData);
+ }
+
+ public static String getMessageText(final FakeCursor messageCursor, final int row) {
+ final String allPartsText = messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row)
+ .toString();
+ final int partsCount = (Integer) messageCursor.getAt(
+ ConversationMessageViewColumns.PARTS_COUNT, row);
+ final String messageId = messageCursor.getAt(
+ ConversationMessageViewColumns._ID, row).toString();
+ final List<MessagePartData> parts = ConversationMessageData.makeParts(
+ messageCursor.getAt(ConversationMessageViewColumns.PARTS_IDS, row).toString(),
+ messageCursor.getAt(ConversationMessageViewColumns.PARTS_CONTENT_TYPES, row).toString(),
+ messageCursor.getAt(ConversationMessageViewColumns.PARTS_CONTENT_URIS, row).toString(),
+ messageCursor.getAt(ConversationMessageViewColumns.PARTS_WIDTHS, row).toString(),
+ messageCursor.getAt(ConversationMessageViewColumns.PARTS_HEIGHTS, row).toString(),
+ messageCursor.getAt(ConversationMessageViewColumns.PARTS_TEXTS, row).toString(),
+ partsCount,
+ messageId);
+
+ for (final MessagePartData part : parts) {
+ if (part.isText()) {
+ return part.getText();
+ }
+ }
+ return null;
+ }
+
+ // Indexes where to find consecutive and non consecutive messages from same participant
+ // (respect to index - 1).
+ public static final int MESSAGE_WITH_SAME_PARTICIPANT_AS_PREVIOUS = 3;
+ public static final int MESSAGE_WITH_DIFFERENT_PARTICIPANT_AS_PREVIOUS = 2;
+
+ public static FakeCursor getConversationParticipantsCursor() {
+ final String[] sConversationParticipantsCursorColumns = new String[] {
+ ParticipantColumns._ID,
+ ParticipantColumns.SUB_ID,
+ ParticipantColumns.NORMALIZED_DESTINATION,
+ ParticipantColumns.SEND_DESTINATION,
+ ParticipantColumns.FULL_NAME,
+ ParticipantColumns.FIRST_NAME,
+ ParticipantColumns.PROFILE_PHOTO_URI,
+ };
+
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { 1, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15554567890",
+ "(555)456-7890", "alice in wonderland", "alice", "alice.png" },
+ new Object[] { 2, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15551011121",
+ "(555)101-1121", "bob the baker", "bob", "bob.png"},
+ new Object[] { 3, ParticipantData.OTHER_THAN_SELF_SUB_ID, "+15551314152",
+ "(555)131-4152", "charles in charge", "charles", "charles.png" },
+ };
+
+ return new FakeCursor(ParticipantData.ParticipantsQuery.PROJECTION,
+ sConversationParticipantsCursorColumns, cursorData);
+ }
+
+ public static final int CONTACT_LIST_CURSOR_FIRST_LEVEL_CONTACT_INDEX = 0;
+ public static final int CONTACT_LIST_CURSOR_SECOND_LEVEL_CONTACT_INDEX = 2;
+
+ /**
+ * Returns a cursor for the all contacts list consumable by ContactPickerFragment.
+ */
+ public static FakeCursor getAllContactListCursor() {
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(0), "John Smith", "content://uri1",
+ "425-555-1234", Phone.TYPE_HOME, "", "0", Long.valueOf(0), 0 },
+ new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2",
+ "425-555-1235", Phone.TYPE_MOBILE, "", "1", Long.valueOf(1), 1 },
+ new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2",
+ "425-555-1238", Phone.TYPE_HOME, "", "1", Long.valueOf(2), 2 },
+ new Object[] { Long.valueOf(2), "Anna Kinney", "content://uri3",
+ "425-555-1236", Phone.TYPE_MAIN, "", "3", Long.valueOf(3), 3 },
+ new Object[] { Long.valueOf(3), "Mike Jones", "content://uri3",
+ "425-555-1236", Phone.TYPE_MAIN, "", "5", Long.valueOf(4), 4 },
+ };
+ return new FakeCursor(ContactUtil.PhoneQuery.PROJECTION, sContactCursorColumns,
+ cursorData);
+ }
+
+ /**
+ * Returns a cursor for the frequent contacts list consumable by ContactPickerFragment.
+ * Note: make it so that this cursor is the generated result of getStrequentContactsCursor()
+ * and getAllContactListCursor(), i.e., expand the entries in getStrequentContactsCursor()
+ * with the details from getAllContactListCursor()
+ */
+ public static FakeCursor getFrequentContactListCursor() {
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(2), "Anna Kinney", "content://uri3",
+ "425-555-1236", Phone.TYPE_MAIN, "", "3", Long.valueOf(3), 0 },
+ new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2",
+ "425-555-1235", Phone.TYPE_MOBILE, "", "1", Long.valueOf(1), 1},
+ new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2",
+ "425-555-1238", Phone.TYPE_HOME, "", "1", Long.valueOf(2), 2 },
+ new Object[] { Long.valueOf(0), "John Smith", "content://uri1",
+ "425-555-1234", Phone.TYPE_HOME, "", "0", Long.valueOf(0), 3 },
+ };
+ return new FakeCursor(ContactUtil.PhoneQuery.PROJECTION, sContactCursorColumns,
+ cursorData);
+ }
+
+ /**
+ * Returns a strequent (starred + frequent) cursor (like the one produced by android contact
+ * provider's CONTENT_STREQUENT_URI query) that's consumable by FrequentContactsCursorBuilder.
+ */
+ public static FakeCursor getStrequentContactsCursor() {
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(0), "Anna Kinney", "content://uri1", "3" },
+ new Object[] { Long.valueOf(1), "Sun Woo Kong", "content://uri2", "1" },
+ new Object[] { Long.valueOf(2), "John Smith", "content://uri3", "0" },
+ // Email-only entry that shouldn't be included in the result.
+ new Object[] { Long.valueOf(3), "Email Contact", "content://uri4", "100" },
+ };
+ return new FakeCursor(ContactUtil.FrequentContactQuery.PROJECTION,
+ sFrequentContactCursorColumns, cursorData);
+ }
+
+ public static final int SMS_MMS_THREAD_ID_CURSOR_VALUE = 123456789;
+
+ public static FakeCursor getSmsMmsThreadIdCursor() {
+ final String[] ID_PROJECTION = { BaseColumns._ID };
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(SMS_MMS_THREAD_ID_CURSOR_VALUE) },
+ };
+ return new FakeCursor(ID_PROJECTION, ID_PROJECTION, cursorData);
+ }
+
+ public static FakeCursor getGalleryGridCursor() {
+ final Object[][] cursorData = new Object[][] {
+ new Object[] { Long.valueOf(0), "/sdcard/image1", 100, 100, "image/jpeg" },
+ new Object[] { Long.valueOf(1), "/sdcard/image2", 200, 200, "image/png" },
+ new Object[] { Long.valueOf(2), "/sdcard/image3", 300, 300, "image/jpeg" },
+ };
+ return new FakeCursor(GalleryGridItemData.IMAGE_PROJECTION, sGalleryCursorColumns,
+ cursorData);
+ }
+
+ public static final int NUM_TEST_CONVERSATIONS = 10;
+
+ /**
+ * Create test data in our db.
+ *
+ * Ideally this will create more realistic data with more variety.
+ */
+ public static void createTestData(final SQLiteDatabase db) {
+ BugleDatabaseOperations.clearParticipantIdCache();
+
+ // Timestamp for 1 day ago
+ final long yesterday = System.currentTimeMillis() - (24 * 60 * 60 * 1000);
+
+ final ContentValues conversationValues = new ContentValues();
+ for (int i = 1; i <= NUM_TEST_CONVERSATIONS; i++) {
+ conversationValues.put(ConversationColumns.NAME, "Conversation " + i);
+ final long conversationId = db.insert(DatabaseHelper.CONVERSATIONS_TABLE, null,
+ conversationValues);
+
+ final ContentValues messageValues = new ContentValues();
+ for (int m = 1; m <= 25; m++) {
+ // Move forward ten minutes per conversation, 1 minute per message.
+ final long messageTime = yesterday + (i * 10 * 60 * 1000) + (m * 60 * 1000);
+ messageValues.put(MessageColumns.RECEIVED_TIMESTAMP, messageTime);
+ messageValues.put(MessageColumns.CONVERSATION_ID, conversationId);
+ messageValues.put(MessageColumns.SENDER_PARTICIPANT_ID,
+ Math.abs(("" + messageTime).hashCode()) % 2);
+ final long messageId = db.insert(DatabaseHelper.MESSAGES_TABLE, null, messageValues);
+
+ // Create a text part for this message
+ final ContentValues partValues = new ContentValues();
+ partValues.put(PartColumns.MESSAGE_ID, messageId);
+ partValues.put(PartColumns.CONVERSATION_ID, conversationId);
+ partValues.put(PartColumns.TEXT, "Conversation: " + conversationId +
+ " Message: " + m);
+ db.insert(DatabaseHelper.PARTS_TABLE, null, partValues);
+
+ // Update the snippet for this conversation to the latest message inserted
+ conversationValues.clear();
+ conversationValues.put(ConversationColumns.LATEST_MESSAGE_ID, messageId);
+ final int updatedCount = db.update(DatabaseHelper.CONVERSATIONS_TABLE,
+ conversationValues,
+ "_id=?", new String[]{String.valueOf(conversationId)});
+ Assert.isTrue(updatedCount == 1);
+ }
+ }
+ }
+
+ public static List<MessagePartData> getTestDraftAttachments() {
+ final MessagePartData[] retParts = new MessagePartData[] {
+ new MessagePartData(ContentType.IMAGE_JPEG, Uri.parse("content://image"),
+ 100, 100),
+ new MessagePartData(ContentType.VIDEO_3GPP, Uri.parse("content://video"),
+ 100, 100),
+ new MessagePartData(ContentType.TEXT_VCARD, Uri.parse("content://vcard"),
+ 0, 0),
+ new MessagePartData(ContentType.AUDIO_3GPP, Uri.parse("content://audio"),
+ 0, 0)
+ };
+ return Arrays.asList(retParts);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java
new file mode 100644
index 0000000..7f6ac3f
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/media/FakeImageRequest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.datamodel.media;
+
+import android.text.TextUtils;
+
+import java.util.List;
+
+public class FakeImageRequest implements MediaRequest<FakeImageResource> {
+ public static final String INVALID_KEY = "invalid";
+ private final String mKey;
+ private final int mSize;
+
+ public FakeImageRequest(final String key, final int size) {
+ mKey = key;
+ mSize = size;
+ }
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ public FakeImageResource loadMediaBlocking(List<MediaRequest<FakeImageResource>> chainedTask)
+ throws Exception {
+ if (TextUtils.equals(mKey, INVALID_KEY)) {
+ throw new Exception();
+ } else {
+ return new FakeImageResource(mSize, mKey);
+ }
+ }
+
+ @Override
+ public int getCacheId() {
+ return FakeMediaCacheManager.FAKE_IMAGE_CACHE;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public MediaCache<FakeImageResource> getMediaCache() {
+ return (MediaCache<FakeImageResource>) MediaCacheManager.get().getOrCreateMediaCacheById(
+ getCacheId());
+ }
+
+ @Override
+ public int getRequestType() {
+ return MediaRequest.REQUEST_LOAD_MEDIA;
+ }
+
+ @Override
+ public MediaRequestDescriptor<FakeImageResource> getDescriptor() {
+ return null;
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java
new file mode 100644
index 0000000..969854f
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/media/FakeImageResource.java
@@ -0,0 +1,55 @@
+/*
+ * 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.datamodel.media;
+
+public class FakeImageResource extends RefCountedMediaResource {
+ private boolean mClosed = false;
+ private boolean mCached = false;
+ private final int mSize;
+ private final String mImageId;
+
+ public FakeImageResource(final int size, final String imageId) {
+ super(null);
+ mSize = size;
+ mImageId = imageId;
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public String getImageId() {
+ return mImageId;
+ }
+
+ public void setCached(final boolean cached) {
+ mCached = cached;
+ }
+
+ public boolean getCached() {
+ return mCached;
+ }
+
+ @Override
+ public int getMediaSize() {
+ return mSize;
+ }
+
+ @Override
+ protected void close() {
+ mClosed = true;
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java
new file mode 100644
index 0000000..34b3a55
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/media/FakeMediaCacheManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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.datamodel.media;
+
+public class FakeMediaCacheManager extends MediaCacheManager {
+ // List of available fake cache ids.
+ public static final int FAKE_IMAGE_CACHE = 1;
+ public static final int FAKE_BATCH_IMAGE_CACHE = 2;
+
+ @Override
+ public MediaCache<?> createMediaCacheById(final int id) {
+ switch (id) {
+ case FAKE_IMAGE_CACHE:
+ // Make a cache of only 3 KB of data.
+ return new MediaCache<FakeImageResource>(3, FAKE_IMAGE_CACHE, "FakeImageCache");
+
+ case FAKE_BATCH_IMAGE_CACHE:
+ return new MediaCache<FakeImageResource>(10, FAKE_BATCH_IMAGE_CACHE,
+ "FakeBatchImageCache");
+ }
+ return null;
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java
new file mode 100644
index 0000000..2cfec7d
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/media/ImageRequestTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.datamodel.media;
+
+import android.content.ContentResolver;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.util.ImageUtils;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+import java.io.IOException;
+
+@SmallTest
+public class ImageRequestTest extends BugleTestCase {
+ private static final int DOWNSAMPLE_IMAGE_SIZE = 2;
+
+ @Spy protected ImageUtils spyImageUtils;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getTestContext())
+ .withMemoryCacheManager(new MemoryCacheManager())
+ .withMediaCacheManager(new BugleMediaCacheManager());
+ spyImageUtils = Mockito.spy(new ImageUtils());
+ ImageUtils.set(spyImageUtils);
+ }
+
+ public void testLoadImageUnspecifiedSize() {
+ final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+ getContext().getPackageName() + "/" + R.drawable.ic_audio_light;
+ final Uri uri = Uri.parse(uriString);
+ final UriImageRequest imageRequest = new UriImageRequest(getContext(),
+ new UriImageRequestDescriptor(uri));
+ try {
+ final ImageResource imageResource = imageRequest.loadMediaBlocking(null);
+ final ArgumentCaptor<BitmapFactory.Options> options =
+ ArgumentCaptor.forClass(BitmapFactory.Options.class);
+ Mockito.verify(spyImageUtils).calculateInSampleSize(
+ options.capture(),
+ Matchers.eq(ImageRequest.UNSPECIFIED_SIZE),
+ Matchers.eq(ImageRequest.UNSPECIFIED_SIZE));
+ assertEquals(1, options.getValue().inSampleSize);
+ assertNotNull(imageResource);
+ assertNotNull(imageResource.getBitmap());
+
+ // Make sure there's no scaling on the bitmap.
+ final int bitmapWidth = imageResource.getBitmap().getWidth();
+ final int bitmapHeight = imageResource.getBitmap().getHeight();
+ assertEquals(options.getValue().outWidth, bitmapWidth);
+ assertEquals(options.getValue().outHeight, bitmapHeight);
+ } catch (final IOException e) {
+ fail("IO exception while trying to load image resource");
+ }
+ }
+
+ public void testLoadImageWithDownsampling() {
+ final String uriString = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+ getContext().getPackageName() + "/" + R.drawable.ic_audio_light;
+ final Uri uri = Uri.parse(uriString);
+ final UriImageRequest imageRequest = new UriImageRequest(getContext(),
+ new UriImageRequestDescriptor(uri, DOWNSAMPLE_IMAGE_SIZE, DOWNSAMPLE_IMAGE_SIZE,
+ false, true /* isStatic */, false /* cropToCircle */,
+ ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
+ ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */));
+ try {
+ final ImageResource imageResource = imageRequest.loadMediaBlocking(null);
+ final ArgumentCaptor<BitmapFactory.Options> options =
+ ArgumentCaptor.forClass(BitmapFactory.Options.class);
+ Mockito.verify(spyImageUtils).calculateInSampleSize(
+ options.capture(),
+ Matchers.eq(DOWNSAMPLE_IMAGE_SIZE), Matchers.eq(DOWNSAMPLE_IMAGE_SIZE));
+ assertNotSame(1, options.getValue().inSampleSize);
+ assertNotNull(imageResource);
+ assertNotNull(imageResource.getBitmap());
+
+ // Make sure there's down sampling on the bitmap.
+ final int bitmapWidth = imageResource.getBitmap().getWidth();
+ final int bitmapHeight = imageResource.getBitmap().getHeight();
+ assertTrue(bitmapWidth >= DOWNSAMPLE_IMAGE_SIZE &&
+ bitmapHeight >= DOWNSAMPLE_IMAGE_SIZE &&
+ (bitmapWidth <= DOWNSAMPLE_IMAGE_SIZE * 4 ||
+ bitmapHeight <= DOWNSAMPLE_IMAGE_SIZE * 4));
+ } catch (final IOException e) {
+ fail("IO exception while trying to load image resource");
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java
new file mode 100644
index 0000000..d214067
--- /dev/null
+++ b/tests/src/com/android/messaging/datamodel/media/MediaResourceManagerTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.datamodel.media;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
+
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+public class MediaResourceManagerTest extends BugleTestCase {
+ private static final int KB = 1024;
+
+ // Loaded image resource from the MediaResourceManager callback.
+ private FakeImageResource mImageResource;
+ private BindableMediaRequest<FakeImageResource> mImageRequest;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getTestContext())
+ .withMemoryCacheManager(new MemoryCacheManager())
+ .withMediaCacheManager(new FakeMediaCacheManager());
+ }
+
+ public void testLoadFromCache() {
+ final MediaResourceManager mediaResourceManager =
+ new MediaResourceManager();
+ MediaCacheManager.get().reclaim();
+ assertNotNull(mediaResourceManager);
+
+ // Load one image of 1KB
+ loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false);
+ assertEquals("image1", mImageResource.getImageId());
+ final FakeImageResource loadedResource = mImageResource;
+
+ // Load the same image.
+ loadImage(mediaResourceManager, "image1", 1 * KB, true /* shouldBeCached */, false);
+ assertEquals(loadedResource, mImageResource);
+ }
+
+ public void testCacheEviction() {
+ final MediaResourceManager mediaResourceManager =
+ new MediaResourceManager();
+ MediaCacheManager.get().reclaim();
+ assertNotNull(mediaResourceManager);
+
+ // Load one image of 1KB
+ loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false);
+ assertEquals("image1", mImageResource.getImageId());
+
+ // Load another image
+ loadImage(mediaResourceManager, "image2", 2 * KB, false /* shouldBeCached */, false);
+ assertEquals("image2", mImageResource.getImageId());
+
+ // Load another image. This should fill the cache and cause eviction of image1.
+ loadImage(mediaResourceManager, "image3", 2 * KB, false /* shouldBeCached */, false);
+ assertEquals("image3", mImageResource.getImageId());
+
+ // Load image1. It shouldn't be cached any more.
+ loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false);
+ assertEquals("image1", mImageResource.getImageId());
+ }
+
+ public void testReclaimMemoryFromMediaCache() {
+ final MediaResourceManager mediaResourceManager =
+ new MediaResourceManager();
+ MediaCacheManager.get().reclaim();
+ assertNotNull(mediaResourceManager);
+
+ // Load one image of 1KB
+ loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false);
+ assertEquals("image1", mImageResource.getImageId());
+
+ // Purge everything from the cache, now the image should no longer be cached.
+ MediaCacheManager.get().reclaim();
+
+ // The image resource should have no ref left.
+ assertEquals(0, mImageResource.getRefCount());
+ assertTrue(mImageResource.isClosed());
+ loadImage(mediaResourceManager, "image1", 1 * KB, false /* shouldBeCached */, false);
+ assertEquals("image1", mImageResource.getImageId());
+ }
+
+ public void testLoadInvalidImage() {
+ final MediaResourceManager mediaResourceManager =
+ new MediaResourceManager();
+ MediaCacheManager.get().reclaim();
+ assertNotNull(mediaResourceManager);
+
+ // Test the failure case with invalid resource.
+ loadImage(mediaResourceManager, FakeImageRequest.INVALID_KEY, 1 * KB, false,
+ true /* shouldFail */);
+ }
+
+ public void testLoadImageSynchronously() {
+ final MediaResourceManager mediaResourceManager =
+ new MediaResourceManager();
+ MediaCacheManager.get().reclaim();
+ assertNotNull(mediaResourceManager);
+
+ // Test a normal sync load.
+ final FakeImageRequest request = new FakeImageRequest("image1", 1 * KB);
+ final FakeImageResource resource = mediaResourceManager.requestMediaResourceSync(request);
+ assertNotNull(resource);
+ assertFalse(resource.isClosed());
+ assertNotSame(0, resource.getRefCount());
+ resource.release();
+
+ // Test a failed sync load.
+ final FakeImageRequest invalidRequest =
+ new FakeImageRequest(FakeImageRequest.INVALID_KEY, 1 * KB);
+ assertNull(mediaResourceManager.requestMediaResourceSync(invalidRequest));
+ }
+
+ private void loadImage(final MediaResourceManager manager, final String key,
+ final int size, final boolean shouldBeCached, final boolean shouldFail) {
+ try {
+ final CountDownLatch signal = new CountDownLatch(1);
+ mImageRequest = AsyncMediaRequestWrapper.createWith(new FakeImageRequest(key, size),
+ createAssertListener(shouldBeCached, shouldFail, signal));
+ mImageRequest.bind("1");
+ manager.requestMediaResourceAsync(mImageRequest);
+
+ // Wait for the asynchronous callback before proceeding.
+ signal.await();
+ } catch (final InterruptedException e) {
+ fail("Something interrupted the signal await.");
+ }
+ }
+
+ private MediaResourceLoadListener<FakeImageResource> createAssertListener(
+ final boolean shouldBeCached, final boolean shouldFail, final CountDownLatch signal) {
+ return new MediaResourceLoadListener<FakeImageResource>() {
+ @Override
+ public void onMediaResourceLoaded(final MediaRequest<FakeImageResource> request,
+ final FakeImageResource resource, final boolean isCached) {
+ assertEquals(mImageRequest, request);
+ assertNotNull(resource);
+ assertFalse(resource.isClosed());
+ assertNotSame(0, resource.getRefCount());
+ assertFalse(shouldFail);
+ assertEquals(shouldBeCached, resource.getCached());
+ resource.setCached(true);
+ mImageResource = resource;
+ signal.countDown();
+ }
+
+ @Override
+ public void onMediaResourceLoadError(
+ final MediaRequest<FakeImageResource> request, final Exception exception) {
+ assertTrue(shouldFail);
+ mImageResource = null;
+ signal.countDown();
+ }};
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java b/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java
new file mode 100644
index 0000000..5ea6aa7
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/ActivityInstrumentationTestCaseIntent.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+
+/**
+ * Purpose of this class is providing a workaround for https://b/14561718
+ */
+public class ActivityInstrumentationTestCaseIntent extends Intent {
+ public ActivityInstrumentationTestCaseIntent(Context packageContext, Class<?> cls) {
+ super(packageContext, cls);
+ }
+ @Override
+ public Intent setComponent(ComponentName component) {
+ // Ignore the ComponentName set, as the one ActivityUnitTest does is wrong (and actually
+ // unnecessary).
+ return this;
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java b/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java
new file mode 100644
index 0000000..60cddff
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/BugleActivityInstrumentationTestCase.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import android.app.Activity;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.TestUtil;
+
+/**
+ * Helper class that extends ActivityInstrumentationTestCase2 to provide some extra common
+ * initialization (eg. Mockito boilerplate).
+ */
+public class BugleActivityInstrumentationTestCase<T extends Activity>
+ extends android.test.ActivityInstrumentationTestCase2<T> {
+
+ static {
+ // Set flag during loading of test cases to prevent application initialization starting
+ BugleTestCase.setTestsRunning();
+ }
+
+ public BugleActivityInstrumentationTestCase(final Class<T> activityClass) {
+ super(activityClass);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ setActivityInitialTouchMode(false);
+ TestUtil.testSetup(getInstrumentation().getTargetContext(), this);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ TestUtil.testTeardown(this);
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/ui/BugleActivityTest.java b/tests/src/com/android/messaging/ui/BugleActivityTest.java
new file mode 100644
index 0000000..05e32fa
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/BugleActivityTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.DataModel;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+public abstract class BugleActivityTest extends BugleActivityUnitTestCase<BugleActionBarActivity> {
+ @Mock protected DataModel mDataModel;
+
+ public BugleActivityTest() {
+ super(BugleActionBarActivity.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Create activity
+ final ActivityInstrumentationTestCaseIntent intent =
+ new ActivityInstrumentationTestCaseIntent(getInstrumentation().getTargetContext(),
+ TestActivity.class);
+ startActivity(intent, null, null);
+
+ FakeFactory.register(getInstrumentation().getTargetContext())
+ .withDataModel(mDataModel);
+ }
+
+ public void testOnResumeDataModelCallback() {
+ getInstrumentation().callActivityOnStart(getActivity());
+ getInstrumentation().callActivityOnResume(getActivity());
+ Mockito.verify(mDataModel).onActivityResume();
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java b/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java
new file mode 100644
index 0000000..dcbd785
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/BugleActivityUnitTestCase.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.view.ContextThemeWrapper;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.R;
+import com.android.messaging.TestUtil;
+
+/**
+ * Base class for activity unit test cases, provides boilerplate setup/teardown.
+ */
+public abstract class BugleActivityUnitTestCase<T extends Activity> extends
+ android.test.ActivityUnitTestCase<T> {
+
+ static {
+ // Set flag during loading of test cases to prevent application initialization starting
+ BugleTestCase.setTestsRunning();
+ }
+
+ public BugleActivityUnitTestCase(final Class<T> activityClass) {
+ super(activityClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ TestUtil.testSetup(getInstrumentation().getTargetContext(), this);
+
+ setActivityContext(new ContextThemeWrapper(getInstrumentation().getTargetContext(),
+ R.style.Theme_AppCompat_Light_DarkActionBar));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ TestUtil.testTeardown(this);
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java b/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java
new file mode 100644
index 0000000..011cb82
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/CustomHeaderViewPagerTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SimpleCursorAdapter;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+
+public class CustomHeaderViewPagerTest extends ViewTest<CustomHeaderViewPager> {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getInstrumentation().getTargetContext());
+ }
+
+ public void testBindFirstLevel() {
+ final CustomHeaderViewPager view = new CustomHeaderViewPager(getActivity(), null);
+ final SimpleCursorAdapter adapter =
+ new SimpleCursorAdapter(getActivity(), 0, null, null, null, 0);
+ final CustomHeaderPagerViewHolder[] viewHolders = {
+ new FakeListViewHolder(getActivity(), adapter),
+ new FakeListViewHolder(getActivity(), adapter)
+ };
+
+ view.setViewHolders(viewHolders);
+ final ViewPager pager = (ViewPager) view.findViewById(R.id.pager);
+ final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.tab_strip);
+ final ViewPagerTabStrip realTab = (ViewPagerTabStrip) tabStrip.getChildAt(0);
+
+ assertEquals(2, realTab.getChildCount());
+ View headerTitleButton = realTab.getChildAt(1);
+ // Click on the first page. Now the view pager should switch to that page accordingly.
+ clickButton(headerTitleButton);
+ assertEquals(1, pager.getCurrentItem());
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ // All set up should be done by creating a CustomHeaderViewPager which handles inflating
+ // the layout
+ return 0;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/FakeListViewHolder.java b/tests/src/com/android/messaging/ui/FakeListViewHolder.java
new file mode 100644
index 0000000..d4de885
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/FakeListViewHolder.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.widget.CursorAdapter;
+
+import com.android.messaging.R;
+
+/**
+ * A fake {@link CustomHeaderPagerListViewHolder} for CustomHeaderViewPager tests only.
+ */
+public class FakeListViewHolder extends CustomHeaderPagerListViewHolder {
+ public FakeListViewHolder(final Context context, final CursorAdapter adapter) {
+ super(context, adapter);
+ }
+
+ @Override
+ protected int getLayoutResId() {
+ return 0;
+ }
+
+ @Override
+ protected int getPageTitleResId() {
+ return android.R.string.untitled;
+ }
+
+ @Override
+ protected int getEmptyViewResId() {
+ return R.id.empty_view;
+ }
+
+ @Override
+ protected int getListViewResId() {
+ return android.R.id.list;
+ }
+
+ @Override
+ protected int getEmptyViewTitleResId() {
+ return R.string.contact_list_empty_text;
+ }
+
+ @Override
+ protected int getEmptyViewImageResId() {
+ return R.drawable.ic_oobe_freq_list;
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/ui/FragmentTestCase.java b/tests/src/com/android/messaging/ui/FragmentTestCase.java
new file mode 100644
index 0000000..eb65dc6
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/FragmentTestCase.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.res.Configuration;
+import android.view.View;
+
+/**
+ * Helper class that extends Bugle.ui.ActivityInstrumentationTestCase to provide common behavior
+ * across fragment tests.
+ */
+public abstract class FragmentTestCase<T extends Fragment>
+ extends BugleActivityInstrumentationTestCase<TestActivity> {
+
+ protected T mFragment;
+ protected Class<T> mFragmentClass;
+
+ public FragmentTestCase(final Class<T> fragmentClass) {
+ super(TestActivity.class);
+ mFragmentClass = fragmentClass;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ protected T getFragment() {
+ // Fragment creation deferred (typically until test time) so that factory/appcontext is
+ // ready.
+ if (mFragment == null) {
+ try {
+ mFragment = mFragmentClass.newInstance();
+ } catch (final InstantiationException e) {
+ throw new IllegalStateException("Failed to instantiate fragment");
+ } catch (final IllegalAccessException e) {
+ throw new IllegalStateException("Failed to instantiate fragment");
+ }
+ }
+
+ return mFragment;
+ }
+
+ protected void attachFragment() {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final FragmentManager fragmentManager = getActivity().getFragmentManager();
+ fragmentManager.beginTransaction().add(mFragment, null /* tag */).commit();
+ }
+ });
+
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // In landscape mode, sleep for a second first.
+ // The reason is: our UI tests don't wait for the UI thread to finish settling down
+ // before exiting (because they can't know when the UI thread is done). In portrait mode,
+ // things generally work fine here -- the UI thread is done by the time the test is done.
+ // In landscape mode, though, since the launcher is in portrait mode, there is a lot of
+ // extra work that happens in our UI when the app launches into landscape mode, and the
+ // UI is often not done by the time the test finishes running. So then our teardown
+ // nulls out the Factory, and then the UI keeps running and derefs the null factory,
+ // and things blow up.
+ // So ... as a cheap hack, sleep for one second before finishing the teardown of UI
+ // tests, but only do it in landscape mode (so that developers running it in portrait
+ // mode can still run the tests faster).
+ if (this.getInstrumentation().getTargetContext().getResources().
+ getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ super.tearDown();
+ }
+
+ protected void clickButton(final View view) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.performClick();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ protected void setFocus(final View view, final boolean focused) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (focused) {
+ view.requestFocus();
+ } else {
+ view.clearFocus();
+ }
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java b/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java
new file mode 100644
index 0000000..cf9a647
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/MultiAttachmentLayoutTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+
+import android.content.Context;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.datamodel.data.MessagePartData;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+@MediumTest
+public class MultiAttachmentLayoutTest extends ViewTest<MultiAttachmentLayout> {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getTargetContext();
+ FakeFactory.register(context);
+ }
+
+ @Override
+ protected MultiAttachmentLayout getView() {
+ if (mView == null) {
+ // View creation deferred (typically until test time) so that factory/appcontext is
+ // ready.
+ mView = new MultiAttachmentLayout(getActivity(), null);
+ mView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
+ }
+ return mView;
+ }
+
+ protected void verifyContent(
+ final MultiAttachmentLayout view,
+ final int imageCount,
+ final int plusCount) {
+ final int count = view.getChildCount();
+ int actualImageCount = 0;
+ final boolean needPlusText = plusCount > 0;
+ boolean hasPlusText = false;
+ for (int i = 0; i < count; i++) {
+ final View child = view.getChildAt(i);
+ if (child instanceof AsyncImageView) {
+ actualImageCount++;
+ } else if (child instanceof TextView) {
+ assertTrue(plusCount > 0);
+ assertTrue(((TextView) child).getText().toString().contains("" + plusCount));
+ hasPlusText = true;
+ } else {
+ // Nothing other than image and overflow text view should appear in this layout.
+ fail("unexpected view in layout. view = " + child);
+ }
+ }
+ assertEquals(imageCount, actualImageCount);
+ assertEquals(needPlusText, hasPlusText);
+ }
+
+ public void testBindTwoAttachments() {
+ final MultiAttachmentLayout view = getView();
+ final MessagePartData testAttachment1 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri1"), 100, 100);
+ final MessagePartData testAttachment2 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri2"), 100, 100);
+
+ view.bindAttachments(createAttachmentList(testAttachment1, testAttachment2),
+ null /* transitionRect */, 2);
+ verifyContent(view, 2, 0);
+ }
+
+ public void testBindFiveAttachments() {
+ final MultiAttachmentLayout view = getView();
+ final MessagePartData testAttachment1 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri1"), 100, 100);
+ final MessagePartData testAttachment2 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri2"), 100, 100);
+ final MessagePartData testAttachment3 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri3"), 100, 100);
+ final MessagePartData testAttachment4 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri4"), 100, 100);
+ final MessagePartData testAttachment5 = MessagePartData.createMediaMessagePart(
+ "image/jpeg", Uri.parse("content://uri5"), 100, 100);
+
+ view.bindAttachments(createAttachmentList(testAttachment1, testAttachment2, testAttachment3,
+ testAttachment4, testAttachment5), null /* transitionRect */, 5);
+ verifyContent(view, 4, 1);
+ }
+
+ public void testBindTwice() {
+ // Put the above two tests together so we can simulate binding twice.
+ testBindTwoAttachments();
+ testBindFiveAttachments();
+ }
+
+ private Iterable<MessagePartData> createAttachmentList(final MessagePartData... attachments) {
+ return Collections.unmodifiableList(Arrays.asList(attachments));
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return 0; // We construct the view with getView().
+ }
+} \ No newline at end of file
diff --git a/tests/src/com/android/messaging/ui/ViewTest.java b/tests/src/com/android/messaging/ui/ViewTest.java
new file mode 100644
index 0000000..c4e8431
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/ViewTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import android.view.View;
+
+
+/**
+ * Base class for view tests. Derived class just has to provide a layout id. Tests can then just
+ * call getView() to get a created view and test its behavior.
+ */
+public abstract class ViewTest<T extends View> extends BugleActivityUnitTestCase<TestActivity> {
+ public ViewTest() {
+ super(TestActivity.class);
+ }
+
+ protected T mView;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Create activity
+ final ActivityInstrumentationTestCaseIntent intent =
+ new ActivityInstrumentationTestCaseIntent(getInstrumentation().getTargetContext(),
+ TestActivity.class);
+ startActivity(intent, null, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected T getView() {
+ if (mView == null) {
+ // View creation deferred (typically until test time) so that factory/appcontext is
+ // ready.
+ mView = (T) getActivity().getLayoutInflater().inflate(getLayoutIdForView(), null);
+ }
+ return mView;
+ }
+
+ protected void clickButton(final View view) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.performClick();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ protected abstract int getLayoutIdForView();
+}
diff --git a/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java b/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java
new file mode 100644
index 0000000..30c711b
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragmentTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.attachmentchooser;
+
+import android.app.Fragment;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.widget.CheckBox;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.FragmentTestCase;
+import com.android.messaging.ui.TestActivity;
+import com.android.messaging.ui.TestActivity.FragmentEventListener;
+import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment;
+import com.android.messaging.ui.attachmentchooser.AttachmentGridItemView;
+import com.android.messaging.ui.attachmentchooser.AttachmentGridView;
+import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost;
+import com.android.messaging.ui.conversationlist.ConversationListFragment;
+
+import org.mockito.ArgumentMatcher;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * Unit tests for {@link ConversationListFragment}.
+ */
+@LargeTest
+public class AttachmentChooserFragmentTest extends FragmentTestCase<AttachmentChooserFragment> {
+
+ @Mock protected DataModel mockDataModel;
+ @Mock protected DraftMessageData mockDraftMessageData;
+ @Mock protected AttachmentChooserFragmentHost mockHost;
+
+ private static final String CONVERSATION_ID = "cid";
+
+ /** A custom argument matcher that checks whether the set argument passed in is a set
+ * with identical attachment data as the given set.
+ */
+ private class IsSetOfGivenAttachments extends ArgumentMatcher<Set<MessagePartData>> {
+ private final Set<MessagePartData> mGivenParts;
+ public IsSetOfGivenAttachments(final Set<MessagePartData> givenParts) {
+ mGivenParts = givenParts;
+ }
+
+ @Override
+ public boolean matches(final Object set) {
+ @SuppressWarnings("unchecked")
+ final Set<MessagePartData> actualSet = (Set<MessagePartData>) set;
+ if (actualSet.size() != mGivenParts.size()) {
+ return false;
+ }
+ return mGivenParts.containsAll(actualSet) && actualSet.containsAll(mGivenParts);
+ }
+ }
+
+ public AttachmentChooserFragmentTest() {
+ super(AttachmentChooserFragment.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(this.getInstrumentation().getTargetContext())
+ .withDataModel(mockDataModel);
+ }
+
+ private void loadWith(final List<MessagePartData> attachments) {
+ Mockito.when(mockDraftMessageData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ Mockito.doReturn(mockDraftMessageData)
+ .when(mockDataModel)
+ .createDraftMessageData(Mockito.anyString());
+ Mockito.doReturn(attachments)
+ .when(mockDraftMessageData)
+ .getReadOnlyAttachments();
+ Mockito.when(mockDataModel.createDraftMessageData(
+ Matchers.anyString()))
+ .thenReturn(mockDraftMessageData);
+
+ // Create fragment synchronously to avoid need for volatile, synchronization etc.
+ final AttachmentChooserFragment fragment = getFragment();
+ // Binding to model happens when attaching fragment to activity, so hook into test
+ // activity to do so.
+ getActivity().setFragmentEventListener(new FragmentEventListener() {
+ @Override
+ public void onAttachFragment(final Fragment attachedFragment) {
+ if (fragment == attachedFragment) {
+ fragment.setConversationId(CONVERSATION_ID);
+ }
+ }
+ });
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fragment.setHost(mockHost);
+ getActivity().setFragment(fragment);
+ Mockito.verify(mockDataModel).createDraftMessageData(
+ Mockito.matches(CONVERSATION_ID));
+ Mockito.verify(mockDraftMessageData).loadFromStorage(
+ Matchers.eq(fragment.mBinding), Matchers.eq((MessageData) null),
+ Matchers.eq(false));
+ }
+ });
+ // Now load the cursor
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fragment.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ public void testUnselect() {
+ final List<MessagePartData> attachments = TestDataFactory.getTestDraftAttachments();
+ loadWith(attachments);
+ final AttachmentGridView attachmentGridView = (AttachmentGridView)
+ getFragment().getView().findViewById(R.id.grid);
+ assertEquals("bad view count", attachments.size(),
+ attachmentGridView.getAdapter().getCount());
+
+ final AttachmentGridItemView itemView = (AttachmentGridItemView)
+ attachmentGridView.getChildAt(0);
+ assertEquals(attachmentGridView, itemView.testGetHostInterface());
+ final CheckBox checkBox = (CheckBox) itemView.findViewById(R.id.checkbox);
+ assertEquals(true, checkBox.isChecked());
+ assertEquals(true, attachmentGridView.isItemSelected(itemView.mAttachmentData));
+ clickButton(checkBox);
+ assertEquals(false, checkBox.isChecked());
+ assertEquals(false, attachmentGridView.isItemSelected(itemView.mAttachmentData));
+
+ final AttachmentGridItemView itemView2 = (AttachmentGridItemView)
+ attachmentGridView.getChildAt(1);
+ final CheckBox checkBox2 = (CheckBox) itemView2.findViewById(R.id.checkbox);
+ clickButton(checkBox2);
+
+ getFragment().confirmSelection();
+ final MessagePartData[] attachmentsToRemove = new MessagePartData[] {
+ itemView.mAttachmentData, itemView2.mAttachmentData };
+ Mockito.verify(mockDraftMessageData).removeExistingAttachments(Matchers.argThat(
+ new IsSetOfGivenAttachments(new HashSet<>(Arrays.asList(attachmentsToRemove)))));
+ Mockito.verify(mockDraftMessageData).saveToStorage(Matchers.eq(getFragment().mBinding));
+ Mockito.verify(mockHost).onConfirmSelection();
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java b/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java
new file mode 100644
index 0000000..de4c583
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/contact/ContactListItemViewTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.data.ContactListItemData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.ContactIconView;
+import com.android.messaging.ui.ViewTest;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+public class ContactListItemViewTest extends ViewTest<ContactListItemView> {
+
+ @Mock ContactListItemView.HostInterface mockHost;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getTargetContext();
+ FakeFactory.register(context)
+ .withDataModel(new FakeDataModel(context));
+ }
+
+ protected void verifyAddedContactForData(final ContactListItemData data,
+ final ContactListItemView view) {
+ Mockito.verify(mockHost).onContactListItemClicked(data, view);
+ }
+
+ protected void verifyContent(
+ final ContactListItemView view,
+ final String contactName,
+ final String contactDetail,
+ final String avatarUrl,
+ final boolean showAvatar) {
+ final TextView contactNameView = (TextView) view.findViewById(R.id.contact_name);
+ final TextView contactDetailView = (TextView) view.findViewById(R.id.contact_details);
+ final ContactIconView avatarView = (ContactIconView) view.findViewById(R.id.contact_icon);
+
+ assertNotNull(contactNameView);
+ assertEquals(contactName, contactNameView.getText());
+ assertNotNull(contactDetail);
+ assertEquals(contactDetail, contactDetailView.getText());
+ assertNotNull(avatarView);
+ if (showAvatar) {
+ assertTrue(avatarView.mImageRequestBinding.isBound());
+ assertEquals(View.VISIBLE, avatarView.getVisibility());
+ } else {
+ assertFalse(avatarView.mImageRequestBinding.isBound());
+ assertEquals(View.INVISIBLE, avatarView.getVisibility());
+ }
+ }
+
+ public void testBindFirstLevel() {
+ final ContactListItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getAllContactListCursor();
+ final int row = TestDataFactory.CONTACT_LIST_CURSOR_FIRST_LEVEL_CONTACT_INDEX;
+ cursor.moveToPosition(row);
+ view.bind(cursor, mockHost, false, null);
+ verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, row),
+ (String) cursor.getAt(Phone.NUMBER, row),
+ (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, row), true);
+ }
+
+ public void testBindSecondLevel() {
+ final ContactListItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getAllContactListCursor();
+ final int row = TestDataFactory.CONTACT_LIST_CURSOR_SECOND_LEVEL_CONTACT_INDEX;
+ cursor.moveToPosition(row);
+ view.bind(cursor, mockHost, false, null);
+ verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, row),
+ (String) cursor.getAt(Phone.NUMBER, row),
+ (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, row), false);
+ }
+
+ public void testClickAddedContact() {
+ final ContactListItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getAllContactListCursor();
+ cursor.moveToFirst();
+
+ view.bind(cursor, mockHost, false, null);
+ view.performClick();
+ verifyAddedContactForData(view.mData, view);
+ }
+
+ public void testBindTwice() {
+ final ContactListItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getAllContactListCursor();
+
+ cursor.moveToFirst();
+ view.bind(cursor, mockHost, false, null);
+
+ cursor.moveToNext();
+ view.bind(cursor, mockHost, false, null);
+ verifyContent(view, (String) cursor.getAt(Contacts.DISPLAY_NAME, 1),
+ (String) cursor.getAt(Phone.NUMBER, 1),
+ (String) cursor.getAt(Contacts.PHOTO_THUMBNAIL_URI, 1), true);
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return R.layout.contact_list_item_view;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java b/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java
new file mode 100644
index 0000000..5b1503b
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/contact/ContactPickerFragmentTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.contact;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v4.view.ViewPager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+import android.widget.ListView;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.action.ActionTestHelpers;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService;
+import com.android.messaging.datamodel.action.ActionTestHelpers.StubActionService.StubActionServiceCallLog;
+import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
+import com.android.messaging.datamodel.data.ContactPickerData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.CustomHeaderViewPagerAdapter;
+import com.android.messaging.ui.FragmentTestCase;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.contact.ContactPickerFragment.ContactPickerFragmentHost;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Unit tests for {@link ContactPickerFragment}.
+ */
+@LargeTest
+public class ContactPickerFragmentTest
+ extends FragmentTestCase<ContactPickerFragment> {
+
+ @Mock protected ContactPickerData mMockContactPickerData;
+ @Mock protected UIIntents mMockUIIntents;
+ @Mock protected ContactPickerFragmentHost mockHost;
+ protected FakeDataModel mFakeDataModel;
+ private ActionTestHelpers.StubActionService mService;
+
+ public ContactPickerFragmentTest() {
+ super(ContactPickerFragment.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Context context = getInstrumentation().getTargetContext();
+ mService = new StubActionService();
+ mFakeDataModel = new FakeDataModel(context)
+ .withContactPickerData(mMockContactPickerData)
+ .withActionService(mService);
+ FakeFactory.register(context)
+ .withDataModel(mFakeDataModel)
+ .withUIIntents(mMockUIIntents);
+ }
+
+ /**
+ * Helper method to initialize the ContactPickerFragment and its data.
+ */
+ private ContactPickerFragmentTest initFragment(final int initialMode) {
+ Mockito.when(mMockContactPickerData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final ContactPickerFragment fragment = getFragment();
+ fragment.setHost(mockHost);
+ fragment.setContactPickingMode(initialMode, false);
+
+ getActivity().setFragment(fragment);
+ Mockito.verify(mMockContactPickerData).init(fragment.getLoaderManager(),
+ fragment.mBinding);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ return this;
+ }
+
+ /**
+ * Bind the datamodel with all contacts cursor to populate the all contacts list in the
+ * fragment.
+ */
+ private ContactPickerFragmentTest loadWithAllContactsCursor(final Cursor cursor) {
+ Mockito.when(mMockContactPickerData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getFragment().onAllContactsCursorUpdated(cursor);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ return this;
+ }
+
+ /**
+ * Bind the datamodel with frequent contacts cursor to populate the contacts list in the
+ * fragment.
+ */
+ private ContactPickerFragmentTest loadWithFrequentContactsCursor(final Cursor cursor) {
+ Mockito.when(mMockContactPickerData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getFragment().onFrequentContactsCursorUpdated(cursor);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ return this;
+ }
+
+ /**
+ * Test the initial state of the fragment before loading data.
+ */
+ public void testInitialState() {
+ initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT);
+
+ // Make sure that the frequent contacts view is shown by default.
+ final ViewPager pager = (ViewPager) getFragment().getView().findViewById(R.id.pager);
+ final View currentPagedView = pager.getChildAt(pager.getCurrentItem());
+ final View frequentContactsView = ((CustomHeaderViewPagerAdapter) pager.getAdapter())
+ .getViewHolder(0).getView(null);
+ assertEquals(frequentContactsView, currentPagedView);
+ }
+
+ /**
+ * Verifies that list view gets correctly populated given a cursor.
+ */
+ public void testLoadAllContactsList() {
+ final Cursor cursor = TestDataFactory.getAllContactListCursor();
+ initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT)
+ .loadWithAllContactsCursor(cursor);
+ final ListView listView = (ListView) getFragment().getView()
+ .findViewById(R.id.all_contacts_list);
+ assertEquals(cursor.getCount(), listView.getCount());
+ }
+
+ /**
+ * Verifies that list view gets correctly populated given a cursor.
+ */
+ public void testLoadFrequentContactsList() {
+ final Cursor cursor = TestDataFactory.getFrequentContactListCursor();
+ initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT)
+ .loadWithFrequentContactsCursor(cursor);
+ final ListView listView = (ListView) getFragment().getView()
+ .findViewById(R.id.frequent_contacts_list);
+ assertEquals(cursor.getCount(), listView.getCount());
+ }
+
+ public void testPickInitialContact() {
+ final Cursor cursor = TestDataFactory.getFrequentContactListCursor();
+ initFragment(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT)
+ .loadWithFrequentContactsCursor(cursor);
+ final ListView listView = (ListView) getFragment().getView()
+ .findViewById(R.id.frequent_contacts_list);
+ // Click on the first contact to add it.
+ final ContactListItemView cliv = (ContactListItemView) listView.getChildAt(0);
+ clickButton(cliv);
+ final ContactRecipientAutoCompleteView chipsView = (ContactRecipientAutoCompleteView)
+ getFragment().getView()
+ .findViewById(R.id.recipient_text_view);
+ // Verify the contact is added to the chips view.
+ final List<ParticipantData> participants =
+ chipsView.getRecipientParticipantDataForConversationCreation();
+ assertEquals(1, participants.size());
+ assertEquals(cliv.mData.getDestination(), participants.get(0).getSendDestination());
+ assertTrue(mService.getCalls().get(0).action instanceof GetOrCreateConversationAction);
+ }
+
+ public void testLeaveChipsMode() {
+ final Cursor cursor = TestDataFactory.getFrequentContactListCursor();
+ initFragment(ContactPickerFragment.MODE_CHIPS_ONLY)
+ .loadWithFrequentContactsCursor(cursor);
+ // Click on the add more participants button
+ // TODO: Figure out a way to click on the add more participants button now that
+ // it's part of the menu.
+ // final ImageButton AddMoreParticipantsButton = (ImageButton) getFragment().getView()
+ // .findViewById(R.id.add_more_participants_button);
+ // clickButton(AddMoreParticipantsButton);
+ // Mockito.verify(mockHost).onInitiateAddMoreParticipants();
+ }
+
+ public void testPickMoreContacts() {
+ final Cursor cursor = TestDataFactory.getFrequentContactListCursor();
+ initFragment(ContactPickerFragment.MODE_PICK_MORE_CONTACTS)
+ .loadWithFrequentContactsCursor(cursor);
+ final ListView listView = (ListView) getFragment().getView()
+ .findViewById(R.id.frequent_contacts_list);
+ // Click on the first contact to add it.
+ final ContactListItemView cliv = (ContactListItemView) listView.getChildAt(0);
+ clickButton(cliv);
+ // Verify that we don't attempt to create a conversation right away.
+ assertEquals(0, mService.getCalls().size());
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java
new file mode 100644
index 0000000..2dd2a89
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.conversation;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.widget.EditText;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask;
+import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.ui.ViewTest;
+import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.FakeMediaUtil;
+import com.android.messaging.util.ImeUtil;
+
+import org.mockito.ArgumentMatcher;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+@MediumTest
+public class ComposeMessageViewTest extends ViewTest<ComposeMessageView> {
+ private Context mContext;
+
+ @Mock protected DataModel mockDataModel;
+ @Mock protected DraftMessageData mockDraftMessageData;
+ @Mock protected BugleGservices mockBugleGservices;
+ @Mock protected ImeUtil mockImeUtil;
+ @Mock protected IComposeMessageViewHost mockIComposeMessageViewHost;
+ @Mock protected MediaPlayer mockMediaPlayer;
+ @Mock protected ConversationInputManager mockInputManager;
+ @Mock protected ConversationData mockConversationData;
+
+ Binding<ConversationData> mConversationBinding;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ FakeFactory.register(mContext)
+ .withDataModel(mockDataModel)
+ .withBugleGservices(mockBugleGservices)
+ .withMediaUtil(new FakeMediaUtil(mockMediaPlayer));
+
+ Mockito.doReturn(true).when(mockConversationData).isBound(Mockito.anyString());
+ mConversationBinding = BindingBase.createBinding(this);
+ mConversationBinding.bind(mockConversationData);
+ }
+
+ @Override
+ protected ComposeMessageView getView() {
+ final ComposeMessageView view = super.getView();
+ view.setInputManager(mockInputManager);
+ view.setConversationDataModel(BindingBase.createBindingReference(mConversationBinding));
+ return view;
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return R.layout.compose_message_view;
+ }
+
+ public void testSend() {
+ Mockito.when(mockDraftMessageData.getReadOnlyAttachments())
+ .thenReturn(Collections.unmodifiableList(new ArrayList<MessagePartData>()));
+ Mockito.when(mockDraftMessageData.getIsDefaultSmsApp()).thenReturn(true);
+ Mockito.when(mockIComposeMessageViewHost.isReadyForAction()).thenReturn(true);
+ final ComposeMessageView view = getView();
+
+ final MessageData message = MessageData.createDraftSmsMessage("fake_id", "just_a_self_id",
+ "Sample Message");
+
+ Mockito.when(mockDraftMessageData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ Mockito.when(mockDraftMessageData.getMessageText()).thenReturn(message.getMessageText());
+ Mockito.when(mockDraftMessageData.prepareMessageForSending(
+ Matchers.<BindingBase<DraftMessageData>>any()))
+ .thenReturn(message);
+ Mockito.when(mockDraftMessageData.hasPendingAttachments()).thenReturn(false);
+ Mockito.doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ // Synchronously pass the draft check and callback.
+ ((CheckDraftTaskCallback)invocation.getArguments()[2]).onDraftChecked(
+ mockDraftMessageData, CheckDraftForSendTask.RESULT_PASSED);
+ return null;
+ }
+ }).when(mockDraftMessageData).checkDraftForAction(Mockito.anyBoolean(), Mockito.anyInt(),
+ Mockito.<CheckDraftTaskCallback>any(),
+ Mockito.<Binding<DraftMessageData>>any());
+
+ view.bind(mockDraftMessageData, mockIComposeMessageViewHost);
+
+ final EditText composeEditText = (EditText) view.findViewById(R.id.compose_message_text);
+ final View sendButton = view.findViewById(R.id.send_message_button);
+
+ view.requestDraftMessage(false);
+
+ Mockito.verify(mockDraftMessageData).loadFromStorage(Matchers.any(BindingBase.class),
+ Matchers.any(MessageData.class), Mockito.eq(false));
+
+ view.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED);
+
+ assertEquals(message.getMessageText(), composeEditText.getText().toString());
+
+ sendButton.performClick();
+ Mockito.verify(mockIComposeMessageViewHost).sendMessage(
+ Mockito.argThat(new ArgumentMatcher<MessageData>() {
+ @Override
+ public boolean matches(final Object o) {
+ assertEquals(message.getMessageText(), ((MessageData) o).getMessageText());
+ return true;
+ }
+ }));
+ }
+
+ public void testNotDefaultSms() {
+ Mockito.when(mockDraftMessageData.getReadOnlyAttachments())
+ .thenReturn(Collections.unmodifiableList(new ArrayList<MessagePartData>()));
+ Mockito.when(mockDraftMessageData.getIsDefaultSmsApp()).thenReturn(false);
+ Mockito.when(mockIComposeMessageViewHost.isReadyForAction()).thenReturn(false);
+ final ComposeMessageView view = getView();
+
+ final MessageData message = MessageData.createDraftSmsMessage("fake_id", "just_a_self_id",
+ "Sample Message");
+
+ Mockito.when(mockDraftMessageData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ Mockito.when(mockDraftMessageData.getMessageText()).thenReturn(message.getMessageText());
+ Mockito.when(mockDraftMessageData.prepareMessageForSending(
+ Matchers.<BindingBase<DraftMessageData>>any()))
+ .thenReturn(message);
+ Mockito.when(mockDraftMessageData.hasPendingAttachments()).thenReturn(false);
+
+ view.bind(mockDraftMessageData, mockIComposeMessageViewHost);
+
+ final EditText composeEditText = (EditText) view.findViewById(R.id.compose_message_text);
+ final View sendButton = view.findViewById(R.id.send_message_button);
+
+ view.requestDraftMessage(false);
+
+ Mockito.verify(mockDraftMessageData).loadFromStorage(Matchers.any(BindingBase.class),
+ Matchers.any(MessageData.class), Mockito.eq(false));
+
+ view.onDraftChanged(mockDraftMessageData, DraftMessageData.ALL_CHANGED);
+
+ assertEquals(message.getMessageText(), composeEditText.getText().toString());
+
+ sendButton.performClick();
+ Mockito.verify(mockIComposeMessageViewHost).warnOfMissingActionConditions(
+ Matchers.any(Boolean.class), Matchers.any(Runnable.class));
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java
new file mode 100644
index 0000000..7c6903d
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversation/ConversationActivityUiStateTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.conversation;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.ui.contact.ContactPickerFragment;
+import com.android.messaging.ui.conversation.ConversationActivityUiState;
+import com.android.messaging.ui.conversation.ConversationActivityUiState.ConversationActivityUiStateHost;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+@SmallTest
+public class ConversationActivityUiStateTest extends BugleTestCase {
+ @Mock protected ConversationActivityUiStateHost mockListener;
+
+ /**
+ * Test the Ui state where we start off with the contact picker to pick the first contact.
+ */
+ public void testPickInitialContact() {
+ final ConversationActivityUiState uiState = new ConversationActivityUiState(null);
+ uiState.setHost(mockListener);
+ assertTrue(uiState.shouldShowContactPickerFragment());
+ assertFalse(uiState.shouldShowConversationFragment());
+ assertEquals(ContactPickerFragment.MODE_PICK_INITIAL_CONTACT,
+ uiState.getDesiredContactPickingMode());
+ uiState.onGetOrCreateConversation("conversation1");
+ Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged(
+ Mockito.eq(ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT),
+ Mockito.eq(
+ ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW),
+ Mockito.anyBoolean());
+ assertTrue(uiState.shouldShowContactPickerFragment());
+ assertTrue(uiState.shouldShowConversationFragment());
+ assertTrue(TextUtils.equals("conversation1", uiState.getConversationId()));
+ assertEquals(ContactPickerFragment.MODE_CHIPS_ONLY,
+ uiState.getDesiredContactPickingMode());
+ }
+
+ /**
+ * Test the Ui state where we have both the chips view and the conversation view and we
+ * start message compose.
+ */
+ public void testHybridUiStateStartCompose() {
+ final ConversationActivityUiState uiState = new ConversationActivityUiState("conv1");
+ uiState.testSetUiState(
+ ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW);
+ uiState.setHost(mockListener);
+
+ // Start message compose.
+ uiState.onStartMessageCompose();
+ Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged(
+ Mockito.eq(
+ ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW),
+ Mockito.eq(ConversationActivityUiState.STATE_CONVERSATION_ONLY),
+ Mockito.anyBoolean());
+ assertFalse(uiState.shouldShowContactPickerFragment());
+ assertTrue(uiState.shouldShowConversationFragment());
+ }
+
+ /**
+ * Test the Ui state where we have both the chips view and the conversation view and we
+ * try to add a participant.
+ */
+ public void testHybridUiStateAddParticipant() {
+ final ConversationActivityUiState uiState = new ConversationActivityUiState("conv1");
+ uiState.testSetUiState(
+ ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW);
+ uiState.setHost(mockListener);
+
+ uiState.onAddMoreParticipants();
+ Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged(
+ Mockito.eq(
+ ConversationActivityUiState.STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW),
+ Mockito.eq(
+ ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS),
+ Mockito.anyBoolean());
+ assertTrue(uiState.shouldShowContactPickerFragment());
+ assertFalse(uiState.shouldShowConversationFragment());
+ assertEquals(ContactPickerFragment.MODE_PICK_MORE_CONTACTS,
+ uiState.getDesiredContactPickingMode());
+ }
+
+ /**
+ * Test the Ui state where we are trying to add more participants and commit.
+ */
+ public void testCommitAddParticipant() {
+ final ConversationActivityUiState uiState = new ConversationActivityUiState(null);
+ uiState.testSetUiState(
+ ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS);
+ uiState.setHost(mockListener);
+
+ uiState.onGetOrCreateConversation("conversation1");
+
+ // After adding more contacts, the terminal state is always conversation only (i.e. we
+ // don't go back to hybrid mode).
+ Mockito.verify(mockListener, Mockito.times(1)).onConversationContactPickerUiStateChanged(
+ Mockito.eq(ConversationActivityUiState.STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS),
+ Mockito.eq(ConversationActivityUiState.STATE_CONVERSATION_ONLY),
+ Mockito.anyBoolean());
+ assertFalse(uiState.shouldShowContactPickerFragment());
+ assertTrue(uiState.shouldShowConversationFragment());
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java
new file mode 100644
index 0000000..c92fbf6
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversation/ConversationFragmentTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.conversation;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.MemoryCacheManager;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.datamodel.media.MediaResourceManager;
+import com.android.messaging.ui.FragmentTestCase;
+import com.android.messaging.ui.PlainTextEditText;
+import com.android.messaging.ui.TestActivity.FragmentEventListener;
+import com.android.messaging.ui.conversation.ConversationFragment.ConversationFragmentHost;
+import com.android.messaging.ui.conversationlist.ConversationListFragment;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.ImeUtil;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+
+/**
+ * Unit tests for {@link ConversationListFragment}.
+ */
+@LargeTest
+public class ConversationFragmentTest extends FragmentTestCase<ConversationFragment> {
+
+ @Mock protected DataModel mockDataModel;
+ @Mock protected ConversationData mockConversationData;
+ @Mock protected DraftMessageData mockDraftMessageData;
+ @Mock protected MediaResourceManager mockMediaResourceManager;
+ @Mock protected BugleGservices mockBugleGservices;
+ @Mock protected ConversationFragmentHost mockHost;
+ @Mock protected MemoryCacheManager mockMemoryCacheManager;
+
+ private ImeUtil mSpiedImeUtil;
+
+ private static final String CONVERSATION_ID = "cid";
+
+
+ public ConversationFragmentTest() {
+ super(ConversationFragment.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ImeUtil.clearInstance();
+ mSpiedImeUtil = Mockito.spy(new ImeUtil());
+ FakeFactory.register(this.getInstrumentation().getTargetContext())
+ .withDataModel(mockDataModel)
+ .withBugleGservices(mockBugleGservices)
+ .withMemoryCacheManager(mockMemoryCacheManager);
+ }
+
+ /**
+ * Helper that will do the 'binding' of ConversationFragmentTest with ConversationData and
+ * leave fragment in 'ready' state.
+ * @param cursor
+ */
+ private void loadWith(final Cursor cursor) {
+ Mockito.when(mockDraftMessageData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ Mockito.when(mockConversationData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ Mockito.doReturn(mockDraftMessageData)
+ .when(mockDataModel)
+ .createDraftMessageData(Mockito.anyString());
+ Mockito.when(mockDataModel.createConversationData(
+ Matchers.any(Activity.class),
+ Matchers.any(ConversationDataListener.class),
+ Matchers.anyString()))
+ .thenReturn(mockConversationData);
+
+ // Create fragment synchronously to avoid need for volatile, synchronization etc.
+ final ConversationFragment fragment = getFragment();
+ // Binding to model happens when attaching fragment to activity, so hook into test
+ // activity to do so.
+ getActivity().setFragmentEventListener(new FragmentEventListener() {
+ @Override
+ public void onAttachFragment(final Fragment attachedFragment) {
+ if (fragment == attachedFragment) {
+ fragment.setConversationInfo(getActivity(), CONVERSATION_ID, null);
+ }
+ }
+ });
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fragment.setHost(mockHost);
+ getActivity().setFragment(fragment);
+ Mockito.verify(mockDataModel).createConversationData(
+ getActivity(), fragment, CONVERSATION_ID);
+ Mockito.verify(mockConversationData).init(fragment.getLoaderManager(),
+ fragment.mBinding);
+ }
+ });
+ // Wait for initial layout pass to work around crash in recycler view
+ getInstrumentation().waitForIdleSync();
+ // Now load the cursor
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fragment.onConversationMessagesCursorUpdated(mockConversationData, cursor, null,
+ false);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ /**
+ * Verifies that list view gets correctly populated given a cursor.
+ */
+ public void testLoadListView() {
+ final Cursor cursor = TestDataFactory.getConversationMessageCursor();
+ loadWith(cursor);
+ final RecyclerView listView =
+ (RecyclerView) getFragment().getView().findViewById(android.R.id.list);
+ assertEquals("bad cursor", cursor.getCount(), listView.getAdapter().getItemCount());
+ assertEquals("bad cursor count", cursor.getCount(), listView.getChildCount());
+ }
+
+ public void testClickComposeMessageView() {
+ final Cursor cursor = TestDataFactory.getConversationMessageCursor();
+ loadWith(cursor);
+
+ final PlainTextEditText composeEditText = (PlainTextEditText) getFragment().getView()
+ .findViewById(R.id.compose_message_text);
+ setFocus(composeEditText, false);
+ Mockito.verify(mockHost, Mockito.never()).onStartComposeMessage();
+ setFocus(composeEditText, true);
+ Mockito.verify(mockHost).onStartComposeMessage();
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java
new file mode 100644
index 0000000..f335785
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversation/ConversationInputManagerTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.conversation;
+
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.EditText;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.ConversationData;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.SubscriptionListData;
+import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost;
+import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
+import com.android.messaging.ui.mediapicker.MediaPicker;
+import com.android.messaging.util.BugleGservices;
+import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.ImeUtil.ImeStateHost;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+@SmallTest
+public class ConversationInputManagerTest extends BugleTestCase {
+ @Spy protected ImeUtil spyImeUtil;
+ @Mock protected BugleGservices mockBugleGservices;
+ @Mock protected FragmentManager mockFragmentManager;
+ @Mock protected ConversationInputHost mockConversationInputHost;
+ @Mock protected ConversationInputSink mockConversationInputSink;
+ @Mock protected ImeStateHost mockImeStateHost;
+ @Mock protected ConversationData mockConversationData;
+ @Mock protected DraftMessageData mockDraftMessageData;
+ @Mock protected MediaPicker mockMediaPicker;
+ @Mock protected SubscriptionListData mockSubscriptionListData;
+ @Mock protected FragmentTransaction mockFragmentTransaction;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getTestContext())
+ .withBugleGservices(mockBugleGservices);
+ spyImeUtil = Mockito.spy(new ImeUtil());
+ ImeUtil.set(spyImeUtil);
+ }
+
+ private ConversationInputManager initNewInputManager(final Bundle savedState) {
+ // Set up the mocks.
+ Mockito.when(mockConversationInputHost.getSimSelectorView())
+ .thenReturn(new SimSelectorView(getTestContext(), null));
+ Mockito.when(mockConversationInputHost.createMediaPicker()).thenReturn(mockMediaPicker);
+ Mockito.when(mockConversationInputSink.getComposeEditText())
+ .thenReturn(new EditText(getTestContext()));
+ Mockito.doReturn(mockFragmentTransaction).when(mockFragmentTransaction).replace(
+ Mockito.eq(R.id.mediapicker_container), Mockito.any(MediaPicker.class),
+ Mockito.anyString());
+ Mockito.when(mockFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG))
+ .thenReturn(null);
+ Mockito.when(mockFragmentManager.beginTransaction()).thenReturn(mockFragmentTransaction);
+ Mockito.when(mockSubscriptionListData.hasData()).thenReturn(true);
+ Mockito.when(mockConversationData.getSubscriptionListData())
+ .thenReturn(mockSubscriptionListData);
+ Mockito.doReturn(true).when(mockConversationData).isBound(Mockito.anyString());
+ Mockito.doReturn(true).when(mockDraftMessageData).isBound(Mockito.anyString());
+
+ final Binding<ConversationData> dataBinding = BindingBase.createBinding(this);
+ dataBinding.bind(mockConversationData);
+ final Binding<DraftMessageData> draftBinding = BindingBase.createBinding(this);
+ draftBinding.bind(mockDraftMessageData);
+ final ConversationInputManager inputManager = new ConversationInputManager(getTestContext(),
+ mockConversationInputHost, mockConversationInputSink, mockImeStateHost,
+ mockFragmentManager, dataBinding, draftBinding, savedState);
+ return inputManager;
+ }
+
+ public void testShowHideInputs() {
+ final ConversationInputManager inputManager = initNewInputManager(new Bundle());
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(true);
+ inputManager.showHideMediaPicker(true /* show */, true /* animate */);
+ Mockito.verify(mockFragmentTransaction).replace(
+ Mockito.eq(R.id.mediapicker_container), Mockito.any(MediaPicker.class),
+ Mockito.anyString());
+ Mockito.verify(mockMediaPicker).open(Mockito.anyInt(), Mockito.eq(true /* animate */));
+
+ assertEquals(true, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(false);
+ inputManager.showHideMediaPicker(false /* show */, true /* animate */);
+ Mockito.verify(mockMediaPicker).dismiss(Mockito.eq(true /* animate */));
+
+ assertEquals(false, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+ }
+
+ public void testShowTwoInputsSequentially() {
+ // First show the media picker, then show the IME keyboard.
+ final ConversationInputManager inputManager = initNewInputManager(new Bundle());
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(true);
+ inputManager.showHideMediaPicker(true /* show */, true /* animate */);
+
+ assertEquals(true, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(false);
+ inputManager.showHideImeKeyboard(true /* show */, true /* animate */);
+
+ assertEquals(false, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(true, inputManager.isImeKeyboardVisible());
+ }
+
+ public void testOnKeyboardShow() {
+ final ConversationInputManager inputManager = initNewInputManager(new Bundle());
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(true);
+ inputManager.showHideMediaPicker(true /* show */, true /* animate */);
+
+ assertEquals(true, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(false);
+ inputManager.testNotifyImeStateChanged(true /* imeOpen */);
+
+ assertEquals(false, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(true, inputManager.isImeKeyboardVisible());
+ }
+
+ public void testRestoreState() {
+ final ConversationInputManager inputManager = initNewInputManager(new Bundle());
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(true);
+ inputManager.showHideMediaPicker(true /* show */, true /* animate */);
+
+ assertEquals(true, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+
+ Bundle savedInstanceState = new Bundle();
+ inputManager.onSaveInputState(savedInstanceState);
+
+ // Now try to restore the state
+ final ConversationInputManager restoredInputManager =
+ initNewInputManager(savedInstanceState);
+
+ // Make sure the state is preserved.
+ assertEquals(true, restoredInputManager.isMediaPickerVisible());
+ assertEquals(false, restoredInputManager.isSimSelectorVisible());
+ assertEquals(false, restoredInputManager.isImeKeyboardVisible());
+ }
+
+ public void testBackPress() {
+ final ConversationInputManager inputManager = initNewInputManager(new Bundle());
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(true);
+ inputManager.showHideMediaPicker(true /* show */, true /* animate */);
+
+ assertEquals(true, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+
+ Mockito.when(mockMediaPicker.isOpen()).thenReturn(false);
+ assertEquals(true, inputManager.onBackPressed());
+
+ assertEquals(false, inputManager.isMediaPickerVisible());
+ assertEquals(false, inputManager.isSimSelectorVisible());
+ assertEquals(false, inputManager.isImeKeyboardVisible());
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java
new file mode 100644
index 0000000..6b8b0c0
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversation/ConversationMessageViewTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.conversation;
+
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.ViewTest;
+import com.android.messaging.ui.conversation.ConversationMessageView;
+import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
+import com.android.messaging.util.Dates;
+
+import org.mockito.Mock;
+
+@MediumTest
+public class ConversationMessageViewTest extends ViewTest<ConversationMessageView> {
+ @Mock ConversationMessageViewHost mockHost;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getInstrumentation().getTargetContext());
+ }
+
+ @Override
+ protected ConversationMessageView getView() {
+ final ConversationMessageView view = super.getView();
+ view.setHost(mockHost);
+ return view;
+ }
+
+ protected void verifyContent(final ConversationMessageView view, final String messageText,
+ final boolean showTimestamp, final String timestampText) {
+
+ final TextView messageTextView = (TextView) view.findViewById(R.id.message_text);
+ final TextView statusTextView = (TextView) view.findViewById(R.id.message_status);
+
+ assertNotNull(messageTextView);
+ assertEquals(messageText, messageTextView.getText());
+
+ if (showTimestamp) {
+ assertEquals(View.VISIBLE, statusTextView.getVisibility());
+ assertEquals(timestampText, statusTextView.getText());
+ } else {
+ assertEquals(View.GONE, statusTextView.getVisibility());
+ }
+ }
+
+ public void testBind() {
+ final ConversationMessageView view = getView();
+
+ final FakeCursor cursor = TestDataFactory.getConversationMessageCursor();
+ cursor.moveToFirst();
+
+ view.bind(cursor);
+ verifyContent(view, TestDataFactory.getMessageText(cursor, 0), true, Dates
+ .getMessageTimeString((Long) cursor.getAt("received_timestamp", 0)).toString());
+ }
+
+ public void testBindTwice() {
+ final ConversationMessageView view = getView();
+
+ final FakeCursor cursor = TestDataFactory.getConversationMessageCursor();
+ cursor.moveToFirst();
+ view.bind(cursor);
+
+ cursor.moveToNext();
+ view.bind(cursor);
+ verifyContent(view, TestDataFactory.getMessageText(cursor, 1), true, Dates
+ .getMessageTimeString((Long) cursor.getAt("received_timestamp", 1)).toString());
+ }
+
+ public void testBindLast() {
+ final ConversationMessageView view = getView();
+
+ final FakeCursor cursor = TestDataFactory.getConversationMessageCursor();
+ final int lastPos = cursor.getCount() - 1;
+ cursor.moveToPosition(lastPos);
+
+ view.bind(cursor);
+ verifyContent(view, TestDataFactory.getMessageText(cursor, lastPos), true, Dates
+ .getMessageTimeString((Long) cursor.getAt("received_timestamp", lastPos))
+ .toString());
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return R.layout.conversation_message_view;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java b/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java
new file mode 100644
index 0000000..f9cc9e1
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversationlist/ConversationListFragmentTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.conversationlist;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.data.ConversationListData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.FragmentTestCase;
+import com.android.messaging.ui.UIIntents;
+import com.android.messaging.ui.conversationlist.ConversationListFragment;
+import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+
+/**
+ * Unit tests for {@link ConversationListFragment}.
+ */
+@LargeTest
+public class ConversationListFragmentTest
+ extends FragmentTestCase<ConversationListFragment> {
+
+ @Mock protected ConversationListData mMockConversationListData;
+ @Mock protected ConversationListFragmentHost mMockConversationHostListHost;
+ @Mock protected UIIntents mMockUIIntents;
+ protected FakeDataModel mFakeDataModel;
+
+ public ConversationListFragmentTest() {
+ super(ConversationListFragment.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Context context = getInstrumentation().getTargetContext();
+ mFakeDataModel = new FakeDataModel(context)
+ .withConversationListData(mMockConversationListData);
+ FakeFactory.register(context)
+ .withDataModel(mFakeDataModel)
+ .withUIIntents(mMockUIIntents);
+ }
+
+ /**
+ * Helper that will do the 'binding' of ConversationListFragmentTest with ConversationListData
+ * and leave fragment in 'ready' state.
+ * @param cursor
+ */
+ private void loadWith(final Cursor cursor) {
+ Mockito.when(mMockConversationListData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+
+ final ConversationListFragment fragment = getFragment();
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fragment.setHost(mMockConversationHostListHost);
+ getActivity().setFragment(fragment);
+ Mockito.verify(mMockConversationListData).init(fragment.getLoaderManager(),
+ fragment.mListBinding);
+ fragment.onConversationListCursorUpdated(mMockConversationListData, cursor);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ /**
+ * Verifies that list view gets correctly populated given a cursor.
+ */
+ public void testLoadListView() {
+ final Cursor cursor = TestDataFactory.getConversationListCursor();
+ loadWith(cursor);
+ final RecyclerView listView =
+ (RecyclerView) getFragment().getView().findViewById(android.R.id.list);
+ //assertEquals(cursor.getCount(), listView.getCount());
+ assertEquals(cursor.getCount(), listView.getChildCount());
+ }
+
+ /**
+ * Verifies that 'empty list' promo is rendered with an empty cursor.
+ */
+ public void testEmptyView() {
+ loadWith(TestDataFactory.getEmptyConversationListCursor());
+ final RecyclerView listView =
+ (RecyclerView) getFragment().getView().findViewById(android.R.id.list);
+ final View emptyMessageView =
+ getFragment().getView().findViewById(R.id.no_conversations_view);
+ assertEquals(View.VISIBLE, emptyMessageView.getVisibility());
+ assertEquals(0, listView.getChildCount());
+ }
+
+ /**
+ * Verifies that the button to start a new conversation works.
+ */
+ public void testStartNewConversation() {
+ final Cursor cursor = TestDataFactory.getConversationListCursor();
+ loadWith(cursor);
+ final ImageView startNewConversationButton = (ImageView)
+ getFragment().getView().findViewById(R.id.start_new_conversation_button);
+
+ clickButton(startNewConversationButton);
+ Mockito.verify(mMockConversationHostListHost).onCreateConversationClick();
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java b/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java
new file mode 100644
index 0000000..be054a8
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/conversationlist/ConversationListItemViewTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.conversationlist;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.messaging.Factory;
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.data.ConversationListItemData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.AsyncImageView;
+import com.android.messaging.ui.UIIntentsImpl;
+import com.android.messaging.ui.ViewTest;
+import com.android.messaging.ui.conversationlist.ConversationListItemView;
+import com.android.messaging.util.Dates;
+
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+@MediumTest
+public class ConversationListItemViewTest extends ViewTest<ConversationListItemView> {
+
+ @Mock private ConversationListItemView.HostInterface mockHost;
+ private FakeCursor mCursor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getTargetContext();
+ FakeFactory.register(context)
+ .withDataModel(new FakeDataModel(context))
+ .withUIIntents(new UIIntentsImpl());
+ mCursor = TestDataFactory.getConversationListCursor();
+ }
+
+
+ protected void verifyLaunchedConversationForId(final String id,
+ final ConversationListItemView conversationView) {
+ // Must be a short click.
+ final ArgumentMatcher<ConversationListItemData> itemDataIdMatcher =
+ new ArgumentMatcher<ConversationListItemData>() {
+ @Override
+ public boolean matches(final Object arg) {
+ return TextUtils.equals(id, ((ConversationListItemData) arg).getConversationId());
+ }
+ };
+ Mockito.verify(mockHost).onConversationClicked(
+ Mockito.argThat(itemDataIdMatcher), Mockito.eq(false),
+ Mockito.eq(conversationView));
+ }
+
+ protected void verifyContent(
+ final ConversationListItemView view, final FakeCursor cursor, final int index) {
+ /* ConversationQueryColumns.NAME */
+ final String conversationQueryColumnsName = "name";
+ final String name = (String) cursor.getAt(conversationQueryColumnsName, index);
+
+ /* ConversationQueryColumns.SNIPPET_TEXT */
+ final String conversationQueryColumnsSnippetText = "snippet_text";
+ final String snippet = (String) cursor.getAt(conversationQueryColumnsSnippetText, index);
+
+ /* ConversationQueryColumns.SORT_TIMESTAMP */
+ final String conversationQueryColumnsSortTimestamp = "sort_timestamp";
+ final String timestamp = Dates.getConversationTimeString(
+ (Long) cursor.getAt(conversationQueryColumnsSortTimestamp, index)).toString();
+
+ final boolean unread = !isRead(cursor, index);
+ verifyContent(view, name, snippet, timestamp, unread);
+ }
+
+ protected void verifyContent(
+ final ConversationListItemView view,
+ final String conversationName,
+ final String snippet,
+ final String timestamp,
+ final boolean unread) {
+ final TextView conversationNameView =
+ (TextView) view.findViewById(R.id.conversation_name);
+ final TextView snippetTextView = (TextView) view.findViewById(R.id.conversation_snippet);
+ final TextView timestampTextView = (TextView) view.findViewById(
+ R.id.conversation_timestamp);
+ final AsyncImageView imagePreviewView =
+ (AsyncImageView) view.findViewById(R.id.conversation_image_preview);
+
+ final Context context = Factory.get().getApplicationContext();
+ assertNotNull(conversationNameView);
+ assertEquals(conversationName, conversationNameView.getText());
+ assertNotNull(snippetTextView);
+ if (unread) {
+ assertEquals(ConversationListItemView.UNREAD_SNIPPET_LINE_COUNT,
+ snippetTextView.getMaxLines());
+ assertEquals(context.getResources().getColor(R.color.conversation_list_item_unread),
+ snippetTextView.getCurrentTextColor());
+ assertEquals(context.getResources().getColor(R.color.conversation_list_item_unread),
+ conversationNameView.getCurrentTextColor());
+
+ } else {
+ assertEquals(ConversationListItemView.NO_UNREAD_SNIPPET_LINE_COUNT,
+ snippetTextView.getMaxLines());
+ assertEquals(context.getResources().getColor(R.color.conversation_list_item_read),
+ snippetTextView.getCurrentTextColor());
+ assertEquals(context.getResources().getColor(R.color.conversation_list_item_read),
+ conversationNameView.getCurrentTextColor());
+ }
+
+ assertEquals(View.VISIBLE, imagePreviewView.getVisibility());
+ assertTrue(snippetTextView.getText().toString().contains(snippet));
+ assertEquals(timestamp, timestampTextView.getText());
+ }
+
+ protected boolean isRead(final FakeCursor cursor, final int index) {
+ return 1 == ((Integer) cursor.getAt("read", index)).intValue();
+ }
+
+ public void testBindUnread() {
+ final ConversationListItemView view = getView();
+ final int messageIndex = TestDataFactory.CONVERSATION_LIST_CURSOR_UNREAD_MESSAGE_INDEX;
+ mCursor.moveToPosition(messageIndex);
+ assertFalse(isRead(mCursor, messageIndex));
+ view.bind(mCursor, mockHost);
+ verifyContent(view, mCursor, messageIndex);
+ }
+
+ public void testBindRead() {
+ final ConversationListItemView view = getView();
+
+ final int messageIndex = TestDataFactory.CONVERSATION_LIST_CURSOR_READ_MESSAGE_INDEX;
+ mCursor.moveToPosition(messageIndex);
+ assertTrue(isRead(mCursor, messageIndex));
+ view.bind(mCursor, mockHost);
+ verifyContent(view, mCursor, messageIndex);
+ }
+
+ public void testClickLaunchesConversation() {
+ final ConversationListItemView view = getView();
+ final View swipeableContainer = view.findViewById(R.id.swipeableContainer);
+ mCursor.moveToFirst();
+ view.bind(mCursor, mockHost);
+ swipeableContainer.performClick();
+ verifyLaunchedConversationForId(
+ mCursor.getAt("_id" /* ConversationQueryColumns._ID */, 0).toString(), view);
+ }
+
+ public void testBindTwice() {
+ final ConversationListItemView view = getView();
+
+ mCursor.moveToFirst();
+ view.bind(mCursor, mockHost);
+
+ mCursor.moveToNext();
+ view.bind(mCursor, mockHost);
+ verifyContent(view, mCursor, mCursor.getPosition());
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return R.layout.conversation_list_item_view;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java
new file mode 100644
index 0000000..a38dac2
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/mediapicker/AudioRecordViewTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.media.MediaPlayer;
+import android.media.MediaRecorder.OnErrorListener;
+import android.media.MediaRecorder.OnInfoListener;
+import android.net.Uri;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.ui.ViewTest;
+import com.android.messaging.util.FakeMediaUtil;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+public class AudioRecordViewTest extends ViewTest<AudioRecordView> {
+
+ @Mock AudioRecordView.HostInterface mockHost;
+ @Mock LevelTrackingMediaRecorder mockRecorder;
+ @Mock MediaPlayer mockMediaPlayer;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ FakeFactory.register(getInstrumentation().getContext())
+ .withMediaUtil(new FakeMediaUtil(mockMediaPlayer));
+ }
+
+ private void verifyAudioSubmitted() {
+ Mockito.verify(mockHost).onAudioRecorded(Matchers.any(MessagePartData.class));
+ }
+
+ private AudioRecordView initView() {
+ final AudioRecordView view = getView();
+ view.testSetMediaRecorder(mockRecorder);
+ view.setHostInterface(mockHost);
+ return view;
+ }
+
+ public void testRecording() {
+ Mockito.when(mockRecorder.isRecording()).thenReturn(false);
+ Mockito.when(mockRecorder.startRecording(Matchers.<OnErrorListener>any(),
+ Matchers.<OnInfoListener>any(), Matchers.anyInt())).thenReturn(true);
+ Mockito.when(mockRecorder.stopRecording()).thenReturn(Uri.parse("content://someaudio/2"));
+ final AudioRecordView view = initView();
+ view.onRecordButtonTouchDown();
+ Mockito.verify(mockRecorder).startRecording(Matchers.<OnErrorListener>any(),
+ Matchers.<OnInfoListener>any(), Matchers.anyInt());
+ Mockito.when(mockRecorder.isRecording()).thenReturn(true);
+ // Record for 1 second to make it meaningful.
+ sleepNoThrow(1000);
+ view.onRecordButtonTouchUp();
+ // We added some buffer to the end of the audio recording, so sleep for sometime and
+ // verify audio is recorded.
+ sleepNoThrow(700);
+ Mockito.verify(mockRecorder).stopRecording();
+ verifyAudioSubmitted();
+ }
+
+ private void sleepNoThrow(final long duration) {
+ try {
+ Thread.sleep(duration);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return R.layout.mediapicker_audio_chooser;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java b/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java
new file mode 100644
index 0000000..951c694
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/mediapicker/CameraManagerTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.os.AsyncTask;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.ui.mediapicker.CameraManager.CameraWrapper;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+@SmallTest
+public class CameraManagerTest extends BugleTestCase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // Force each test to set up a camera wrapper to match their needs
+ CameraManager.setCameraWrapper(null);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ MockCameraFactory.cleanup();
+ }
+
+ public void testNoCameraDeviceGetInfo() {
+ CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper());
+ assertEquals(false, CameraManager.get().hasAnyCamera());
+ assertEquals(false, CameraManager.get().hasFrontAndBackCamera());
+ try {
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ fail("selectCamera should have thrown");
+ } catch (AssertionError e) {
+ }
+ }
+
+ public void testFrontFacingOnlyGetInfo() {
+ CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper(
+ MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT)
+ ));
+ assertEquals(true, CameraManager.get().hasAnyCamera());
+ assertEquals(false, CameraManager.get().hasFrontAndBackCamera());
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT);
+ assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing);
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing);
+ }
+
+ public void testBackFacingOnlyGetInfo() {
+ CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper(
+ MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK)
+ ));
+ assertEquals(true, CameraManager.get().hasAnyCamera());
+ assertEquals(false, CameraManager.get().hasFrontAndBackCamera());
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT);
+ assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing);
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing);
+ }
+
+ public void testFrontAndBackGetInfo() {
+ CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper(
+ MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT),
+ MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK)
+ ));
+ assertEquals(true, CameraManager.get().hasAnyCamera());
+ assertEquals(true, CameraManager.get().hasFrontAndBackCamera());
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT);
+ assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing);
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing);
+ }
+
+ public void testSwapCamera() {
+ CameraManager.setCameraWrapper(MockCameraFactory.createCameraWrapper(
+ MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT),
+ MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK)
+ ));
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_FRONT);
+ assertEquals(CameraInfo.CAMERA_FACING_FRONT, CameraManager.get().getCameraInfo().facing);
+ CameraManager.get().swapCamera();
+ assertEquals(CameraInfo.CAMERA_FACING_BACK, CameraManager.get().getCameraInfo().facing);
+ }
+
+ public void testOpenCamera() {
+ Camera backCamera = MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_BACK);
+ Camera frontCamera = MockCameraFactory.createCamera(CameraInfo.CAMERA_FACING_FRONT);
+ CameraWrapper wrapper = MockCameraFactory.createCameraWrapper(frontCamera, backCamera);
+ CameraManager.setCameraWrapper(wrapper);
+ CameraManager.get().selectCamera(CameraInfo.CAMERA_FACING_BACK);
+ CameraManager.get().openCamera();
+ CameraManager.get().openCamera();
+ CameraManager.get().openCamera();
+ waitForPendingAsyncTasks();
+ Mockito.verify(wrapper, Mockito.never()).open(0);
+ Mockito.verify(wrapper).open(1);
+ Mockito.verify(wrapper, Mockito.never()).release(frontCamera);
+ Mockito.verify(wrapper, Mockito.never()).release(backCamera);
+ CameraManager.get().swapCamera();
+ waitForPendingAsyncTasks();
+ Mockito.verify(wrapper).open(0);
+ Mockito.verify(wrapper).open(1);
+ Mockito.verify(wrapper, Mockito.never()).release(frontCamera);
+ Mockito.verify(wrapper).release(backCamera);
+ InOrder inOrder = Mockito.inOrder(wrapper);
+ inOrder.verify(wrapper).open(1);
+ inOrder.verify(wrapper).release(backCamera);
+ inOrder.verify(wrapper).open(0);
+ }
+
+ private void waitForPendingAsyncTasks() {
+ try {
+ final Object lockObject = new Object();
+
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ synchronized (lockObject) {
+ lockObject.notifyAll();
+ }
+ }
+ }.execute();
+
+ synchronized (lockObject) {
+ lockObject.wait(500);
+ }
+ } catch (InterruptedException e) {
+ fail();
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java
new file mode 100644
index 0000000..83d8ac9
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/mediapicker/GalleryGridItemViewTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.provider.MediaStore.Images.Media;
+import android.view.View;
+import android.widget.CheckBox;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeCursor;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.datamodel.data.TestDataFactory;
+import com.android.messaging.ui.AsyncImageView;
+import com.android.messaging.ui.ViewTest;
+import com.android.messaging.util.UriUtil;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+public class GalleryGridItemViewTest extends ViewTest<GalleryGridItemView> {
+
+ @Mock GalleryGridItemView.HostInterface mockHost;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getTargetContext();
+ FakeFactory.register(context)
+ .withDataModel(new FakeDataModel(context));
+ }
+
+ protected void verifyClickedItem(final View view, final GalleryGridItemData data) {
+ Mockito.verify(mockHost).onItemClicked(view, data, false /* longClick */);
+ }
+
+ protected void verifyContent(
+ final GalleryGridItemView view,
+ final String imageUrl,
+ final boolean showCheckbox,
+ final boolean isSelected) {
+ final AsyncImageView imageView = (AsyncImageView) view.findViewById(R.id.image);
+ final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkbox);
+
+ assertNotNull(imageView);
+ assertTrue(imageView.mImageRequestBinding.isBound());
+ assertTrue(imageView.mImageRequestBinding.getData().getKey().startsWith(imageUrl));
+ assertNotNull(checkBox);
+ if (showCheckbox) {
+ assertEquals(View.VISIBLE, checkBox.getVisibility());
+ assertEquals(isSelected, checkBox.isChecked());
+ } else {
+ assertNotSame(View.VISIBLE, checkBox.getVisibility());
+ }
+ }
+
+ public void testBind() {
+ Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(false);
+ Mockito.when(mockHost.isItemSelected(Matchers.<GalleryGridItemData>any()))
+ .thenReturn(false);
+ final GalleryGridItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getGalleryGridCursor();
+ cursor.moveToFirst();
+ final String path = (String) cursor.getAt(Media.DATA, 0);
+ view.bind(cursor, mockHost);
+ verifyContent(view, UriUtil.getUriForResourceFile(path).toString(),
+ false, false);
+ }
+
+ public void testBindMultiSelectUnSelected() {
+ Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true);
+ Mockito.when(mockHost.isItemSelected(Matchers.<GalleryGridItemData>any()))
+ .thenReturn(false);
+ final GalleryGridItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getGalleryGridCursor();
+ cursor.moveToFirst();
+ final String path = (String) cursor.getAt(Media.DATA, 0);
+ view.bind(cursor, mockHost);
+ verifyContent(view, UriUtil.getUriForResourceFile(path).toString(),
+ true, false);
+ }
+
+ public void testBindMultiSelectSelected() {
+ Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true);
+ Mockito.when(mockHost.isItemSelected(Matchers.<GalleryGridItemData>any()))
+ .thenReturn(true);
+ final GalleryGridItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getGalleryGridCursor();
+ cursor.moveToFirst();
+ final String path = (String) cursor.getAt(Media.DATA, 0);
+ view.bind(cursor, mockHost);
+ verifyContent(view, UriUtil.getUriForResourceFile(path).toString(),
+ true, true);
+ }
+
+ public void testClick() {
+ Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(false);
+ Mockito.when(mockHost.isItemSelected(Matchers.<GalleryGridItemData>any()))
+ .thenReturn(false);
+ final GalleryGridItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getGalleryGridCursor();
+ cursor.moveToFirst();
+ view.bind(cursor, mockHost);
+ view.performClick();
+ verifyClickedItem(view, view.mData);
+ }
+
+ public void testBindTwice() {
+ Mockito.when(mockHost.isMultiSelectEnabled()).thenReturn(true);
+ Mockito.when(mockHost.isItemSelected(Matchers.<GalleryGridItemData>any()))
+ .thenReturn(false);
+ final GalleryGridItemView view = getView();
+ final FakeCursor cursor = TestDataFactory.getGalleryGridCursor();
+
+ cursor.moveToFirst();
+ view.bind(cursor, mockHost);
+
+ cursor.moveToNext();
+ final String path = (String) cursor.getAt(Media.DATA, 1);
+ view.bind(cursor, mockHost);
+ verifyContent(view, UriUtil.getUriForResourceFile(path).toString(),
+ true, false);
+ }
+
+ @Override
+ protected int getLayoutIdForView() {
+ return R.layout.gallery_grid_item_view;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java
new file mode 100644
index 0000000..4a7040e
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/mediapicker/MediaPickerTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import com.android.messaging.FakeFactory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.FakeDataModel;
+import com.android.messaging.datamodel.binding.Binding;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.ui.FragmentTestCase;
+
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+public class MediaPickerTest extends FragmentTestCase<MediaPicker> {
+ @Mock protected MediaPickerData mMockMediaPickerData;
+ @Mock protected DraftMessageData mMockDraftMessageData;
+ protected FakeDataModel mFakeDataModel;
+
+ public MediaPickerTest() {
+ super(MediaPicker.class);
+ }
+
+ @Override
+ protected MediaPicker getFragment() {
+ if (mFragment == null) {
+ mFragment = new MediaPicker(getInstrumentation().getTargetContext());
+ }
+ return mFragment;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getTargetContext();
+ mFakeDataModel = new FakeDataModel(context)
+ .withMediaPickerData(mMockMediaPickerData);
+ FakeFactory.register(context)
+ .withDataModel(mFakeDataModel);
+ }
+
+ /**
+ * Helper method to initialize the MediaPicker and its data.
+ */
+ private void initFragment(final int supportedMediaTypes, final Integer[] expectedLoaderIds,
+ final boolean filterTabBeforeAttach) {
+ Mockito.when(mMockMediaPickerData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ Mockito.when(mMockDraftMessageData.isBound(Matchers.anyString()))
+ .thenReturn(true);
+ final Binding<DraftMessageData> draftBinding = BindingBase.createBinding(this);
+ draftBinding.bind(mMockDraftMessageData);
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final MediaPicker fragment = getFragment();
+ if (filterTabBeforeAttach) {
+ fragment.setSupportedMediaTypes(supportedMediaTypes);
+ getActivity().setFragment(fragment);
+ } else {
+ getActivity().setFragment(fragment);
+ fragment.setSupportedMediaTypes(supportedMediaTypes);
+ }
+ fragment.setDraftMessageDataModel(draftBinding);
+ Mockito.verify(mMockMediaPickerData,
+ Mockito.atLeastOnce()).init(
+ Matchers.eq(fragment.getLoaderManager()));
+ fragment.open(MediaPicker.MEDIA_TYPE_ALL, false);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ public void testDefaultTabs() {
+ Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0);
+ initFragment(MediaPicker.MEDIA_TYPE_ALL, new Integer[] {
+ MediaPickerData.GALLERY_IMAGE_LOADER },
+ false);
+ final MediaPicker mediaPicker = getFragment();
+ final View view = mediaPicker.getView();
+ assertNotNull(view);
+ final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip);
+ assertEquals(tabStrip.getChildCount(), 3);
+ for (int i = 0; i < tabStrip.getChildCount(); i++) {
+ final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i);
+ assertEquals(View.VISIBLE, tabButton.getVisibility());
+ assertEquals(i == 0, tabButton.isSelected());
+ }
+ }
+
+ public void testFilterTabsBeforeAttach() {
+ Mockito.when(mMockMediaPickerData.getSelectedChooserIndex()).thenReturn(0);
+ initFragment(MediaPicker.MEDIA_TYPE_IMAGE, new Integer[] {
+ MediaPickerData.GALLERY_IMAGE_LOADER },
+ true);
+ final MediaPicker mediaPicker = getFragment();
+ final View view = mediaPicker.getView();
+ assertNotNull(view);
+ final ViewGroup tabStrip = (ViewGroup) view.findViewById(R.id.mediapicker_tabstrip);
+ assertEquals(tabStrip.getChildCount(), 3);
+ for (int i = 0; i < tabStrip.getChildCount(); i++) {
+ final ImageButton tabButton = (ImageButton) tabStrip.getChildAt(i);
+ assertEquals(i == 0, tabButton.isSelected());
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java b/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java
new file mode 100644
index 0000000..789a78f
--- /dev/null
+++ b/tests/src/com/android/messaging/ui/mediapicker/MockCameraFactory.java
@@ -0,0 +1,73 @@
+/*
+ * 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.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+
+import com.android.messaging.ui.mediapicker.CameraManager.CameraWrapper;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class MockCameraFactory {
+ private static Map<Camera, CameraInfo> sCameraInfos = new HashMap<Camera, CameraInfo>();
+
+ public static Camera createCamera(int facing) {
+ Camera camera = Mockito.mock(Camera.class);
+ CameraInfo cameraInfo = new CameraInfo();
+ cameraInfo.facing = facing;
+ sCameraInfos.put(camera, cameraInfo);
+ return camera;
+ }
+
+ public static void getCameraInfo(Camera camera, CameraInfo outCameraInfo) {
+ CameraInfo cameraInfo = sCameraInfos.get(camera);
+ outCameraInfo.facing = cameraInfo.facing;
+ outCameraInfo.orientation = cameraInfo.orientation;
+ outCameraInfo.canDisableShutterSound = cameraInfo.canDisableShutterSound;
+ }
+
+ public static CameraWrapper createCameraWrapper(final Camera... cameras) {
+ CameraWrapper wrapper = Mockito.mock(CameraWrapper.class);
+ Mockito.when(wrapper.getNumberOfCameras()).thenReturn(cameras.length);
+ Mockito.when(wrapper.open(Mockito.anyInt())).then(new Answer<Camera>() {
+ @Override
+ public Camera answer(InvocationOnMock invocation) {
+ return cameras[(Integer) invocation.getArguments()[0]];
+ }
+ });
+ Mockito.doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ getCameraInfo(
+ cameras[(Integer) invocation.getArguments()[0]],
+ (CameraInfo) invocation.getArguments()[1]
+ );
+ return null;
+ }
+ }).when(wrapper).getCameraInfo(Mockito.anyInt(), Mockito.any(CameraInfo.class));
+ return wrapper;
+ }
+
+ public static void cleanup() {
+ sCameraInfos.clear();
+ }
+}
diff --git a/tests/src/com/android/messaging/util/BugleGservicesTest.java b/tests/src/com/android/messaging/util/BugleGservicesTest.java
new file mode 100644
index 0000000..1a0a10e
--- /dev/null
+++ b/tests/src/com/android/messaging/util/BugleGservicesTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+
+@SmallTest
+public class BugleGservicesTest extends BugleTestCase {
+
+ public void testGServiceGet() {
+ final BugleGservices bugleGservices = new FakeBugleGservices();
+
+ assertEquals(BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT,
+ bugleGservices.getString(
+ BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX,
+ BugleGservicesKeys.SMS_IGNORE_MESSAGE_REGEX_DEFAULT));
+ }
+}
diff --git a/tests/src/com/android/messaging/util/ContactUtilTest.java b/tests/src/com/android/messaging/util/ContactUtilTest.java
new file mode 100644
index 0000000..48a7ced
--- /dev/null
+++ b/tests/src/com/android/messaging/util/ContactUtilTest.java
@@ -0,0 +1,306 @@
+/*
+ * 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.util;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.text.TextUtils;
+
+import com.android.messaging.BugleTestCase;
+import com.android.messaging.FakeFactory;
+
+import org.junit.Assert;
+
+import java.util.ArrayList;
+
+/*
+ * Class for testing ContactUtil.
+ */
+@LargeTest
+public class ContactUtilTest extends BugleTestCase {
+ private static final String TEST_NAME_PREFIX = "BugleTest:";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // TODO: This test will actually mess with contacts on your phone.
+ // Ideally we would use a fake content provider to give us contact data...
+ FakeFactory.registerWithoutFakeContext(getTestContext());
+
+ // add test contacts.
+ addTestContact("John", "650-123-1233", "john@gmail.com", false);
+ addTestContact("Joe", "(650)123-1233", "joe@gmail.com", false);
+ addTestContact("Jim", "650 123 1233", "jim@gmail.com", false);
+ addTestContact("Samantha", "650-123-1235", "samantha@gmail.com", true);
+ addTestContact("Adrienne", "650-123-1236", "adrienne@gmail.com", true);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ deleteTestContacts();
+ super.tearDown();
+ }
+
+ /**
+ * Add a test contact based on contact name, phone and email.
+ */
+ private void addTestContact(
+ final String name, final String phone, final String email, final boolean starred)
+ throws Exception {
+ final ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
+ .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
+ .build());
+
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
+ TEST_NAME_PREFIX + name).build());
+
+ if (phone != null) {
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
+ .withValue(ContactsContract.CommonDataKinds.Phone.TYPE,
+ ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
+ .build());
+ }
+
+ if (email != null) {
+ ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ .withValue(ContactsContract.Data.MIMETYPE,
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+ .withValue(ContactsContract.CommonDataKinds.Email.DATA, email)
+ .withValue(ContactsContract.CommonDataKinds.Email.TYPE,
+ ContactsContract.CommonDataKinds.Email.TYPE_WORK)
+ .build());
+ }
+
+ mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
+
+ // Star the whole contact if needed.
+ if (starred) {
+ final ContentValues values = new ContentValues();
+ values.put(Contacts.STARRED, 1);
+ getContext().getContentResolver().update(Contacts.CONTENT_URI, values,
+ Contacts.DISPLAY_NAME + "= ?", new String[] { TEST_NAME_PREFIX + name });
+ }
+ }
+
+ /**
+ * Remove test contacts added during test setup.
+ */
+ private void deleteTestContacts() {
+ final Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
+ Uri.encode(TEST_NAME_PREFIX));
+ final Cursor cur =
+ mContext.getContentResolver().query(contactUri, null, null, null, null);
+ try {
+ if (cur.moveToFirst()) {
+ do {
+ final String lookupKey = cur.getString(cur.getColumnIndex(Contacts.LOOKUP_KEY));
+ final Uri uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
+ mContext.getContentResolver().delete(uri, null, null);
+ } while (cur.moveToNext());
+ }
+ } catch (final Exception e) {
+ System.out.println(e.getStackTrace());
+ }
+ }
+
+ /**
+ * Verify ContactUtil.getPhone will return all phones, including the ones added for test.
+ */
+ public void ingoredTestGetPhones() {
+ final Cursor cur = ContactUtil.getPhones(getContext())
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testGetPhones: Number of phones on the device:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne");
+ }
+
+ /**
+ * Verify ContactUtil.filterPhone will work on name based matches.
+ */
+ public void ingoredTestFilterPhonesByName() {
+ final Cursor cur = ContactUtil.filterPhones(getContext(), TEST_NAME_PREFIX)
+ .performSynchronousQuery();
+
+ if (cur.getCount() != 5) {
+ Assert.fail("Cursor should have size of 5");
+ return;
+ }
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne");
+ }
+
+ /**
+ * Verify ContactUtil.filterPhone will work on partial number matches.
+ */
+ public void ingoredTestFilterPhonesByPartialNumber() {
+ final String[] filters = new String[] { "650123", "650-123", "(650)123", "650 123" };
+
+ for (final String filter : filters) {
+ final Cursor cur = ContactUtil.filterPhones(getContext(), filter)
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testFilterPhonesByPartialNumber: Number of phones:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne");
+ }
+ }
+
+ /**
+ * Verify ContactUtil.filterPhone will work on full number matches.
+ */
+ public void ingoredTestFilterPhonesByFullNumber() {
+ final String[] filters = new String[] {
+ "6501231233", "650-123-1233", "(650)123-1233", "650 123 1233" };
+
+ for (final String filter : filters) {
+ final Cursor cur = ContactUtil.filterPhones(getContext(), filter)
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testFilterPhonesByFullNumber: Number of phones:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim");
+ }
+ }
+
+ /**
+ * Verify ContactUtil.lookPhone will work on number including area code.
+ */
+ public void ingoredTestLookupPhoneWithAreaCode() {
+ final String[] filters = new String[] {
+ "6501231233", "650-123-1233", "(650)123-1233", "650 123 1233" };
+
+ for (final String filter : filters) {
+ final Cursor cur = ContactUtil.lookupPhone(getContext(), filter)
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testLookupPhoneWithAreaCode: Number of phones:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim");
+ }
+ }
+
+ /**
+ * Verify ContactUtil.lookPhone will work on number without area code.
+ */
+ public void ingoredTestLookupPhoneWithoutAreaCode() {
+ final String[] filters = new String[] {
+ "1231233", "123-1233", "123 1233" };
+
+ for (final String filter : filters) {
+ final Cursor cur = ContactUtil.lookupPhone(getContext(), filter)
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testLookupPhoneWithoutAreaCode: Number of phones:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Joe");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Jim");
+ }
+ }
+
+ public void ingoredTestGetFrequentPhones() {
+ final Cursor cur = ContactUtil.getFrequentContacts(getContext())
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testGetFrequentPhones: Number of phones on the device:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Samantha");
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "Adrienne");
+ }
+
+ /**
+ * Verify ContactUtil.filterEmails will work on partial email.
+ */
+ public void ingoredTestFilterEmails() {
+ final Cursor cur = ContactUtil.filterEmails(getContext(), "john@")
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testFilterEmails: Number of emails:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ }
+
+ /**
+ * Verify ContactUtil.lookupEmail will work on full email.
+ */
+ public void ingoredTestLookupEmail() {
+ final Cursor cur = ContactUtil.lookupEmail(getContext(), "john@gmail.com")
+ .performSynchronousQuery();
+
+ LogUtil.i(LogUtil.BUGLE_TAG, "testLookupEmail: Number of emails:" +
+ cur.getCount());
+
+ verifyCursorContains(cur, TEST_NAME_PREFIX + "John");
+ }
+
+ /**
+ * Utility method to check whether cursor contains a particular contact.
+ */
+ private void verifyCursorContains(final Cursor cursor, final String nameToVerify) {
+ if (cursor.moveToFirst()) {
+ do {
+ final String name = cursor.getString(ContactUtil.INDEX_DISPLAY_NAME);
+ if (TextUtils.equals(name, nameToVerify)) {
+ return;
+ }
+ } while (cursor.moveToNext());
+ }
+ Assert.fail("Cursor should have " + nameToVerify);
+ }
+}
diff --git a/tests/src/com/android/messaging/util/FakeBugleGservices.java b/tests/src/com/android/messaging/util/FakeBugleGservices.java
new file mode 100644
index 0000000..f7d90da
--- /dev/null
+++ b/tests/src/com/android/messaging/util/FakeBugleGservices.java
@@ -0,0 +1,55 @@
+/*
+ * 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.util;
+
+/**
+ * Fake implementation which just returns the default values.
+ */
+public class FakeBugleGservices extends BugleGservices {
+ public FakeBugleGservices() {
+ }
+
+ @Override
+ public void registerForChanges(final Runnable r) {
+ }
+
+ @Override
+ public long getLong(final String key, final long defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public int getInt(final String key, final int defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getBoolean(final String key, final boolean defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public String getString(final String key, final String defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public float getFloat(String key, float defaultValue) {
+ return defaultValue;
+ }
+
+}
diff --git a/tests/src/com/android/messaging/util/FakeBuglePrefs.java b/tests/src/com/android/messaging/util/FakeBuglePrefs.java
new file mode 100644
index 0000000..b3429a6
--- /dev/null
+++ b/tests/src/com/android/messaging/util/FakeBuglePrefs.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util;
+
+
+/**
+ * Fake implementation which just returns the default values and ignores put operations.
+ */
+public class FakeBuglePrefs extends BuglePrefs {
+ public FakeBuglePrefs() {
+ }
+
+ @Override
+ public int getInt(final String key, final int defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public long getLong(final String key, final long defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getBoolean(final String key, final boolean defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public String getString(final String key, final String defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public byte[] getBytes(String key) {
+ return null;
+ }
+
+ @Override
+ public void putInt(final String key, final int value) {
+ }
+
+ @Override
+ public void putLong(final String key, final long value) {
+ }
+
+ @Override
+ public void putBoolean(final String key, final boolean value) {
+ }
+
+ @Override
+ public void putString(final String key, final String value) {
+ }
+
+ @Override
+ public void putBytes(String key, byte[] value) {
+ }
+
+ @Override
+ public String getSharedPreferencesName() {
+ return "FakeBuglePrefs";
+ }
+
+ @Override
+ public void onUpgrade(int oldVersion, int newVersion) {
+ }
+
+ @Override
+ public void remove(String key) {
+ }
+}
diff --git a/tests/src/com/android/messaging/util/FakeMediaUtil.java b/tests/src/com/android/messaging/util/FakeMediaUtil.java
new file mode 100644
index 0000000..e37ff0c
--- /dev/null
+++ b/tests/src/com/android/messaging/util/FakeMediaUtil.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+
+public class FakeMediaUtil extends MediaUtil {
+ private final MediaPlayer mMediaPlayer;
+ public FakeMediaUtil(final MediaPlayer mediaPlayer) {
+ mMediaPlayer = mediaPlayer;
+ }
+
+ @Override
+ public void playSound(Context context, int resId, OnCompletionListener completionListener) {
+ if (completionListener != null) {
+ completionListener.onCompletion();
+ }
+ }
+}
diff --git a/tests/src/com/android/messaging/util/YouTubeUtilTest.java b/tests/src/com/android/messaging/util/YouTubeUtilTest.java
new file mode 100644
index 0000000..03c461a
--- /dev/null
+++ b/tests/src/com/android/messaging/util/YouTubeUtilTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.util;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.messaging.BugleTestCase;
+
+/*
+ * Class for testing YouTubeUtil.
+ */
+@SmallTest
+public class YouTubeUtilTest extends BugleTestCase {
+ public void testGetYoutubePreviewImageLink() {
+ final String videoId = "dQw4w9WgXcQ";
+ final String videoThumbnailUrl = YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_PREFIX + videoId
+ + YouTubeUtil.YOUTUBE_STATIC_THUMBNAIL_END;
+
+ // Check known valid youtube links to videos
+ assertEquals(
+ YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/watch?v=" + videoId),
+ videoThumbnailUrl);
+ assertEquals(
+ YouTubeUtil.getYoutubePreviewImageLink("https://www.youtube.com/watch?v=" + videoId
+ + "&feature=youtu.be"), videoThumbnailUrl);
+ assertEquals(
+ YouTubeUtil.getYoutubePreviewImageLink("www.youtube.com/watch?v=" + videoId),
+ videoThumbnailUrl);
+ assertEquals(
+ YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/embed/" + videoId),
+ videoThumbnailUrl);
+ assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/v/" + videoId),
+ videoThumbnailUrl);
+ assertEquals(
+ YouTubeUtil.getYoutubePreviewImageLink("https://youtube.googleapis.com/v/"
+ + videoId), videoThumbnailUrl);
+ assertEquals(
+ YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com/apiplayer?video_id="
+ + videoId), videoThumbnailUrl);
+ // This is the type of links that are used as shares from YouTube and will be the most
+ // likely case that we see
+ assertEquals(YouTubeUtil.getYoutubePreviewImageLink("http://youtu.be/" + videoId),
+ videoThumbnailUrl);
+
+ // Try links that shouldn't work
+ assertNull(YouTubeUtil.getYoutubePreviewImageLink("http://www.youtube.com"));
+ }
+} \ No newline at end of file
diff --git a/tools/buglesql b/tools/buglesql
new file mode 100755
index 0000000..c9ac8ef
--- /dev/null
+++ b/tools/buglesql
@@ -0,0 +1,43 @@
+#!/bin/bash
+# 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.
+
+usage='buglesql [-c] [-r] sql
+ -c display in columns with headers (default)
+ -r display as records'
+
+opts='-column -header'
+
+while test $# -gt 0
+do
+ case $1 in
+ -c)
+ opts='-column -header'
+ shift
+ ;;
+ -r)
+ opts='-line'
+ shift
+ ;;
+ *)
+ break;
+ esac
+done
+
+if [ $# -lt 1 ]; then
+ echo "$usage"
+ exit 1
+fi
+
+adb shell su -c sqlite3 $opts data/data/com.android.messaging/databases/bugle_db "$1"
diff --git a/tools/dumpapkversion.sh b/tools/dumpapkversion.sh
new file mode 100755
index 0000000..48ff590
--- /dev/null
+++ b/tools/dumpapkversion.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# 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.
+for i in `find . -name \*.apk`
+do
+ echo -n $i
+ echo -n " "
+ aapt dump badging $i | grep versionCode | sed -E 's/^.*versionCode/versionCode/'
+done
diff --git a/tools/messagegen/fillsms b/tools/messagegen/fillsms
new file mode 100755
index 0000000..6903941
--- /dev/null
+++ b/tools/messagegen/fillsms
@@ -0,0 +1,287 @@
+#!/bin/bash
+# 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.
+###################################################################
+## Script that generates SMS and MMS messages and fills the Android
+## telephony provider mmssms.db. This is used for testing SMS/MMS.
+###################################################################
+
+AREA_CODE=605
+
+TABLE_CANONICAL_ADDRESSES_START_ID=100
+TABLE_THREADS_START_ID=100
+TABLE_SMS_START_ID=1000
+
+START_TIMESTAMP_IN_SECONDS=1357683093 # 1/8/2013 2:11:33 PM
+TIMESTAMP_INC_IN_SECONDS=120
+
+PART_DIR="/data/data/com.android.providers.telephony/app_parts"
+
+USAGE='fillsms [-f] [-x] <device_phone_number> <# of threads> <# of sms per thread> <# of mms per thread> <image list file> <sql file>
+ -f -- Only generates the SQL file, do not push to the device
+ -x -- Only execute a SQL file
+ -g -- For GB devices
+Examples:
+ # Generate 2 threads each with 10 SMSes and 10 MMSes on device with phone
+ # number +16508619525. MMS messages use images listed in ./images, which list
+ # *.jpg and *.gif files in local directory. The SQL commands are in sql.txt
+ fillsms +16508619525 2 10 10 images sql.txt
+
+ # Same as above but only creating the SQL command file without pushing to
+ # device
+ fillsms -f +16508619525 2 10 10 images sql.txt
+
+ # Just push the sql.txt to device without generating new SQLs
+ fillsms -x +16508619525 2 10 10 images sql.txt
+'
+
+SMIL='<smil> <head> <layout> <root-layout height="%dpx" width="%dpx"> <region fit="meet" height="%dpx" id="Image" left="0" top="0" width="%dpx"/></root-layout> </layout> </head> <body> <par dur="5000ms"> <img region="Image" src="%s"/> </par> </body> </smil>'
+
+MAX_WORDS_PER_MESSAGE=15
+
+DICT=american-english
+
+# don't actually run the sql on device
+opt_sql_only=0
+opt_exec_only=0
+opt_for_gb=0
+
+while test $# -gt 0
+do
+ case $1 in
+ -f)
+ opt_sql_only=1
+ shift
+ ;;
+ -x)
+ opt_exec_only=1
+ shift
+ ;;
+ -g)
+ opt_for_gb=1
+ shift
+ ;;
+ *)
+ break;
+ esac
+done
+
+
+if [ $opt_sql_only -eq "1" -a $opt_exec_only -eq "1" ]; then
+ echo "-f and -x can not coexist"
+ echo "$USAGE"
+ exit 1
+fi
+
+if [ $# -lt 6 ]; then
+ echo "$USAGE"
+ exit 1
+fi
+device_phone=$1
+shift
+num_of_threads=$1
+shift
+sms_per_thread=$1
+shift
+mms_per_thread=$1
+shift
+image_list_file=$1
+shift
+sql_file=$1
+shift
+
+dict_lines=`wc -l < $DICT`
+image_files=`wc -l < $image_list_file`
+
+if [ $mms_per_thread -gt "0" ]; then
+ if [ ! -f $image_list_file ]; then
+ echo "No image files for creating MMS messages"
+ exit 1
+ fi
+fi
+
+echoerr ()
+{
+ echo "$@" 1>&2;
+}
+
+random_value ()
+{
+ echo $(( $RANDOM % $1 + 1 ))
+}
+
+dict_word ()
+{
+ local v=$(random_value 30000)
+ sed $v"q;d" $DICT
+}
+
+gen_message ()
+{
+ local words=$(random_value $MAX_WORDS_PER_MESSAGE)
+ local message=
+ for k in `seq 1 $words`;
+ do
+ local word=$(dict_word)
+ message="$message $word"
+ done
+ echo $message | sed -e "s/'//g"
+}
+
+random_image ()
+{
+ local v=$(random_value $image_files)
+ sed $v"q;d" $image_list_file
+}
+
+add_sql ()
+{
+ echo $1 >> $sql_file
+}
+
+adb_sql ()
+{
+ echo $1
+ adb shell sqlite3 data/data/com.android.providers.telephony/databases/mmssms.db "$1"
+}
+
+######################################################################################
+######################################################################################
+
+if [ $opt_exec_only -eq "0" ]; then
+ # clean up sql file
+ rm -f $sql_file
+
+ # add sql to clean up database
+ add_sql "delete from pdu where _id>=$TABLE_SMS_START_ID;"
+ add_sql "delete from part where _id>=$TABLE_SMS_START_ID;"
+ add_sql "delete from addr where _id>=$TABLE_SMS_START_ID;"
+ add_sql "delete from sms where _id>=$TABLE_SMS_START_ID;"
+ add_sql "delete from threads where _id>=$TABLE_THREADS_START_ID;"
+ add_sql "delete from canonical_addresses where _id>=$TABLE_CANONICAL_ADDRESSES_START_ID;"
+
+ for i in `seq 1 $num_of_threads`;
+ do
+ echo
+ echo "Creating thread $i ......"
+ echo
+
+ # Get random phone number
+ value=$(random_value 1000)
+ middle=$(printf '%03d' $value)
+ value=$(random_value 10000)
+ last=$(printf '%04d' $value)
+ phone="+1$AREA_CODE$middle$last"
+ echo $phone
+ echo
+
+ timestamp=$(( $START_TIMESTAMP_IN_SECONDS + 5 * $TIMESTAMP_INC_IN_SECONDS * $i ))
+
+ # Generate threads
+ addr_id=$(( $TABLE_CANONICAL_ADDRESSES_START_ID + $i ))
+ add_sql "insert into canonical_addresses (_id,address) values ($addr_id,'$phone');"
+
+ thread_id=$(( $TABLE_THREADS_START_ID + $i ))
+ add_sql "insert into threads (_id,date,message_count,recipient_ids,snippet,snippet_cs,read,type,error,has_attachment) values ($thread_id, $timestamp, $sms_per_thread, $addr_id, 'snippet', 0, 1, 0, 0, 0);"
+
+ # Generate SMS
+ if [ $sms_per_thread -gt "0" ]; then
+ half_timestamp_inc=$(( 500 + ((($sms_per_thread + $mms_per_thread) * $TIMESTAMP_INC_IN_SECONDS) * 500 / $sms_per_thread) ))
+ for j in `seq 1 $sms_per_thread`;
+ do
+ message=$(gen_message)
+ date=$(( ( 1000 * $timestamp ) - $half_timestamp_inc * ( 2 * ($sms_per_thread - $j) + ( $i % 2 ) ) ))
+ message_id=$(( $TABLE_SMS_START_ID + $sms_per_thread * $i * 2 + (2 * $j) ))
+ message_type=$(( $j % 2 + 1 ))
+ add_sql "insert into sms (_id,thread_id,address,person,date,status,type,body,read,seen) values ($message_id, $thread_id, '$phone', '$phone', $date, -1, $message_type, '$message', 1, 1);"
+ done
+ fi
+
+ # Generate MMS
+ if [ $mms_per_thread -gt "0" ]; then
+ half_timestamp_inc=$(( 1 + ((($sms_per_thread + $mms_per_thread) * $TIMESTAMP_INC_IN_SECONDS) / ( 2 * $mms_per_thread) ) ))
+ for j in `seq 1 $mms_per_thread`;
+ do
+ image_line=$(random_image)
+ image=`echo $image_line | awk '{ print $1 }'`
+ width=`echo $image_line | awk '{ print $2 }'`
+ height=`echo $image_line | awk '{ print $3 }'`
+ size=`echo $image_line | awk '{ print $4 }'`
+ date=$(( $timestamp - $half_timestamp_inc * ( 2 * ($mms_per_thread - $j) + ( ($i+1) % 2 ) ) ))
+ message_id=$(( $TABLE_SMS_START_ID + $sms_per_thread * $i * 2 + (2 * $j + 1) ))
+ message_type=$(( $j % 2 + 1 ))
+ if [ $message_type -eq '1' ]; then
+ m_type=132
+ else
+ m_type=128
+ fi
+ if [ $opt_for_gb -eq "0" ]; then
+ add_sql "insert into pdu (_id,thread_id,date,date_sent,msg_box,read,m_id,sub,sub_cs,ct_t,m_cls,m_type,v,m_size,pri,rr,tr_id,d_rpt,locked,seen,text_only) values ($message_id, $thread_id, $date, 0, $message_type, 1, 'hmma3p5s1a3m526@w.tmomail.net', 'no subject', 106, 'application/vnd.wap.multipart.related', 'personal', $m_type, 18, $size, 129, 129 , '$message_id', 129, 0, 1, 0);"
+ else
+ add_sql "insert into pdu (_id,thread_id,date,msg_box,read,m_id,sub,sub_cs,ct_t,m_cls,m_type,v,m_size,pri,rr,tr_id,d_rpt,locked,seen) values ($message_id, $thread_id, $date, $message_type, 1, 'hmma3p5s1a3m526@w.tmomail.net', 'no subject', 106, 'application/vnd.wap.multipart.related', 'personal', $m_type, 18, $size, 129, 129 , '$message_id', 129, 0, 1);"
+ fi
+ id_1=$(( $message_id ))
+ id_2=$(( $message_id + 1 ))
+ smil=$(printf "$SMIL" $height $width $height $width $image)
+ add_sql "insert into part (_id,mid,seq,ct,cid,cl,text) values ($id_1, $message_id, -1, 'application/smil', '<smil>', 'smil.xml', '$smil');"
+ image_no_suffix=${image%.*}
+ add_sql "insert into part (_id,mid,seq,ct,cid,cl,_data) values ($id_2, $message_id, 0, 'image/jpeg', '$image_no_suffix', '$image', '$PART_DIR/$image');"
+ if [ $message_type -eq '1' ]; then
+ add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_1, $message_id, '$phone', 137, 106);"
+ add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_2, $message_id, '$device_phone', 151, 106);"
+ else
+ add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_1, $message_id, 'insert-address-token', 137, 106);"
+ add_sql "insert into addr (_id,msg_id,address,type,charset) values ($id_2, $message_id, '$phone', 151, 106);"
+ fi
+ done
+ fi
+ done
+fi
+
+# Push to device
+if [ $opt_sql_only -eq "0" ]; then
+ # make sure we have access
+ adb root
+
+ # Push all local jpgs or gifs to device, being lazy here.
+ if [ $mms_per_thread -gt "0" ]; then
+ for file in `ls *.jpg *.gif`;
+ do
+ echo "adb push $file $PART_DIR/$file"
+ adb push $file $PART_DIR/$file
+ done
+ fi
+
+ echo "adb push $sql_file /data/fillsms"
+ adb push $sql_file /data/fillsms
+ echo
+ adb_sql ".read /data/fillsms"
+ echo
+ adb_sql "select count(*) from canonical_addresses where _id>=$TABLE_CANONICAL_ADDRESSES_START_ID;"
+ echo
+ adb_sql "select count(*) from threads where _id>=$TABLE_THREADS_START_ID;"
+ echo
+ if [ $sms_per_thread -gt "0" ]; then
+ adb_sql "select count(*) from sms where _id>=$TABLE_SMS_START_ID;"
+ echo
+ fi
+ if [ $mms_per_thread -gt "0" ]; then
+ adb_sql "select count(*) from pdu where _id>=$TABLE_SMS_START_ID;"
+ echo
+ adb_sql "select count(*) from part where _id>=$TABLE_SMS_START_ID;"
+ echo
+ adb_sql "select count(*) from addr where _id>=$TABLE_SMS_START_ID;"
+ echo
+ fi
+fi
diff --git a/tools/messagegen/listimages b/tools/messagegen/listimages
new file mode 100755
index 0000000..9c50072
--- /dev/null
+++ b/tools/messagegen/listimages
@@ -0,0 +1,21 @@
+#!/bin/bash
+# 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.
+
+for file in `ls *.jpg *.gif`;
+do
+ dimension=`identify -format "%w %h" $file`
+ size=`stat -c%s $file`
+ echo "$file $dimension $size"
+done
diff --git a/tools/mmssql b/tools/mmssql
new file mode 100755
index 0000000..1643dbb
--- /dev/null
+++ b/tools/mmssql
@@ -0,0 +1,43 @@
+#!/bin/bash
+# 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.
+
+usage='mmssql [-c] [-r] sql
+ -c display in columns with headers (default)
+ -r display as records'
+
+opts='-column -header'
+
+while test $# -gt 0
+do
+ case $1 in
+ -c)
+ opts='-column -header'
+ shift
+ ;;
+ -r)
+ opts='-line'
+ shift
+ ;;
+ *)
+ break;
+ esac
+done
+
+if [ $# -lt 1 ]; then
+ echo "$usage"
+ exit 1
+fi
+
+adb shell su -c sqlite3 $opts data/data/com.android.providers.telephony/databases/mmssms.db "$1"
diff --git a/version.mk b/version.mk
new file mode 100644
index 0000000..7407993
--- /dev/null
+++ b/version.mk
@@ -0,0 +1,123 @@
+# 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.
+#
+
+# The version code scheme for the package apk is:
+# Mmbbbtad
+# where
+# M - major version (one or more digits)
+# m - minor version (exactly 1 digit)
+# bbb - manually specified build number (exactly 3 digits)
+# t - build type (exactly 1 digit). Current valid values are:
+# 0 : internal (dev build)
+# 1 : prod build
+# a - device architecture (exactly 1 digit). Current valid values are:
+# 0 : non-native
+# 1 : armv5te
+# 3 : armv7-a
+# 4 : arm64-v8a
+# 5 : mips
+# 6 : mips-64
+# 7 : x86
+# 8 : x86-64
+# d - asset density (exactly 1 digit). Current valid values are:
+# 0 : all densities
+# 2 : mdpi
+# 4 : hdpi
+# 6 : xhdpi
+# 8 : xxhdpi
+# 9 : xxxhdpi
+# Mmbbb is specified manually. tad is automatically set during the build.
+#
+# For the client jar, the version code is agnostic to the target architecture and density: Mmbbbt00
+#
+# NOTE: arch needs to be more significant than density because x86 devices support running ARM
+# code in emulation mode, so all x86 versions must be higher than all ARM versions to ensure
+# we deliver true x86 code to those devices.
+
+# Specify the following manually. Note that base_version_minor must be exactly 1 digit and
+# base_version_build must be exactly 3 digits.
+base_version_major := 1
+base_version_minor := 0
+base_version_build := 001
+
+#####################################################
+#####################################################
+# Collect automatic version code parameters
+ifneq "" "$(filter eng.%,$(BUILD_NUMBER))"
+ # This is an eng build
+ base_version_buildtype := 0
+else
+ # This is a build server build
+ base_version_buildtype := 1
+endif
+
+# Set the device architecture digit
+ifeq "$(TARGET_ARCH)" "arm"
+ ifeq "$(TARGET_ARCH_VARIANT)" "armv5te"
+ base_version_arch := 1
+ else ifeq "$(TARGET_ARCH_VARIANT)" "armv7-a"
+ base_version_arch := 3
+ endif
+else ifeq "$(TARGET_ARCH)" "arm64"
+ base_version_arch := 4
+else ifeq "$(TARGET_ARCH)" "mips"
+ base_version_arch := 5
+else ifeq "$(TARGET_ARCH)" "x86"
+ base_version_arch := 7
+else ifeq "$(TARGET_ARCH)" "x86_64"
+ base_version_arch := 8
+else
+ base_version_arch := 0
+endif
+
+ifeq "$(package_dpi)" "mdpi"
+ base_version_density := 2
+else ifeq "$(package_dpi)" "hdpi"
+ base_version_density := 4
+else ifeq "$(package_dpi)" "xhdpi"
+ base_version_density := 6
+else ifeq "$(package_dpi)" "xxhdpi"
+ base_version_density := 8
+else ifeq "$(package_dpi)" "xxxhdpi"
+ base_version_density := 9
+else
+ base_version_density := 0
+endif
+
+# Build the version code
+version_code_package := $(base_version_major)$(base_version_minor)$(base_version_build)$(base_version_buildtype)$(base_version_arch)$(base_version_density)
+
+# The version name scheme for the package apk is:
+# - For eng build (t=1): M.m.bbb eng.$(USER)-hh
+# - For build server (t=0): M.m.bbb (nnnnnn-hh)
+# where nnnnnn is the build number from the build server (no zero-padding)
+# On eng builds, the BUILD_NUMBER has the user and timestamp inline
+ifneq "" "$(filter eng.%,$(BUILD_NUMBER))"
+ git_hash := $(shell git --git-dir $(LOCAL_PATH)/.git log -n 1 --pretty=format:%h)
+ date_string := $(shell date +%m%d%y_%H%M%S)
+ version_name_package := $(base_version_major).$(base_version_minor).$(base_version_build) (eng.$(USER).$(git_hash).$(date_string)-$(base_version_arch)$(base_version_density))
+else
+ version_name_package := $(base_version_major).$(base_version_minor).$(base_version_build) ($(BUILD_NUMBER)-$(base_version_arch)$(base_version_density))
+endif
+
+# Cleanup the locals
+base_version_major :=
+base_version_minor :=
+base_version_build :=
+base_version_buildtype :=
+base_version_arch :=
+base_version_density :=
+git_hash :=
+date_string :=